From 5fa9468d3acb7c0e6929055a96e61584fa5a4754 Mon Sep 17 00:00:00 2001 From: Moritz Sanft <58110325+msanft@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:41:06 +0200 Subject: [PATCH] use state file in CLI Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> take clusterConfig from IDFile for compat Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> various fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> wip Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> --- cli/internal/cmd/create.go | 4 +- cli/internal/cmd/init.go | 7 +- cli/internal/cmd/init_test.go | 29 +++--- cli/internal/cmd/upgradeapply.go | 143 ++++++++++---------------- cli/internal/cmd/upgradeapply_test.go | 133 ++++++++++++++---------- cli/internal/helm/helm_test.go | 15 +-- cli/internal/helm/loader_test.go | 6 +- cli/internal/state/BUILD.bazel | 2 + cli/internal/state/state.go | 50 ++++++++- cli/internal/state/state_test.go | 59 ++++++++++- 10 files changed, 267 insertions(+), 181 deletions(-) diff --git a/cli/internal/cmd/create.go b/cli/internal/cmd/create.go index 347ee83dc8..a718d4d70a 100644 --- a/cli/internal/cmd/create.go +++ b/cli/internal/cmd/create.go @@ -172,13 +172,13 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler } c.log.Debugf("Successfully created the cloud resources for the cluster") - // TODO(msanft): Remove IDFile as per AB#3354 + // TODO(msanft): Remove IDFile as per AB#3425 idFile := convertToIDFile(infraState, provider) if err := fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptNone); err != nil { return err } - state := state.NewState().SetInfrastructure(infraState) + state := state.New().SetInfrastructure(infraState) if err := state.WriteToFile(fileHandler, constants.StateFilename); err != nil { return fmt.Errorf("writing state file: %w", err) } diff --git a/cli/internal/cmd/init.go b/cli/internal/cmd/init.go index f9aafa5f4f..424c7ecf38 100644 --- a/cli/internal/cmd/init.go +++ b/cli/internal/cmd/init.go @@ -9,7 +9,6 @@ package cmd import ( "bytes" "context" - "encoding/base64" "encoding/hex" "errors" "fmt" @@ -157,7 +156,7 @@ func (i *initCmd) initialize( cmd.PrintErrln("WARNING: Attestation temporarily relies on AWS nitroTPM. See https://docs.edgeless.systems/constellation/workflows/config#choosing-a-vm-type for more information.") } - // TODO(msanft): Remove IDFile as per AB#3354 + // TODO(msanft): Remove IDFile as per AB#3425 i.log.Debugf("Checking cluster ID file") var idFile clusterid.File if err := i.fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { @@ -208,7 +207,7 @@ func (i *initCmd) initialize( idFile.MeasurementSalt = measurementSalt stateFile.SetClusterValues(state.ClusterValues{ - MeasurementSalt: base64.StdEncoding.EncodeToString(measurementSalt), + MeasurementSalt: measurementSalt, }) clusterName := clusterid.GetClusterName(conf, idFile) @@ -246,7 +245,7 @@ func (i *initCmd) initialize( } i.log.Debugf("Initialization request succeeded") - // TODO(msanft): Remove IDFile as per AB#3354 + // TODO(msanft): Remove IDFile as per AB#3425 i.log.Debugf("Writing Constellation ID file") idFile.CloudProvider = provider diff --git a/cli/internal/cmd/init_test.go b/cli/internal/cmd/init_test.go index a917decc3f..2d53e423c1 100644 --- a/cli/internal/cmd/init_test.go +++ b/cli/internal/cmd/init_test.go @@ -239,7 +239,7 @@ func TestInitialize(t *testing.T) { tc.configMutator(config) } require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptNone)) - stateFile := state.NewState() + stateFile := state.New() require.NoError(stateFile.WriteToFile(fileHandler, constants.StateFilename)) if tc.idFile != nil { tc.idFile.CloudProvider = tc.provider @@ -415,8 +415,12 @@ func TestWriteOutput(t *testing.T) { } expectedStateFile := &state.State{ - Version: state.Version1, - ClusterValues: state.ClusterValues{ClusterID: clusterID, OwnerID: ownerID}, + Version: state.Version1, + ClusterValues: state.ClusterValues{ + ClusterID: clusterID, + OwnerID: ownerID, + MeasurementSalt: []byte{}, + }, Infrastructure: state.Infrastructure{APIServerCertSANs: []string{}}, } @@ -428,7 +432,7 @@ func TestWriteOutput(t *testing.T) { UID: "test-uid", IP: clusterEndpoint, } - stateFile := state.NewState() + stateFile := state.New() i := newInitCmd(fileHandler, &nopSpinner{}, &stubMerger{}, logger.NewTest(t)) err = i.writeOutput(idFile, stateFile, resp.GetInitSuccess(), false, &out) @@ -793,23 +797,18 @@ func (c stubInitClient) Recv() (*initproto.InitResponse, error) { return res, err } -type stubShowInfrastructure struct{} +type stubShowInfrastructure struct { + showInfraErr error +} -func (s *stubShowInfrastructure) ShowInfrastructure(_ context.Context, csp cloudprovider.Provider) (state.Infrastructure, error) { - res := state.Infrastructure{} - switch csp { - case cloudprovider.Azure: - res.Azure = &state.Azure{} - case cloudprovider.GCP: - res.GCP = &state.GCP{} - } - return res, nil +func (s *stubShowInfrastructure) ShowInfrastructure(context.Context, cloudprovider.Provider) (state.Infrastructure, error) { + return state.Infrastructure{}, s.showInfraErr } type stubAttestationApplier struct { applyErr error } -func (a *stubAttestationApplier) ApplyJoinConfig(_ context.Context, _ config.AttestationCfg, _ []byte) error { +func (a *stubAttestationApplier) ApplyJoinConfig(context.Context, config.AttestationCfg, []byte) error { return a.applyErr } diff --git a/cli/internal/cmd/upgradeapply.go b/cli/internal/cmd/upgradeapply.go index 039a0796de..8001014970 100644 --- a/cli/internal/cmd/upgradeapply.go +++ b/cli/internal/cmd/upgradeapply.go @@ -176,11 +176,18 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, upgradeDir string, fl return err } + // TODO(msanft): Remove reading from idFile once v2.12.0 is released and read from state file directly. + // For now, this is only here to ensure upgradability from an id-file to a state file version. var idFile clusterid.File if err := u.fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { return fmt.Errorf("reading cluster ID file: %w", err) } - conf.UpdateMAAURL(idFile.AttestationURL) + + // Convert id-file to state file + stateFile := state.NewFromIDFile(idFile) + if stateFile.Infrastructure.Azure != nil { + conf.UpdateMAAURL(stateFile.Infrastructure.Azure.AttestationURL) + } // Apply migrations necessary for the upgrade if err := migrateFrom2_10(cmd.Context(), u.kubeUpgrader); err != nil { @@ -190,32 +197,51 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, upgradeDir string, fl return fmt.Errorf("applying migration for upgrading from v2.11: %w", err) } - if err := u.confirmAndUpgradeAttestationConfig(cmd, conf.GetAttestationConfig(), idFile.MeasurementSalt, flags); err != nil { + if err := u.confirmAndUpgradeAttestationConfig(cmd, conf.GetAttestationConfig(), stateFile.ClusterValues.MeasurementSalt, flags); err != nil { return fmt.Errorf("upgrading measurements: %w", err) } - // Perform Terraform migrations if infrastructure upgrade phase is not skipped. - // If it is skipped, we expect the new infrastructure to be in the state file - // already. - if !flags.skipPhases.contains(skipInfrastructurePhase) { - if err := u.migrateTerraform(cmd, conf, upgradeDir, flags); err != nil { + // If infrastructure phase is skipped, we expect the new infrastructure + // to be in the Terraform configuration already. Otherwise, perform + // the Terraform migrations. + var postMigrationInfraState state.Infrastructure + if flags.skipPhases.contains(skipInfrastructurePhase) { + postMigrationInfraState, err = u.clusterShower.ShowInfrastructure(cmd.Context(), conf.GetProvider()) + if err != nil { + return fmt.Errorf("getting Terraform state: %w", err) + } + } else { + postMigrationInfraState, err = u.migrateTerraform(cmd, conf, upgradeDir, flags) + if err != nil { return fmt.Errorf("performing Terraform migrations: %w", err) } } - // reload idFile after terraform migration - // it might have been updated by the migration - if err := u.fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { - return fmt.Errorf("reading updated cluster ID file: %w", err) + + // Merge the pre-upgrade state with the post-migration infrastructure values + if _, err := stateFile.Merge( + // temporary state with post-migration infrastructure values + state.New().SetInfrastructure(postMigrationInfraState), + ); err != nil { + return fmt.Errorf("merging pre-upgrade state with post-migration infrastructure values: %w", err) } - stateFile, err := state.ReadFromFile(u.fileHandler, constants.StateFilename) - if err != nil { - return fmt.Errorf("reading state file: %w", err) + + // Write the post-migration state to disk + if err := stateFile.WriteToFile(u.fileHandler, constants.StateFilename); err != nil { + return fmt.Errorf("writing state file: %w", err) } + cmd.Printf("Infrastructure migrations applied successfully and output written to: %s and %s\n"+ + "A backup of the pre-upgrade state has been written to: %s\n", + flags.pf.PrefixPrintablePath(constants.StateFilename), + flags.pf.PrefixPrintablePath(constants.ClusterIDsFilename), + flags.pf.PrefixPrintablePath(filepath.Join(upgradeDir, constants.TerraformUpgradeBackupDir)), + ) + // extend the clusterConfig cert SANs with any of the supported endpoints: // - (legacy) public IP // - fallback endpoint // - custom (user-provided) endpoint + // At this point, state file and id-file should have been merged, so we can use the state file. sans := append([]string{stateFile.Infrastructure.ClusterEndpoint, conf.CustomEndpoint}, stateFile.Infrastructure.APIServerCertSANs...) if err := u.kubeUpgrader.ExtendClusterConfigCertSANs(cmd.Context(), sans); err != nil { return fmt.Errorf("extending cert SANs: %w", err) @@ -269,14 +295,15 @@ func diffAttestationCfg(currentAttestationCfg config.AttestationCfg, newAttestat } // migrateTerraform checks if the Constellation version the cluster is being upgraded to requires a migration -// of cloud resources with Terraform. If so, the migration is performed and the output is written to the state / ID file. -func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, conf *config.Config, upgradeDir string, flags upgradeApplyFlags, -) error { +// of cloud resources with Terraform. If so, the migration is performed and the post-migration infrastructure state is returned. +func (u *upgradeApplyCmd) migrateTerraform( + cmd *cobra.Command, conf *config.Config, upgradeDir string, flags upgradeApplyFlags, +) (state.Infrastructure, error) { u.log.Debugf("Planning Terraform migrations") vars, err := cloudcmd.TerraformUpgradeVars(conf) if err != nil { - return fmt.Errorf("parsing upgrade variables: %w", err) + return state.Infrastructure{}, fmt.Errorf("parsing upgrade variables: %w", err) } u.log.Debugf("Using Terraform variables:\n%v", vars) @@ -292,7 +319,7 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, conf *config.Conf hasDiff, err := u.clusterUpgrader.PlanClusterUpgrade(cmd.Context(), cmd.OutOrStdout(), vars, conf.GetProvider()) if err != nil { - return fmt.Errorf("planning terraform migrations: %w", err) + return state.Infrastructure{}, fmt.Errorf("planning terraform migrations: %w", err) } if hasDiff { @@ -301,45 +328,35 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, conf *config.Conf if !flags.yes { ok, err := askToConfirm(cmd, "Do you want to apply the Terraform migrations?") if err != nil { - return fmt.Errorf("asking for confirmation: %w", err) + return state.Infrastructure{}, fmt.Errorf("asking for confirmation: %w", err) } if !ok { cmd.Println("Aborting upgrade.") // User doesn't expect to see any changes in his workspace after aborting an "upgrade apply", // therefore, roll back to the backed up state. if err := u.clusterUpgrader.RestoreClusterWorkspace(); err != nil { - return fmt.Errorf( + return state.Infrastructure{}, fmt.Errorf( "restoring Terraform workspace: %w, restore the Terraform workspace manually from %s ", err, filepath.Join(upgradeDir, constants.TerraformUpgradeBackupDir), ) } - return fmt.Errorf("cluster upgrade aborted by user") + return state.Infrastructure{}, fmt.Errorf("cluster upgrade aborted by user") } } u.log.Debugf("Applying Terraform migrations") + infraState, err := u.clusterUpgrader.ApplyClusterUpgrade(cmd.Context(), conf.GetProvider()) if err != nil { - return fmt.Errorf("applying terraform migrations: %w", err) + return state.Infrastructure{}, fmt.Errorf("applying terraform migrations: %w", err) } - // Apply possible updates to cluster ID file - if err := updateClusterIDFile(infraState, u.fileHandler); err != nil { - return fmt.Errorf("merging cluster ID files: %w", err) - } - - cmd.Printf("Terraform migrations applied successfully and output written to: %s and %s\n"+ - "A backup of the pre-upgrade state has been written to: %s\n", - flags.pf.PrefixPrintablePath(constants.StateFilename), - flags.pf.PrefixPrintablePath(constants.ClusterIDsFilename), - flags.pf.PrefixPrintablePath(filepath.Join(upgradeDir, constants.TerraformUpgradeBackupDir)), - ) - } else { - u.log.Debugf("No Terraform diff detected") + cmd.Printf("Terraform migrations applied successfully.") + return infraState, nil } - u.log.Debugf("No Terraform diff detected") - return nil + u.log.Debugf("No Terraform diff detected") + return state.Infrastructure{}, nil } // validK8sVersion checks if the Kubernetes patch version is supported and asks for confirmation if not. @@ -401,7 +418,7 @@ func (u *upgradeApplyCmd) confirmAndUpgradeAttestationConfig( if err := u.kubeUpgrader.ApplyJoinConfig(cmd.Context(), newConfig, measurementSalt); err != nil { return fmt.Errorf("updating attestation config: %w", err) } - cmd.Println("Successfully update the cluster's attestation config") + cmd.Println("Successfully updated the cluster's attestation config") return nil } @@ -579,54 +596,6 @@ func parseUpgradeApplyFlags(cmd *cobra.Command) (upgradeApplyFlags, error) { }, nil } -func updateClusterIDFile(infraState state.Infrastructure, fileHandler file.Handler) error { - newIDFile := clusterid.File{ - InitSecret: []byte(infraState.InitSecret), - IP: infraState.ClusterEndpoint, - APIServerCertSANs: infraState.APIServerCertSANs, - UID: infraState.UID, - } - if infraState.Azure != nil { - newIDFile.AttestationURL = infraState.Azure.AttestationURL - } - newStateFile := state.NewState() - newStateFile.SetInfrastructure(state.Infrastructure{ - InitSecret: infraState.InitSecret, - ClusterEndpoint: infraState.ClusterEndpoint, - APIServerCertSANs: infraState.APIServerCertSANs, - UID: infraState.UID, - }) - if infraState.Azure != nil { - newStateFile.Infrastructure.Azure = &state.Azure{ - AttestationURL: infraState.Azure.AttestationURL, - } - } - - idFile := &clusterid.File{} - if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, idFile); err != nil { - return fmt.Errorf("reading %s: %w", constants.ClusterIDsFilename, err) - } - - stateFile, err := state.ReadFromFile(fileHandler, constants.StateFilename) - if err != nil { - return fmt.Errorf("reading %s: %w", constants.StateFilename, err) - } - - if err := fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile.Merge(newIDFile), file.OptOverwrite); err != nil { - return fmt.Errorf("writing %s: %w", constants.ClusterIDsFilename, err) - } - - _, err = stateFile.Merge(newStateFile) - if err != nil { - return fmt.Errorf("merging state: %w", err) - } - if err := stateFile.WriteToFile(fileHandler, constants.StateFilename); err != nil { - return fmt.Errorf("writing %s: %w", constants.StateFilename, err) - } - - return nil -} - type upgradeApplyFlags struct { pf pathprefix.PathPrefixer yes bool diff --git a/cli/internal/cmd/upgradeapply_test.go b/cli/internal/cmd/upgradeapply_test.go index d4f00dc979..b7eafe2552 100644 --- a/cli/internal/cmd/upgradeapply_test.go +++ b/cli/internal/cmd/upgradeapply_test.go @@ -35,74 +35,82 @@ import ( func TestUpgradeApply(t *testing.T) { testCases := map[string]struct { - helmUpgrader helmApplier - kubeUpgrader *stubKubernetesUpgrader - terraformUpgrader clusterUpgrader - wantErr bool - customK8sVersion string - flags upgradeApplyFlags - stdin string + helmUpgrader helmApplier + kubeUpgrader *stubKubernetesUpgrader + terraformUpgrader clusterUpgrader + infrastructureShower *stubShowInfrastructure + wantErr bool + customK8sVersion string + flags upgradeApplyFlags + stdin string }{ "success": { - kubeUpgrader: &stubKubernetesUpgrader{currentConfig: config.DefaultForAzureSEVSNP()}, - helmUpgrader: stubApplier{}, - terraformUpgrader: &stubTerraformUpgrader{}, - flags: upgradeApplyFlags{yes: true}, + kubeUpgrader: &stubKubernetesUpgrader{currentConfig: config.DefaultForAzureSEVSNP()}, + helmUpgrader: stubApplier{}, + terraformUpgrader: &stubTerraformUpgrader{}, + flags: upgradeApplyFlags{yes: true}, + infrastructureShower: &stubShowInfrastructure{}, }, "nodeVersion some error": { kubeUpgrader: &stubKubernetesUpgrader{ currentConfig: config.DefaultForAzureSEVSNP(), nodeVersionErr: assert.AnError, }, - helmUpgrader: stubApplier{}, - terraformUpgrader: &stubTerraformUpgrader{}, - wantErr: true, - flags: upgradeApplyFlags{yes: true}, + helmUpgrader: stubApplier{}, + terraformUpgrader: &stubTerraformUpgrader{}, + wantErr: true, + flags: upgradeApplyFlags{yes: true}, + infrastructureShower: &stubShowInfrastructure{}, }, "nodeVersion in progress error": { kubeUpgrader: &stubKubernetesUpgrader{ currentConfig: config.DefaultForAzureSEVSNP(), nodeVersionErr: kubecmd.ErrInProgress, }, - helmUpgrader: stubApplier{}, - terraformUpgrader: &stubTerraformUpgrader{}, - flags: upgradeApplyFlags{yes: true}, + helmUpgrader: stubApplier{}, + terraformUpgrader: &stubTerraformUpgrader{}, + flags: upgradeApplyFlags{yes: true}, + infrastructureShower: &stubShowInfrastructure{}, }, "helm other error": { kubeUpgrader: &stubKubernetesUpgrader{ currentConfig: config.DefaultForAzureSEVSNP(), }, - helmUpgrader: stubApplier{err: assert.AnError}, - terraformUpgrader: &stubTerraformUpgrader{}, - wantErr: true, - flags: upgradeApplyFlags{yes: true}, + helmUpgrader: stubApplier{err: assert.AnError}, + terraformUpgrader: &stubTerraformUpgrader{}, + wantErr: true, + flags: upgradeApplyFlags{yes: true}, + infrastructureShower: &stubShowInfrastructure{}, }, "abort": { kubeUpgrader: &stubKubernetesUpgrader{ currentConfig: config.DefaultForAzureSEVSNP(), }, - helmUpgrader: stubApplier{}, - terraformUpgrader: &stubTerraformUpgrader{terraformDiff: true}, - wantErr: true, - stdin: "no\n", + helmUpgrader: stubApplier{}, + terraformUpgrader: &stubTerraformUpgrader{terraformDiff: true}, + wantErr: true, + stdin: "no\n", + infrastructureShower: &stubShowInfrastructure{}, }, "abort, restore terraform err": { kubeUpgrader: &stubKubernetesUpgrader{ currentConfig: config.DefaultForAzureSEVSNP(), }, - helmUpgrader: stubApplier{}, - terraformUpgrader: &stubTerraformUpgrader{terraformDiff: true, rollbackWorkspaceErr: assert.AnError}, - wantErr: true, - stdin: "no\n", + helmUpgrader: stubApplier{}, + terraformUpgrader: &stubTerraformUpgrader{terraformDiff: true, rollbackWorkspaceErr: assert.AnError}, + wantErr: true, + stdin: "no\n", + infrastructureShower: &stubShowInfrastructure{}, }, "plan terraform error": { kubeUpgrader: &stubKubernetesUpgrader{ currentConfig: config.DefaultForAzureSEVSNP(), }, - helmUpgrader: stubApplier{}, - terraformUpgrader: &stubTerraformUpgrader{planTerraformErr: assert.AnError}, - wantErr: true, - flags: upgradeApplyFlags{yes: true}, + helmUpgrader: stubApplier{}, + terraformUpgrader: &stubTerraformUpgrader{planTerraformErr: assert.AnError}, + wantErr: true, + flags: upgradeApplyFlags{yes: true}, + infrastructureShower: &stubShowInfrastructure{}, }, "apply terraform error": { kubeUpgrader: &stubKubernetesUpgrader{ @@ -113,8 +121,9 @@ func TestUpgradeApply(t *testing.T) { applyTerraformErr: assert.AnError, terraformDiff: true, }, - wantErr: true, - flags: upgradeApplyFlags{yes: true}, + wantErr: true, + flags: upgradeApplyFlags{yes: true}, + infrastructureShower: &stubShowInfrastructure{}, }, "outdated K8s patch version": { kubeUpgrader: &stubKubernetesUpgrader{ @@ -127,18 +136,19 @@ func TestUpgradeApply(t *testing.T) { require.NoError(t, err) return semver.NewFromInt(v.Major(), v.Minor(), v.Patch()-1, "").String() }(), - flags: upgradeApplyFlags{yes: true}, - wantErr: false, + flags: upgradeApplyFlags{yes: true}, + infrastructureShower: &stubShowInfrastructure{}, }, "outdated K8s version": { kubeUpgrader: &stubKubernetesUpgrader{ currentConfig: config.DefaultForAzureSEVSNP(), }, - helmUpgrader: stubApplier{}, - terraformUpgrader: &stubTerraformUpgrader{}, - customK8sVersion: "v1.20.0", - flags: upgradeApplyFlags{yes: true}, - wantErr: true, + helmUpgrader: stubApplier{}, + terraformUpgrader: &stubTerraformUpgrader{}, + customK8sVersion: "v1.20.0", + flags: upgradeApplyFlags{yes: true}, + wantErr: true, + infrastructureShower: &stubShowInfrastructure{}, }, "skip all upgrade phases": { kubeUpgrader: &stubKubernetesUpgrader{ @@ -150,6 +160,22 @@ func TestUpgradeApply(t *testing.T) { skipPhases: []skipPhase{skipInfrastructurePhase, skipHelmPhase, skipK8sPhase, skipImagePhase}, yes: true, }, + infrastructureShower: &stubShowInfrastructure{}, + }, + "show state err": { + kubeUpgrader: &stubKubernetesUpgrader{ + currentConfig: config.DefaultForAzureSEVSNP(), + }, + helmUpgrader: &stubApplier{}, + terraformUpgrader: &stubTerraformUpgrader{}, + flags: upgradeApplyFlags{ + skipPhases: []skipPhase{skipInfrastructurePhase}, + yes: true, + }, + infrastructureShower: &stubShowInfrastructure{ + showInfraErr: assert.AnError, + }, + wantErr: true, }, "skip all phases except node upgrade": { kubeUpgrader: &stubKubernetesUpgrader{ @@ -161,6 +187,7 @@ func TestUpgradeApply(t *testing.T) { skipPhases: []skipPhase{skipInfrastructurePhase, skipHelmPhase, skipK8sPhase}, yes: true, }, + infrastructureShower: &stubShowInfrastructure{}, }, } @@ -178,12 +205,11 @@ func TestUpgradeApply(t *testing.T) { cfg.KubernetesVersion = versions.ValidK8sVersion(tc.customK8sVersion) } require.NoError(handler.WriteYAML(constants.ConfigFilename, cfg)) - require.NoError(handler.WriteJSON(constants.ClusterIDsFilename, clusterid.File{MeasurementSalt: []byte("measurementSalt")})) + require.NoError(handler.WriteJSON(constants.ClusterIDsFilename, clusterid.File{ + MeasurementSalt: []byte{0x41}, + UID: "uid", + })) require.NoError(handler.WriteJSON(constants.MasterSecretFilename, uri.MasterSecret{})) - stateFile := state.NewState().SetClusterValues(state.ClusterValues{ - MeasurementSalt: "measurementSalt", - }) - require.NoError(stateFile.WriteToFile(handler, constants.StateFilename)) upgrader := upgradeApplyCmd{ kubeUpgrader: tc.kubeUpgrader, @@ -191,7 +217,7 @@ func TestUpgradeApply(t *testing.T) { clusterUpgrader: tc.terraformUpgrader, log: logger.NewTest(t), configFetcher: stubAttestationFetcher{}, - clusterShower: &stubShowInfrastructure{}, + clusterShower: tc.infrastructureShower, fileHandler: handler, } @@ -204,9 +230,12 @@ func TestUpgradeApply(t *testing.T) { assert.Equal(!tc.flags.skipPhases.contains(skipImagePhase), tc.kubeUpgrader.calledNodeUpgrade, "incorrect node upgrade skipping behavior") - expectedState := state.NewState(). - SetInfrastructure(state.Infrastructure{APIServerCertSANs: []string{}}). - SetClusterValues(state.ClusterValues{MeasurementSalt: "measurementSalt"}) + expectedState := state.New(). + SetInfrastructure(state.Infrastructure{ + APIServerCertSANs: []string{}, + UID: "uid", + }). + SetClusterValues(state.ClusterValues{MeasurementSalt: []byte{0x41}}) gotState, err := state.ReadFromFile(handler, constants.StateFilename) require.NoError(err) assert.Equal("v1", gotState.Version) diff --git a/cli/internal/helm/helm_test.go b/cli/internal/helm/helm_test.go index 317179b3f1..9b3f836151 100644 --- a/cli/internal/helm/helm_test.go +++ b/cli/internal/helm/helm_test.go @@ -207,9 +207,9 @@ func TestHelmApply(t *testing.T) { options.AllowDestructive = tc.allowDestructive ex, includesUpgrade, err := sut.PrepareApply(cfg, - state.NewState(). + state.New(). SetInfrastructure(state.Infrastructure{UID: "testuid"}). - SetClusterValues(state.ClusterValues{MeasurementSalt: "measurementSalt"}), + SetClusterValues(state.ClusterValues{MeasurementSalt: []byte{0x41}}), options, fakeServiceAccURI(csp), uri.MasterSecret{Key: []byte("secret"), Salt: []byte("masterSalt")}) var upgradeErr *compatibility.InvalidUpgradeError @@ -226,17 +226,6 @@ func TestHelmApply(t *testing.T) { } } -func fakeInfraOutput(csp cloudprovider.Provider) state.Infrastructure { - switch csp { - case cloudprovider.AWS: - return state.Infrastructure{} - case cloudprovider.GCP: - return state.Infrastructure{GCP: &state.GCP{}} - default: - panic("invalid csp") - } -} - func getActionReleaseNames(actions []applyAction) []string { releaseActionNames := []string{} for _, action := range actions { diff --git a/cli/internal/helm/loader_test.go b/cli/internal/helm/loader_test.go index 545a130af8..bd7c6d6785 100644 --- a/cli/internal/helm/loader_test.go +++ b/cli/internal/helm/loader_test.go @@ -65,9 +65,9 @@ func TestLoadReleases(t *testing.T) { assert := assert.New(t) require := require.New(t) config := &config.Config{Provider: config.ProviderConfig{GCP: &config.GCPConfig{}}} - chartLoader := newLoader(config, state.NewState(). + chartLoader := newLoader(config, state.New(). SetInfrastructure(state.Infrastructure{UID: "testuid"}). - SetClusterValues(state.ClusterValues{MeasurementSalt: "measurementSalt"}), + SetClusterValues(state.ClusterValues{MeasurementSalt: []byte{0x41}}), semver.NewFromInt(2, 10, 0, ""), ) helmReleases, err := chartLoader.loadReleases( @@ -87,7 +87,7 @@ func TestLoadAWSLoadBalancerValues(t *testing.T) { sut := chartLoader{ config: &config.Config{Name: "testCluster"}, clusterName: "testCluster", - stateFile: state.NewState().SetInfrastructure(state.Infrastructure{UID: "testuid"}), + stateFile: state.New().SetInfrastructure(state.Infrastructure{UID: "testuid"}), } val := sut.loadAWSLBControllerValues() assert.Equal(t, "testCluster-testuid", val["clusterName"]) diff --git a/cli/internal/state/BUILD.bazel b/cli/internal/state/BUILD.bazel index 1d8c7d634c..83960d3b61 100644 --- a/cli/internal/state/BUILD.bazel +++ b/cli/internal/state/BUILD.bazel @@ -7,6 +7,7 @@ go_library( importpath = "github.com/edgelesssys/constellation/v2/cli/internal/state", visibility = ["//cli:__subpackages__"], deps = [ + "//cli/internal/clusterid", "//internal/config", "//internal/file", "@cat_dario_mergo//:mergo", @@ -18,6 +19,7 @@ go_test( srcs = ["state_test.go"], embed = [":state"], deps = [ + "//cli/internal/clusterid", "//internal/constants", "//internal/file", "@com_github_spf13_afero//:afero", diff --git a/cli/internal/state/state.go b/cli/internal/state/state.go index 111469f2f2..f0fc3aca1f 100644 --- a/cli/internal/state/state.go +++ b/cli/internal/state/state.go @@ -11,6 +11,7 @@ import ( "fmt" "dario.cat/mergo" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/file" ) @@ -36,13 +37,54 @@ type State struct { ClusterValues ClusterValues `yaml:"clusterValues"` } -// NewState creates a new cluster state (file). -func NewState() *State { +// New creates a new cluster state (file). +func New() *State { return &State{ Version: Version1, } } +// NewFromIDFile creates a new cluster state file from the given ID file. +func NewFromIDFile(idFile clusterid.File) *State { + s := New() + + if idFile.ClusterID != "" { + s.ClusterValues.ClusterID = idFile.ClusterID + } + + if idFile.OwnerID != "" { + s.ClusterValues.OwnerID = idFile.OwnerID + } + + if idFile.UID != "" { + s.Infrastructure.UID = idFile.UID + } + + if idFile.IP != "" { + s.Infrastructure.ClusterEndpoint = idFile.IP + } + + if len(idFile.APIServerCertSANs) > 0 { + s.Infrastructure.APIServerCertSANs = idFile.APIServerCertSANs + } + + if idFile.InitSecret != nil { + s.Infrastructure.InitSecret = string(idFile.InitSecret) + } + + if idFile.AttestationURL != "" { + s.Infrastructure.Azure = &Azure{ + AttestationURL: idFile.AttestationURL, + } + } + + if len(idFile.MeasurementSalt) > 0 { + s.ClusterValues.MeasurementSalt = idFile.MeasurementSalt + } + + return s +} + // SetInfrastructure sets the infrastructure state. func (s *State) SetInfrastructure(infrastructure Infrastructure) *State { s.Infrastructure = infrastructure @@ -72,7 +114,7 @@ func (s *State) Merge(other *State) (*State, error) { return s, nil } -// GetClusterName returns the name of the cluster. +// ClusterName returns the name of the cluster. func (s *State) ClusterName(cfg *config.Config) string { return cfg.Name + "-" + s.Infrastructure.UID } @@ -84,7 +126,7 @@ type ClusterValues struct { // OwnerID is the unique identifier of the owner of the cluster. OwnerID string `yaml:"ownerID"` // MeasurementSalt is the salt generated during cluster init. - MeasurementSalt string `yaml:"measurementSalt"` + MeasurementSalt []byte `yaml:"measurementSalt"` } // Infrastructure describe the state related to the cloud resources of the cluster. diff --git a/cli/internal/state/state_test.go b/cli/internal/state/state_test.go index 6b896ea849..1d7dbf6888 100644 --- a/cli/internal/state/state_test.go +++ b/cli/internal/state/state_test.go @@ -9,6 +9,7 @@ package state import ( "testing" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/spf13/afero" @@ -43,7 +44,7 @@ var defaultState = &State{ ClusterValues: ClusterValues{ ClusterID: "test-cluster-id", OwnerID: "test-owner-id", - MeasurementSalt: "test-measurement-salt", + MeasurementSalt: []byte{0x41}, }, } @@ -326,3 +327,59 @@ func TestMerge(t *testing.T) { }) } } + +func TestNewFromIDFile(t *testing.T) { + testCases := map[string]struct { + idFile clusterid.File + expected *State + }{ + "success": { + idFile: clusterid.File{ + ClusterID: "test-cluster-id", + UID: "test-uid", + }, + expected: &State{ + Version: Version1, + Infrastructure: Infrastructure{ + UID: "test-uid", + }, + ClusterValues: ClusterValues{ + ClusterID: "test-cluster-id", + }, + }, + }, + "empty id file": { + idFile: clusterid.File{}, + expected: &State{Version: Version1}, + }, + "nested pointer": { + idFile: clusterid.File{ + ClusterID: "test-cluster-id", + UID: "test-uid", + AttestationURL: "test-maaUrl", + }, + expected: &State{ + Version: Version1, + Infrastructure: Infrastructure{ + UID: "test-uid", + Azure: &Azure{ + AttestationURL: "test-maaUrl", + }, + }, + ClusterValues: ClusterValues{ + ClusterID: "test-cluster-id", + }, + }, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + state := NewFromIDFile(tc.idFile) + + assert.Equal(tc.expected, state) + }) + } +}