cli: common backend for init and upgrade apply commands (#2449)

* Use common 'apply' backend for init and upgrades
* Move unit tests to new apply backend
* Only perform Terraform migrations if state exists in cwd (#2457)
* Rework skipPhases logic

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
Co-authored-by: Moritz Sanft <58110325+msanft@users.noreply.github.com>
This commit is contained in:
Daniel Weiße 2023-10-24 15:39:18 +02:00 committed by GitHub
parent 15d249092c
commit 671cf36f0a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1399 additions and 1018 deletions

View file

@ -44,6 +44,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/clientcmd"
k8sclientapi "k8s.io/client-go/tools/clientcmd/api"
)
@ -58,8 +60,6 @@ func TestInitArgumentValidation(t *testing.T) {
}
func TestInitialize(t *testing.T) {
require := require.New(t)
respKubeconfig := k8sclientapi.Config{
Clusters: map[string]*k8sclientapi.Cluster{
"cluster": {
@ -68,7 +68,7 @@ func TestInitialize(t *testing.T) {
},
}
respKubeconfigBytes, err := clientcmd.Write(respKubeconfig)
require.NoError(err)
require.NoError(t, err)
gcpServiceAccKey := &gcpshared.ServiceAccountKey{
Type: "service_account",
@ -149,31 +149,47 @@ func TestInitialize(t *testing.T) {
masterSecretShouldExist: true,
wantErr: true,
},
"state file with only version": {
provider: cloudprovider.GCP,
stateFile: &state.State{Version: state.Version1},
initServerAPI: &stubInitServer{},
retriable: true,
wantErr: true,
},
"empty state file": {
provider: cloudprovider.GCP,
stateFile: &state.State{},
initServerAPI: &stubInitServer{},
retriable: true,
wantErr: true,
},
/*
Tests currently disabled since we don't actually have validation for the state file yet
These tests cases only passed in the past because of unrelated errors in the test setup
TODO(AB#3492): Re-enable tests once state file validation is implemented
"state file with only version": {
provider: cloudprovider.GCP,
stateFile: &state.State{Version: state.Version1},
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
serviceAccKey: gcpServiceAccKey,
initServerAPI: &stubInitServer{},
retriable: true,
wantErr: true,
},
"empty state file": {
provider: cloudprovider.GCP,
stateFile: &state.State{},
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
serviceAccKey: gcpServiceAccKey,
initServerAPI: &stubInitServer{},
retriable: true,
wantErr: true,
},
*/
"no state file": {
provider: cloudprovider.GCP,
retriable: true,
wantErr: true,
provider: cloudprovider.GCP,
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
serviceAccKey: gcpServiceAccKey,
retriable: true,
wantErr: true,
},
"init call fails": {
provider: cloudprovider.GCP,
stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}},
initServerAPI: &stubInitServer{initErr: assert.AnError},
retriable: true,
wantErr: true,
provider: cloudprovider.GCP,
configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath },
stateFile: &state.State{Version: state.Version1, Infrastructure: state.Infrastructure{ClusterEndpoint: "192.0.2.1"}},
serviceAccKey: gcpServiceAccKey,
initServerAPI: &stubInitServer{initErr: assert.AnError},
retriable: false,
masterSecretShouldExist: true,
wantErr: true,
},
"k8s version without v works": {
provider: cloudprovider.Azure,
@ -181,7 +197,7 @@ func TestInitialize(t *testing.T) {
initServerAPI: &stubInitServer{res: []*initproto.InitResponse{{Kind: &initproto.InitResponse_InitSuccess{InitSuccess: testInitResp}}}},
configMutator: func(c *config.Config) {
res, err := versions.NewValidK8sVersion(strings.TrimPrefix(string(versions.Default), "v"), true)
require.NoError(err)
require.NoError(t, err)
c.KubernetesVersion = res
},
},
@ -191,7 +207,7 @@ func TestInitialize(t *testing.T) {
initServerAPI: &stubInitServer{res: []*initproto.InitResponse{{Kind: &initproto.InitResponse_InitSuccess{InitSuccess: testInitResp}}}},
configMutator: func(c *config.Config) {
v, err := semver.New(versions.SupportedK8sVersions()[0])
require.NoError(err)
require.NoError(t, err)
outdatedPatchVer := semver.NewFromInt(v.Major(), v.Minor(), v.Patch()-1, "").String()
c.KubernetesVersion = versions.ValidK8sVersion(outdatedPatchVer)
},
@ -203,6 +219,7 @@ func TestInitialize(t *testing.T) {
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
// Networking
netDialer := testdialer.NewBufconnDialer()
newDialer := func(atls.Validator) *dialer.Dialer {
@ -231,8 +248,6 @@ func TestInitialize(t *testing.T) {
tc.configMutator(config)
}
require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptNone))
stateFile := state.New()
require.NoError(stateFile.WriteToFile(fileHandler, constants.StateFilename))
if tc.stateFile != nil {
require.NoError(tc.stateFile.WriteToFile(fileHandler, constants.StateFilename))
}
@ -244,20 +259,28 @@ func TestInitialize(t *testing.T) {
ctx, cancel := context.WithTimeout(ctx, 4*time.Second)
defer cancel()
cmd.SetContext(ctx)
i := newInitCmd(fileHandler, &nopSpinner{}, nil, logger.NewTest(t))
i.flags.force = true
err := i.initialize(
cmd,
newDialer,
&stubLicenseClient{},
stubAttestationFetcher{},
func(io.Writer, string, debugLog) (attestationConfigApplier, error) {
return &stubAttestationApplier{}, nil
},
func(_ string, _ debugLog) (helmApplier, error) {
i := &applyCmd{
fileHandler: fileHandler,
flags: applyFlags{rootFlags: rootFlags{force: true}},
log: logger.NewTest(t),
spinner: &nopSpinner{},
merger: &stubMerger{},
quotaChecker: &stubLicenseClient{},
newHelmClient: func(string, debugLog) (helmApplier, error) {
return &stubApplier{}, nil
})
},
newDialer: newDialer,
newKubeUpgrader: func(io.Writer, string, debugLog) (kubernetesUpgrader, error) {
return &stubKubernetesUpgrader{
// On init, no attestation config exists yet
getClusterAttestationConfigErr: k8serrors.NewNotFound(schema.GroupResource{}, ""),
}, nil
},
clusterUpgrader: stubTerraformUpgrader{},
}
err := i.apply(cmd, stubAttestationFetcher{}, "test")
if tc.wantErr {
assert.Error(err)
@ -291,14 +314,17 @@ func (s stubApplier) PrepareApply(_ *config.Config, _ *state.State, _ helm.Optio
return stubRunner{}, false, s.err
}
type stubRunner struct{}
type stubRunner struct {
applyErr error
saveChartsErr error
}
func (s stubRunner) Apply(_ context.Context) error {
return nil
return s.applyErr
}
func (s stubRunner) SaveCharts(_ string, _ file.Handler) error {
return nil
return s.saveChartsErr
}
func TestGetLogs(t *testing.T) {
@ -420,8 +446,13 @@ func TestWriteOutput(t *testing.T) {
ClusterEndpoint: clusterEndpoint,
})
i := newInitCmd(fileHandler, &nopSpinner{}, &stubMerger{}, logger.NewTest(t))
err = i.writeOutput(stateFile, resp.GetInitSuccess(), false, &out, measurementSalt)
i := &applyCmd{
fileHandler: fileHandler,
spinner: &nopSpinner{},
merger: &stubMerger{},
log: logger.NewTest(t),
}
err = i.writeInitOutput(stateFile, resp.GetInitSuccess(), false, &out, measurementSalt)
require.NoError(err)
assert.Contains(out.String(), clusterID)
assert.Contains(out.String(), constants.AdminConfFilename)
@ -441,7 +472,7 @@ func TestWriteOutput(t *testing.T) {
// test custom workspace
i.flags.pathPrefixer = pathprefix.New("/some/path")
err = i.writeOutput(stateFile, resp.GetInitSuccess(), true, &out, measurementSalt)
err = i.writeInitOutput(stateFile, resp.GetInitSuccess(), true, &out, measurementSalt)
require.NoError(err)
assert.Contains(out.String(), clusterID)
assert.Contains(out.String(), i.flags.pathPrefixer.PrefixPrintablePath(constants.AdminConfFilename))
@ -451,7 +482,7 @@ func TestWriteOutput(t *testing.T) {
i.flags.pathPrefixer = pathprefix.PathPrefixer{}
// test config merging
err = i.writeOutput(stateFile, resp.GetInitSuccess(), true, &out, measurementSalt)
err = i.writeInitOutput(stateFile, resp.GetInitSuccess(), true, &out, measurementSalt)
require.NoError(err)
assert.Contains(out.String(), clusterID)
assert.Contains(out.String(), constants.AdminConfFilename)
@ -462,7 +493,7 @@ func TestWriteOutput(t *testing.T) {
// test config merging with env vars set
i.merger = &stubMerger{envVar: "/some/path/to/kubeconfig"}
err = i.writeOutput(stateFile, resp.GetInitSuccess(), true, &out, measurementSalt)
err = i.writeInitOutput(stateFile, resp.GetInitSuccess(), true, &out, measurementSalt)
require.NoError(err)
assert.Contains(out.String(), clusterID)
assert.Contains(out.String(), constants.AdminConfFilename)
@ -508,8 +539,11 @@ func TestGenerateMasterSecret(t *testing.T) {
require.NoError(tc.createFileFunc(fileHandler))
var out bytes.Buffer
i := newInitCmd(fileHandler, nil, nil, logger.NewTest(t))
secret, err := i.generateMasterSecret(&out)
i := &applyCmd{
fileHandler: fileHandler,
log: logger.NewTest(t),
}
secret, err := i.generateAndPersistMasterSecret(&out)
if tc.wantErr {
assert.Error(err)
@ -601,13 +635,17 @@ func TestAttestation(t *testing.T) {
defer cancel()
cmd.SetContext(ctx)
i := newInitCmd(fileHandler, &nopSpinner{}, nil, logger.NewTest(t))
err := i.initialize(cmd, newDialer, &stubLicenseClient{}, stubAttestationFetcher{},
func(io.Writer, string, debugLog) (attestationConfigApplier, error) {
return &stubAttestationApplier{}, nil
}, func(_ string, _ debugLog) (helmApplier, error) {
return &stubApplier{}, nil
})
i := &applyCmd{
fileHandler: fileHandler,
spinner: &nopSpinner{},
merger: &stubMerger{},
log: logger.NewTest(t),
newKubeUpgrader: func(io.Writer, string, debugLog) (kubernetesUpgrader, error) {
return &stubKubernetesUpgrader{}, nil
},
newDialer: newDialer,
}
_, err := i.runInit(cmd, cfg, existingStateFile)
assert.Error(err)
// make sure the error is actually a TLS handshake error
assert.Contains(err.Error(), "transport: authentication handshake failed")
@ -773,11 +811,3 @@ func (c stubInitClient) Recv() (*initproto.InitResponse, error) {
return res, err
}
type stubAttestationApplier struct {
applyErr error
}
func (a *stubAttestationApplier) ApplyJoinConfig(context.Context, config.AttestationCfg, []byte) error {
return a.applyErr
}