From d1ace1371398ae8e339d36ee71cb2c2a721d5985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wei=C3=9Fe?= <66256922+daniel-weisse@users.noreply.github.com> Date: Fri, 4 Aug 2023 13:53:51 +0200 Subject: [PATCH] cli: add `--workspace` flag to set base directory for Constellation workspace (#2148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove `--config` and `--master-secret` falgs * Add `--workspace` flag * In CLI, only work on files with paths created from `cli/internal/cmd` * Properly print values for GCP on IAM create when not directly updating the config --------- Signed-off-by: Daniel Weiße --- cli/cmd/BUILD.bazel | 1 - cli/cmd/root.go | 31 ++- cli/internal/cloudcmd/BUILD.bazel | 1 - cli/internal/cloudcmd/create.go | 18 +- cli/internal/cloudcmd/create_test.go | 2 +- cli/internal/cloudcmd/iam.go | 77 +++--- cli/internal/cloudcmd/iam_test.go | 32 +-- cli/internal/cloudcmd/terminate.go | 11 +- cli/internal/cloudcmd/terminate_test.go | 4 +- cli/internal/cmd/BUILD.bazel | 2 +- cli/internal/cmd/cloud.go | 6 +- cli/internal/cmd/cloud_test.go | 14 +- cli/internal/cmd/configfetchmeasurements.go | 32 +-- .../cmd/configfetchmeasurements_test.go | 20 +- cli/internal/cmd/configgenerate.go | 25 +- cli/internal/cmd/configgenerate_test.go | 39 +-- cli/internal/cmd/configmigrate.go | 11 +- cli/internal/cmd/create.go | 43 +-- cli/internal/cmd/create_test.go | 17 +- cli/internal/cmd/iamcreate.go | 67 ++--- cli/internal/cmd/iamcreate_test.go | 159 +----------- cli/internal/cmd/iamdestroy.go | 53 ++-- cli/internal/cmd/iamdestroy_test.go | 17 +- cli/internal/cmd/iamupgradeapply.go | 14 +- cli/internal/cmd/init.go | 108 ++++---- cli/internal/cmd/init_test.go | 111 +++----- cli/internal/cmd/minidown.go | 2 +- cli/internal/cmd/miniup.go | 73 +++--- cli/internal/cmd/recover.go | 45 ++-- cli/internal/cmd/recover_test.go | 55 ++-- cli/internal/cmd/status.go | 39 ++- cli/internal/cmd/terminate.go | 22 +- cli/internal/cmd/terminate_test.go | 7 +- cli/internal/cmd/tfmigrationclient.go | 17 +- cli/internal/cmd/upgradeapply.go | 66 +++-- cli/internal/cmd/upgradeapply_test.go | 16 +- cli/internal/cmd/upgradecheck.go | 40 +-- cli/internal/cmd/upgradecheck_test.go | 25 +- cli/internal/cmd/verify.go | 46 ++-- cli/internal/cmd/verify_test.go | 45 ++-- cli/internal/cmd/workspace.go | 54 ++++ cli/internal/helm/backup.go | 3 +- cli/internal/helm/init.go | 47 ++-- cli/internal/helm/install.go | 2 +- cli/internal/helm/upgrade.go | 25 +- cli/internal/kubernetes/upgrade.go | 21 +- cli/internal/terraform/loader_test.go | 16 +- cli/internal/terraform/terraform.go | 20 +- cli/internal/terraform/terraform_test.go | 10 +- cli/internal/upgrade/iammigrate.go | 54 ++-- cli/internal/upgrade/iammigrate_test.go | 13 +- cli/internal/upgrade/terraform.go | 102 +++----- cli/internal/upgrade/terraform_test.go | 75 ++---- debugd/internal/cdbg/cmd/deploy.go | 2 +- docs/docs/reference/cli.md | 244 +++++++++--------- e2e/internal/upgrade/upgrade_test.go | 2 +- internal/constants/constants.go | 8 +- 57 files changed, 966 insertions(+), 1145 deletions(-) create mode 100644 cli/internal/cmd/workspace.go diff --git a/cli/cmd/BUILD.bazel b/cli/cmd/BUILD.bazel index 0872ab55f..321eaf83f 100644 --- a/cli/cmd/BUILD.bazel +++ b/cli/cmd/BUILD.bazel @@ -7,7 +7,6 @@ go_library( visibility = ["//visibility:public"], deps = [ "//cli/internal/cmd", - "//internal/constants", "@com_github_spf13_cobra//:cobra", ], ) diff --git a/cli/cmd/root.go b/cli/cmd/root.go index 5dfaee1bf..00478bbe1 100644 --- a/cli/cmd/root.go +++ b/cli/cmd/root.go @@ -18,7 +18,6 @@ import ( "os/signal" "github.com/edgelesssys/constellation/v2/cli/internal/cmd" - "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/spf13/cobra" ) @@ -34,22 +33,22 @@ func Execute() error { // NewRootCmd creates the root command. func NewRootCmd() *cobra.Command { rootCmd := &cobra.Command{ - Use: "constellation", - Short: "Manage your Constellation cluster", - Long: "Manage your Constellation cluster.", - PersistentPreRun: preRunRoot, + Use: "constellation", + Short: "Manage your Constellation cluster", + Long: "Manage your Constellation cluster.", + PersistentPreRunE: preRunRoot, } // Set output of cmd.Print to stdout. (By default, it's stderr.) rootCmd.SetOut(os.Stdout) - rootCmd.PersistentFlags().String("config", constants.ConfigFilename, "path to the configuration file") - must(rootCmd.MarkPersistentFlagFilename("config", "yaml")) - + rootCmd.PersistentFlags().StringP("workspace", "C", "", "path to the Constellation workspace") rootCmd.PersistentFlags().Bool("debug", false, "enable debug logging") rootCmd.PersistentFlags().Bool("force", false, "disable version compatibility checks - might result in corrupted clusters") rootCmd.PersistentFlags().String("tf-log", "NONE", "Terraform log level") + must(rootCmd.MarkPersistentFlagDirname("workspace")) + rootCmd.AddCommand(cmd.NewConfigCmd()) rootCmd.AddCommand(cmd.NewCreateCmd()) rootCmd.AddCommand(cmd.NewInitCmd()) @@ -92,8 +91,22 @@ func signalContext(ctx context.Context, sig os.Signal) (context.Context, context return sigCtx, cancelFunc } -func preRunRoot(cmd *cobra.Command, _ []string) { +func preRunRoot(cmd *cobra.Command, _ []string) error { cmd.SilenceUsage = true + + workspace, err := cmd.Flags().GetString("workspace") + if err != nil { + return fmt.Errorf("getting workspace flag: %w", err) + } + + // Change to workspace directory if set. + if workspace != "" { + if err := os.Chdir(workspace); err != nil { + return fmt.Errorf("changing from current directory to workspace %q: %w", workspace, err) + } + } + + return nil } func must(err error) { diff --git a/cli/internal/cloudcmd/BUILD.bazel b/cli/internal/cloudcmd/BUILD.bazel index bbee7c8bf..5adf64c90 100644 --- a/cli/internal/cloudcmd/BUILD.bazel +++ b/cli/internal/cloudcmd/BUILD.bazel @@ -28,7 +28,6 @@ go_library( "//internal/cloud/cloudprovider", "//internal/cloud/gcpshared", "//internal/config", - "//internal/constants", "//internal/imagefetcher", "//internal/role", "@com_github_azure_azure_sdk_for_go//profiles/latest/attestation/attestation", diff --git a/cli/internal/cloudcmd/create.go b/cli/internal/cloudcmd/create.go index 694f59f2a..db85c44b0 100644 --- a/cli/internal/cloudcmd/create.go +++ b/cli/internal/cloudcmd/create.go @@ -23,7 +23,6 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" - "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/imagefetcher" ) @@ -31,7 +30,7 @@ import ( type Creator struct { out io.Writer image imageFetcher - newTerraformClient func(ctx context.Context) (tfResourceClient, error) + newTerraformClient func(ctx context.Context, workspace string) (tfResourceClient, error) newLibvirtRunner func() libvirtRunner newRawDownloader func() rawDownloader policyPatcher policyPatcher @@ -42,8 +41,8 @@ func NewCreator(out io.Writer) *Creator { return &Creator{ out: out, image: imagefetcher.New(), - newTerraformClient: func(ctx context.Context) (tfResourceClient, error) { - return terraform.New(ctx, constants.TerraformWorkingDir) + newTerraformClient: func(ctx context.Context, workspace string) (tfResourceClient, error) { + return terraform.New(ctx, workspace) }, newLibvirtRunner: func() libvirtRunner { return libvirt.New() @@ -57,10 +56,11 @@ func NewCreator(out io.Writer) *Creator { // CreateOptions are the options for creating a Constellation cluster. type CreateOptions struct { - Provider cloudprovider.Provider - Config *config.Config - image string - TFLogLevel terraform.LogLevel + Provider cloudprovider.Provider + Config *config.Config + TFWorkspace string + image string + TFLogLevel terraform.LogLevel } // Create creates the handed amount of instances and all the needed resources. @@ -74,7 +74,7 @@ func (c *Creator) Create(ctx context.Context, opts CreateOptions) (clusterid.Fil } opts.image = image - cl, err := c.newTerraformClient(ctx) + cl, err := c.newTerraformClient(ctx, opts.TFWorkspace) if err != nil { return clusterid.File{}, err } diff --git a/cli/internal/cloudcmd/create_test.go b/cli/internal/cloudcmd/create_test.go index b8bef3109..327f3b438 100644 --- a/cli/internal/cloudcmd/create_test.go +++ b/cli/internal/cloudcmd/create_test.go @@ -203,7 +203,7 @@ func TestCreator(t *testing.T) { image: &stubImageFetcher{ reference: "some-image", }, - newTerraformClient: func(ctx context.Context) (tfResourceClient, error) { + newTerraformClient: func(_ context.Context, _ string) (tfResourceClient, error) { return tc.tfClient, tc.newTfClientErr }, newLibvirtRunner: func() libvirtRunner { diff --git a/cli/internal/cloudcmd/iam.go b/cli/internal/cloudcmd/iam.go index c5c164ebf..4b6e486d1 100644 --- a/cli/internal/cloudcmd/iam.go +++ b/cli/internal/cloudcmd/iam.go @@ -19,26 +19,26 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared" - "github.com/edgelesssys/constellation/v2/internal/constants" ) // IAMDestroyer destroys an IAM configuration. type IAMDestroyer struct { - client tfIAMClient + newTerraformClient newTFIAMClientFunc } // NewIAMDestroyer creates a new IAM Destroyer. -func NewIAMDestroyer(ctx context.Context) (*IAMDestroyer, error) { - cl, err := terraform.New(ctx, constants.TerraformIAMWorkingDir) - if err != nil { - return nil, err - } - return &IAMDestroyer{client: cl}, nil +func NewIAMDestroyer() *IAMDestroyer { + return &IAMDestroyer{newTerraformClient: newTerraformIAMClient} } -// GetTfstateServiceAccountKey returns the sa_key output from the terraform state. -func (d *IAMDestroyer) GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error) { - tfState, err := d.client.ShowIAM(ctx, cloudprovider.GCP) +// GetTfStateServiceAccountKey returns the sa_key output from the terraform state. +func (d *IAMDestroyer) GetTfStateServiceAccountKey(ctx context.Context, tfWorkspace string) (gcpshared.ServiceAccountKey, error) { + client, err := d.newTerraformClient(ctx, tfWorkspace) + if err != nil { + return gcpshared.ServiceAccountKey{}, err + } + + tfState, err := client.ShowIAM(ctx, cloudprovider.GCP) if err != nil { return gcpshared.ServiceAccountKey{}, fmt.Errorf("getting terraform state: %w", err) } @@ -58,25 +58,31 @@ func (d *IAMDestroyer) GetTfstateServiceAccountKey(ctx context.Context) (gcpshar } // DestroyIAMConfiguration destroys the previously created IAM configuration and deletes the local IAM terraform files. -func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context, logLevel terraform.LogLevel) error { - if err := d.client.Destroy(ctx, logLevel); err != nil { +func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context, tfWorkspace string, logLevel terraform.LogLevel) error { + client, err := d.newTerraformClient(ctx, tfWorkspace) + if err != nil { return err } - return d.client.CleanUpWorkspace() + + if err := client.Destroy(ctx, logLevel); err != nil { + return err + } + return client.CleanUpWorkspace() } // IAMCreator creates the IAM configuration on the cloud provider. type IAMCreator struct { out io.Writer - newTerraformClient func(ctx context.Context) (tfIAMClient, error) + newTerraformClient newTFIAMClientFunc } // IAMConfigOptions holds the necessary values for IAM configuration. type IAMConfigOptions struct { - GCP GCPIAMConfig - Azure AzureIAMConfig - AWS AWSIAMConfig - TFLogLevel terraform.LogLevel + GCP GCPIAMConfig + Azure AzureIAMConfig + AWS AWSIAMConfig + TFLogLevel terraform.LogLevel + TFWorkspace string } // GCPIAMConfig holds the necessary values for GCP IAM configuration. @@ -103,36 +109,25 @@ type AWSIAMConfig struct { // NewIAMCreator creates a new IAM creator. func NewIAMCreator(out io.Writer) *IAMCreator { return &IAMCreator{ - out: out, - newTerraformClient: func(ctx context.Context) (tfIAMClient, error) { - return terraform.New(ctx, constants.TerraformIAMWorkingDir) - }, + out: out, + newTerraformClient: newTerraformIAMClient, } } // Create prepares and hands over the corresponding providers IAM creator. func (c *IAMCreator) Create(ctx context.Context, provider cloudprovider.Provider, opts *IAMConfigOptions) (iamid.File, error) { + cl, err := c.newTerraformClient(ctx, opts.TFWorkspace) + if err != nil { + return iamid.File{}, err + } + defer cl.RemoveInstaller() + switch provider { case cloudprovider.GCP: - cl, err := c.newTerraformClient(ctx) - if err != nil { - return iamid.File{}, err - } - defer cl.RemoveInstaller() return c.createGCP(ctx, cl, opts) case cloudprovider.Azure: - cl, err := c.newTerraformClient(ctx) - if err != nil { - return iamid.File{}, err - } - defer cl.RemoveInstaller() return c.createAzure(ctx, cl, opts) case cloudprovider.AWS: - cl, err := c.newTerraformClient(ctx) - if err != nil { - return iamid.File{}, err - } - defer cl.RemoveInstaller() return c.createAWS(ctx, cl, opts) default: return iamid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider) @@ -222,3 +217,9 @@ func (c *IAMCreator) createAWS(ctx context.Context, cl tfIAMClient, opts *IAMCon }, }, nil } + +type newTFIAMClientFunc func(ctx context.Context, workspace string) (tfIAMClient, error) + +func newTerraformIAMClient(ctx context.Context, workspace string) (tfIAMClient, error) { + return terraform.New(ctx, workspace) +} diff --git a/cli/internal/cloudcmd/iam_test.go b/cli/internal/cloudcmd/iam_test.go index 498837010..e9d6ab964 100644 --- a/cli/internal/cloudcmd/iam_test.go +++ b/cli/internal/cloudcmd/iam_test.go @@ -22,8 +22,6 @@ import ( ) func TestIAMCreator(t *testing.T) { - someErr := errors.New("failed") - validGCPIAMConfig := GCPIAMConfig{ Region: "europe-west1", Zone: "europe-west1-a", @@ -91,30 +89,32 @@ func TestIAMCreator(t *testing.T) { }{ "new terraform client err": { tfClient: &stubTerraformClient{}, - newTfClientErr: someErr, + newTfClientErr: assert.AnError, wantErr: true, + config: &IAMConfigOptions{TFWorkspace: "test"}, }, "create iam config err": { - tfClient: &stubTerraformClient{iamOutputErr: someErr}, + tfClient: &stubTerraformClient{iamOutputErr: assert.AnError}, wantErr: true, + config: &IAMConfigOptions{TFWorkspace: "test"}, }, "gcp": { tfClient: &stubTerraformClient{iamOutput: validGCPIAMOutput}, wantIAMIDFile: validGCPIAMIDFile, provider: cloudprovider.GCP, - config: &IAMConfigOptions{GCP: validGCPIAMConfig}, + config: &IAMConfigOptions{GCP: validGCPIAMConfig, TFWorkspace: "test"}, }, "azure": { tfClient: &stubTerraformClient{iamOutput: validAzureIAMOutput}, wantIAMIDFile: validAzureIAMIDFile, provider: cloudprovider.Azure, - config: &IAMConfigOptions{Azure: validAzureIAMConfig}, + config: &IAMConfigOptions{Azure: validAzureIAMConfig, TFWorkspace: "test"}, }, "aws": { tfClient: &stubTerraformClient{iamOutput: validAWSIAMOutput}, wantIAMIDFile: validAWSIAMIDFile, provider: cloudprovider.AWS, - config: &IAMConfigOptions{AWS: validAWSIAMConfig}, + config: &IAMConfigOptions{AWS: validAWSIAMConfig, TFWorkspace: "test"}, }, } @@ -124,7 +124,7 @@ func TestIAMCreator(t *testing.T) { creator := &IAMCreator{ out: &bytes.Buffer{}, - newTerraformClient: func(ctx context.Context) (tfIAMClient, error) { + newTerraformClient: func(_ context.Context, _ string) (tfIAMClient, error) { return tc.tfClient, tc.newTfClientErr }, } @@ -181,9 +181,11 @@ func TestDestroyIAMConfiguration(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { assert := assert.New(t) - destroyer := &IAMDestroyer{client: tc.tfClient} + destroyer := &IAMDestroyer{newTerraformClient: func(_ context.Context, _ string) (tfIAMClient, error) { + return tc.tfClient, nil + }} - err := destroyer.DestroyIAMConfiguration(context.Background(), terraform.LogLevelNone) + err := destroyer.DestroyIAMConfiguration(context.Background(), "", terraform.LogLevelNone) if tc.wantErr { assert.Error(err) @@ -198,8 +200,6 @@ func TestDestroyIAMConfiguration(t *testing.T) { } func TestGetTfstateServiceAccountKey(t *testing.T) { - someError := errors.New("failed") - gcpFile := ` { "auth_provider_x509_cert_url": "", @@ -235,7 +235,7 @@ func TestGetTfstateServiceAccountKey(t *testing.T) { }, "show error": { cl: &stubTerraformClient{ - showErr: someError, + showErr: assert.AnError, }, wantErr: true, wantShowCalled: true, @@ -275,9 +275,11 @@ func TestGetTfstateServiceAccountKey(t *testing.T) { t.Run(name, func(t *testing.T) { assert := assert.New(t) - destroyer := IAMDestroyer{client: tc.cl} + destroyer := IAMDestroyer{newTerraformClient: func(_ context.Context, _ string) (tfIAMClient, error) { + return tc.cl, nil + }} - saKey, err := destroyer.GetTfstateServiceAccountKey(context.Background()) + saKey, err := destroyer.GetTfStateServiceAccountKey(context.Background(), "") if tc.wantErr { assert.Error(err) diff --git a/cli/internal/cloudcmd/terminate.go b/cli/internal/cloudcmd/terminate.go index 2d2efdd5e..2fb92d1b5 100644 --- a/cli/internal/cloudcmd/terminate.go +++ b/cli/internal/cloudcmd/terminate.go @@ -11,20 +11,19 @@ import ( "github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" - "github.com/edgelesssys/constellation/v2/internal/constants" ) // Terminator deletes cloud provider resources. type Terminator struct { - newTerraformClient func(ctx context.Context) (tfResourceClient, error) + newTerraformClient func(ctx context.Context, tfWorkspace string) (tfResourceClient, error) newLibvirtRunner func() libvirtRunner } // NewTerminator create a new cloud terminator. func NewTerminator() *Terminator { return &Terminator{ - newTerraformClient: func(ctx context.Context) (tfResourceClient, error) { - return terraform.New(ctx, constants.TerraformWorkingDir) + newTerraformClient: func(ctx context.Context, tfWorkspace string) (tfResourceClient, error) { + return terraform.New(ctx, tfWorkspace) }, newLibvirtRunner: func() libvirtRunner { return libvirt.New() @@ -33,14 +32,14 @@ func NewTerminator() *Terminator { } // Terminate deletes the could provider resources. -func (t *Terminator) Terminate(ctx context.Context, logLevel terraform.LogLevel) (retErr error) { +func (t *Terminator) Terminate(ctx context.Context, tfWorkspace string, logLevel terraform.LogLevel) (retErr error) { defer func() { if retErr == nil { retErr = t.newLibvirtRunner().Stop(ctx) } }() - cl, err := t.newTerraformClient(ctx) + cl, err := t.newTerraformClient(ctx, tfWorkspace) if err != nil { return err } diff --git a/cli/internal/cloudcmd/terminate_test.go b/cli/internal/cloudcmd/terminate_test.go index b12795bf6..f624eaef7 100644 --- a/cli/internal/cloudcmd/terminate_test.go +++ b/cli/internal/cloudcmd/terminate_test.go @@ -55,7 +55,7 @@ func TestTerminator(t *testing.T) { assert := assert.New(t) terminator := &Terminator{ - newTerraformClient: func(ctx context.Context) (tfResourceClient, error) { + newTerraformClient: func(_ context.Context, _ string) (tfResourceClient, error) { return tc.tfClient, tc.newTfClientErr }, newLibvirtRunner: func() libvirtRunner { @@ -63,7 +63,7 @@ func TestTerminator(t *testing.T) { }, } - err := terminator.Terminate(context.Background(), terraform.LogLevelNone) + err := terminator.Terminate(context.Background(), "", terraform.LogLevelNone) if tc.wantErr { assert.Error(err) diff --git a/cli/internal/cmd/BUILD.bazel b/cli/internal/cmd/BUILD.bazel index 4980efa12..c7913a82c 100644 --- a/cli/internal/cmd/BUILD.bazel +++ b/cli/internal/cmd/BUILD.bazel @@ -36,6 +36,7 @@ go_library( "validargs.go", "verify.go", "version.go", + "workspace.go", ], importpath = "github.com/edgelesssys/constellation/v2/cli/internal/cmd", visibility = ["//cli:__subpackages__"], @@ -169,7 +170,6 @@ go_test( "@com_github_spf13_cobra//:cobra", "@com_github_stretchr_testify//assert", "@com_github_stretchr_testify//require", - "@in_gopkg_yaml_v3//:yaml_v3", "@io_k8s_api//core/v1:core", "@io_k8s_apimachinery//pkg/apis/meta/v1:meta", "@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured", diff --git a/cli/internal/cmd/cloud.go b/cli/internal/cmd/cloud.go index 6d8ed13f8..f41f37446 100644 --- a/cli/internal/cmd/cloud.go +++ b/cli/internal/cmd/cloud.go @@ -33,10 +33,10 @@ type cloudIAMCreator interface { } type iamDestroyer interface { - DestroyIAMConfiguration(ctx context.Context, logLevel terraform.LogLevel) error - GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error) + DestroyIAMConfiguration(ctx context.Context, tfWorkspace string, logLevel terraform.LogLevel) error + GetTfStateServiceAccountKey(ctx context.Context, tfWorkspace string) (gcpshared.ServiceAccountKey, error) } type cloudTerminator interface { - Terminate(ctx context.Context, logLevel terraform.LogLevel) error + Terminate(ctx context.Context, workspace string, logLevel terraform.LogLevel) error } diff --git a/cli/internal/cmd/cloud_test.go b/cli/internal/cmd/cloud_test.go index 38b127a88..eca706cf7 100644 --- a/cli/internal/cmd/cloud_test.go +++ b/cli/internal/cmd/cloud_test.go @@ -46,7 +46,7 @@ type stubCloudTerminator struct { terminateErr error } -func (c *stubCloudTerminator) Terminate(_ context.Context, _ terraform.LogLevel) error { +func (c *stubCloudTerminator) Terminate(_ context.Context, _ string, _ terraform.LogLevel) error { c.called = true return c.terminateErr } @@ -73,18 +73,18 @@ func (c *stubIAMCreator) Create( type stubIAMDestroyer struct { destroyCalled bool - getTfstateKeyCalled bool + getTfStateKeyCalled bool gcpSaKey gcpshared.ServiceAccountKey destroyErr error - getTfstateKeyErr error + getTfStateKeyErr error } -func (d *stubIAMDestroyer) DestroyIAMConfiguration(_ context.Context, _ terraform.LogLevel) error { +func (d *stubIAMDestroyer) DestroyIAMConfiguration(_ context.Context, _ string, _ terraform.LogLevel) error { d.destroyCalled = true return d.destroyErr } -func (d *stubIAMDestroyer) GetTfstateServiceAccountKey(_ context.Context) (gcpshared.ServiceAccountKey, error) { - d.getTfstateKeyCalled = true - return d.gcpSaKey, d.getTfstateKeyErr +func (d *stubIAMDestroyer) GetTfStateServiceAccountKey(_ context.Context, _ string) (gcpshared.ServiceAccountKey, error) { + d.getTfStateKeyCalled = true + return d.gcpSaKey, d.getTfStateKeyErr } diff --git a/cli/internal/cmd/configfetchmeasurements.go b/cli/internal/cmd/configfetchmeasurements.go index 58499bdfb..bd023ac0d 100644 --- a/cli/internal/cmd/configfetchmeasurements.go +++ b/cli/internal/cmd/configfetchmeasurements.go @@ -12,6 +12,7 @@ import ( "fmt" "net/http" "net/url" + "path/filepath" "time" "github.com/edgelesssys/constellation/v2/cli/internal/featureset" @@ -19,6 +20,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/api/versionsapi" "github.com/edgelesssys/constellation/v2/internal/attestation/measurements" "github.com/edgelesssys/constellation/v2/internal/config" + "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/sigstore" "github.com/edgelesssys/constellation/v2/internal/sigstore/keyselect" @@ -47,8 +49,8 @@ type fetchMeasurementsFlags struct { measurementsURL *url.URL signatureURL *url.URL insecure bool - configPath string force bool + workspace string } type configFetchMeasurementsCmd struct { @@ -88,9 +90,9 @@ func (cfm *configFetchMeasurementsCmd) configFetchMeasurements( return errors.New("fetching measurements is not supported") } - cfm.log.Debugf("Loading configuration file from %q", flags.configPath) + cfm.log.Debugf("Loading configuration file from %q", filepath.Join(flags.workspace, constants.ConfigFilename)) - conf, err := config.New(fileHandler, flags.configPath, fetcher, flags.force) + conf, err := config.New(fileHandler, constants.ConfigFilename, fetcher, flags.force) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { cmd.PrintErrln(configValidationErr.LongMessage()) @@ -168,10 +170,10 @@ func (cfm *configFetchMeasurementsCmd) configFetchMeasurements( cfm.log.Debugf("Updating measurements in configuration") conf.UpdateMeasurements(fetchedMeasurements) - if err := fileHandler.WriteYAML(flags.configPath, conf, file.OptOverwrite); err != nil { + if err := fileHandler.WriteYAML(constants.ConfigFilename, conf, file.OptOverwrite); err != nil { return err } - cfm.log.Debugf("Configuration written to %s", flags.configPath) + cfm.log.Debugf("Configuration written to %s", configPath(flags.workspace)) cmd.Print("Successfully fetched measurements and updated Configuration\n") return nil } @@ -192,41 +194,39 @@ func (cfm *configFetchMeasurementsCmd) parseURLFlag(cmd *cobra.Command, flag str } func (cfm *configFetchMeasurementsCmd) parseFetchMeasurementsFlags(cmd *cobra.Command) (*fetchMeasurementsFlags, error) { + workspace, err := cmd.Flags().GetString("workspace") + if err != nil { + return nil, fmt.Errorf("parsing workspace argument: %w", err) + } measurementsURL, err := cfm.parseURLFlag(cmd, "url") if err != nil { - return &fetchMeasurementsFlags{}, err + return nil, err } cfm.log.Debugf("Parsed measurements URL as %v", measurementsURL) measurementsSignatureURL, err := cfm.parseURLFlag(cmd, "signature-url") if err != nil { - return &fetchMeasurementsFlags{}, err + return nil, err } cfm.log.Debugf("Parsed measurements signature URL as %v", measurementsSignatureURL) insecure, err := cmd.Flags().GetBool("insecure") if err != nil { - return &fetchMeasurementsFlags{}, fmt.Errorf("parsing insecure argument: %w", err) + return nil, fmt.Errorf("parsing insecure argument: %w", err) } cfm.log.Debugf("Insecure flag is %v", insecure) - config, err := cmd.Flags().GetString("config") - if err != nil { - return &fetchMeasurementsFlags{}, fmt.Errorf("parsing config path argument: %w", err) - } - cfm.log.Debugf("Configuration path is %q", config) - force, err := cmd.Flags().GetBool("force") if err != nil { - return &fetchMeasurementsFlags{}, fmt.Errorf("parsing force argument: %w", err) + return nil, fmt.Errorf("parsing force argument: %w", err) } return &fetchMeasurementsFlags{ measurementsURL: measurementsURL, signatureURL: measurementsSignatureURL, insecure: insecure, - configPath: config, force: force, + workspace: workspace, }, nil } diff --git a/cli/internal/cmd/configfetchmeasurements_test.go b/cli/internal/cmd/configfetchmeasurements_test.go index e2fd938ca..f11159a1a 100644 --- a/cli/internal/cmd/configfetchmeasurements_test.go +++ b/cli/internal/cmd/configfetchmeasurements_test.go @@ -39,7 +39,6 @@ func TestParseFetchMeasurementsFlags(t *testing.T) { testCases := map[string]struct { urlFlag string signatureURLFlag string - configFlag string forceFlag bool wantFlags *fetchMeasurementsFlags wantErr bool @@ -48,7 +47,6 @@ func TestParseFetchMeasurementsFlags(t *testing.T) { wantFlags: &fetchMeasurementsFlags{ measurementsURL: nil, signatureURL: nil, - configPath: constants.ConfigFilename, }, }, "url": { @@ -57,19 +55,12 @@ func TestParseFetchMeasurementsFlags(t *testing.T) { wantFlags: &fetchMeasurementsFlags{ measurementsURL: urlMustParse("https://some.other.url/with/path"), signatureURL: urlMustParse("https://some.other.url/with/path.sig"), - configPath: constants.ConfigFilename, }, }, "broken url": { urlFlag: "%notaurl%", wantErr: true, }, - "config": { - configFlag: "someOtherConfig.yaml", - wantFlags: &fetchMeasurementsFlags{ - configPath: "someOtherConfig.yaml", - }, - }, } for name, tc := range testCases { @@ -78,8 +69,8 @@ func TestParseFetchMeasurementsFlags(t *testing.T) { require := require.New(t) cmd := newConfigFetchMeasurementsCmd() - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("force", false, "") // register persistent flag manually + cmd.Flags().String("workspace", "", "") // register persistent flag manually + cmd.Flags().Bool("force", false, "") // register persistent flag manually if tc.urlFlag != "" { require.NoError(cmd.Flags().Set("url", tc.urlFlag)) @@ -87,9 +78,6 @@ func TestParseFetchMeasurementsFlags(t *testing.T) { if tc.signatureURLFlag != "" { require.NoError(cmd.Flags().Set("signature-url", tc.signatureURLFlag)) } - if tc.configFlag != "" { - require.NoError(cmd.Flags().Set("config", tc.configFlag)) - } cfm := &configFetchMeasurementsCmd{log: logger.NewTest(t)} flags, err := cfm.parseFetchMeasurementsFlags(cmd) if tc.wantErr { @@ -283,8 +271,8 @@ func TestConfigFetchMeasurements(t *testing.T) { require := require.New(t) cmd := newConfigFetchMeasurementsCmd() - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("force", true, "") // register persistent flag manually + cmd.Flags().String("workspace", "", "") // register persistent flag manually + cmd.Flags().Bool("force", true, "") // register persistent flag manually require.NoError(cmd.Flags().Set("insecure", strconv.FormatBool(tc.insecureFlag))) fileHandler := file.NewHandler(afero.NewMemMapFs()) diff --git a/cli/internal/cmd/configgenerate.go b/cli/internal/cmd/configgenerate.go index 02fd0fb4d..ef88bcd6d 100644 --- a/cli/internal/cmd/configgenerate.go +++ b/cli/internal/cmd/configgenerate.go @@ -17,7 +17,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/versions" - "github.com/siderolabs/talos/pkg/machinery/config/encoder" "github.com/spf13/afero" "github.com/spf13/cobra" "golang.org/x/mod/semver" @@ -35,7 +34,6 @@ func newConfigGenerateCmd() *cobra.Command { ValidArgsFunction: generateCompletion, RunE: runConfigGenerate, } - cmd.Flags().StringP("file", "f", constants.ConfigFilename, "path to output file, or '-' for stdout") cmd.Flags().StringP("kubernetes", "k", semver.MajorMinor(config.Default().KubernetesVersion), "Kubernetes version to use in format MAJOR.MINOR") cmd.Flags().StringP("attestation", "a", "", fmt.Sprintf("attestation variant to use %s. If not specified, the default for the cloud provider is used", printFormattedSlice(variant.GetAvailableAttestationVariants()))) @@ -43,7 +41,7 @@ func newConfigGenerateCmd() *cobra.Command { } type generateFlags struct { - file string + workspace string k8sVersion string attestationVariant variant.Variant } @@ -77,23 +75,12 @@ func (cg *configGenerateCmd) configGenerate(cmd *cobra.Command, fileHandler file return fmt.Errorf("creating config: %w", err) } conf.KubernetesVersion = flags.k8sVersion - if flags.file == "-" { - content, err := encoder.NewEncoder(conf).Encode() - if err != nil { - return fmt.Errorf("encoding config content: %w", err) - } - - cg.log.Debugf("Writing YAML data to stdout") - _, err = cmd.OutOrStdout().Write(content) - return err - } - cg.log.Debugf("Writing YAML data to configuration file") - if err := fileHandler.WriteYAML(flags.file, conf, file.OptMkdirAll); err != nil { + if err := fileHandler.WriteYAML(constants.ConfigFilename, conf, file.OptMkdirAll); err != nil { return err } - cmd.Println("Config file written to", flags.file) + cmd.Println("Config file written to", configPath(flags.workspace)) cmd.Println("Please fill in your CSP-specific configuration before proceeding.") cmd.Println("For more information refer to the documentation:") cmd.Println("\thttps://docs.edgeless.systems/constellation/getting-started/first-steps") @@ -150,9 +137,9 @@ func supportedVersions() string { } func parseGenerateFlags(cmd *cobra.Command) (generateFlags, error) { - file, err := cmd.Flags().GetString("file") + workspace, err := cmd.Flags().GetString("workspace") if err != nil { - return generateFlags{}, fmt.Errorf("parsing file flag: %w", err) + return generateFlags{}, fmt.Errorf("parsing workspace flag: %w", err) } k8sVersion, err := cmd.Flags().GetString("kubernetes") if err != nil { @@ -179,7 +166,7 @@ func parseGenerateFlags(cmd *cobra.Command) (generateFlags, error) { } } return generateFlags{ - file: file, + workspace: workspace, k8sVersion: resolvedVersion, attestationVariant: attestationVariant, }, nil diff --git a/cli/internal/cmd/configgenerate_test.go b/cli/internal/cmd/configgenerate_test.go index 8034b00a0..d11cd5d80 100644 --- a/cli/internal/cmd/configgenerate_test.go +++ b/cli/internal/cmd/configgenerate_test.go @@ -7,7 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-only package cmd import ( - "bytes" "fmt" "testing" @@ -23,7 +22,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/mod/semver" - "gopkg.in/yaml.v3" ) func TestConfigGenerateKubernetesVersion(t *testing.T) { @@ -51,6 +49,7 @@ func TestConfigGenerateKubernetesVersion(t *testing.T) { fileHandler := file.NewHandler(afero.NewMemMapFs()) cmd := newConfigGenerateCmd() + cmd.Flags().String("workspace", "", "") // register persistent flag manually err := cmd.Flags().Set("kubernetes", tc.version) require.NoError(err) @@ -72,6 +71,7 @@ func TestConfigGenerateDefault(t *testing.T) { fileHandler := file.NewHandler(afero.NewMemMapFs()) cmd := newConfigGenerateCmd() + cmd.Flags().String("workspace", "", "") // register persistent flag manually cg := &configGenerateCmd{log: logger.NewTest(t)} require.NoError(cg.configGenerate(cmd, fileHandler, cloudprovider.Unknown, "")) @@ -97,6 +97,7 @@ func TestConfigGenerateDefaultProviderSpecific(t *testing.T) { fileHandler := file.NewHandler(afero.NewMemMapFs()) cmd := newConfigGenerateCmd() + cmd.Flags().String("workspace", "", "") // register persistent flag manually wantConf := config.Default() wantConf.RemoveProviderAndAttestationExcept(provider) @@ -122,6 +123,7 @@ func TestConfigGenerateWithStackIt(t *testing.T) { fileHandler := file.NewHandler(afero.NewMemMapFs()) cmd := newConfigGenerateCmd() + cmd.Flags().String("workspace", "", "") // register persistent flag manually wantConf := config.Default().WithOpenStackProviderDefaults(openStackProvider) wantConf.RemoveProviderAndAttestationExcept(cloudprovider.OpenStack) @@ -143,42 +145,12 @@ func TestConfigGenerateDefaultExists(t *testing.T) { fileHandler := file.NewHandler(afero.NewMemMapFs()) require.NoError(fileHandler.Write(constants.ConfigFilename, []byte("foobar"), file.OptNone)) cmd := newConfigGenerateCmd() + cmd.Flags().String("workspace", "", "") // register persistent flag manually cg := &configGenerateCmd{log: logger.NewTest(t)} require.Error(cg.configGenerate(cmd, fileHandler, cloudprovider.Unknown, "")) } -func TestConfigGenerateFileFlagRemoved(t *testing.T) { - require := require.New(t) - - fileHandler := file.NewHandler(afero.NewMemMapFs()) - cmd := newConfigGenerateCmd() - cmd.ResetFlags() - - cg := &configGenerateCmd{log: logger.NewTest(t)} - require.Error(cg.configGenerate(cmd, fileHandler, cloudprovider.Unknown, "")) -} - -func TestConfigGenerateStdOut(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - fileHandler := file.NewHandler(afero.NewMemMapFs()) - - var outBuffer bytes.Buffer - cmd := newConfigGenerateCmd() - cmd.SetOut(&outBuffer) - require.NoError(cmd.Flags().Set("file", "-")) - - cg := &configGenerateCmd{log: logger.NewTest(t)} - require.NoError(cg.configGenerate(cmd, fileHandler, cloudprovider.Unknown, "")) - - var readConfig config.Config - require.NoError(yaml.NewDecoder(&outBuffer).Decode(&readConfig)) - - assert.Equal(*config.Default(), readConfig) -} - func TestNoValidProviderAttestationCombination(t *testing.T) { assert := assert.New(t) tests := []struct { @@ -294,6 +266,7 @@ func TestAttestationArgument(t *testing.T) { assert := assert.New(t) cmd := newConfigGenerateCmd() + cmd.Flags().String("workspace", "", "") // register persistent flag manually require.NoError(test.setFlag(cmd)) fileHandler := file.NewHandler(afero.NewMemMapFs()) diff --git a/cli/internal/cmd/configmigrate.go b/cli/internal/cmd/configmigrate.go index be627239e..36f97fa19 100644 --- a/cli/internal/cmd/configmigrate.go +++ b/cli/internal/cmd/configmigrate.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/edgelesssys/constellation/v2/internal/config" + "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -28,19 +29,15 @@ func newConfigMigrateCmd() *cobra.Command { func runConfigMigrate(cmd *cobra.Command, _ []string) error { handler := file.NewHandler(afero.NewOsFs()) - configPath, err := cmd.Flags().GetString("config") - if err != nil { - return fmt.Errorf("parsing config path flag: %w", err) - } - return configMigrate(cmd, configPath, handler) + return configMigrate(cmd, handler) } -func configMigrate(cmd *cobra.Command, configPath string, handler file.Handler) error { +func configMigrate(cmd *cobra.Command, handler file.Handler) error { // Make sure we are reading a v2 config var cfgVersion struct { Version string `yaml:"version"` } - if err := handler.ReadYAML(configPath, &cfgVersion); err != nil { + if err := handler.ReadYAML(constants.ConfigFilename, &cfgVersion); err != nil { return err } diff --git a/cli/internal/cmd/create.go b/cli/internal/cmd/create.go index bccbc998b..e7a7f970f 100644 --- a/cli/internal/cmd/create.go +++ b/cli/internal/cmd/create.go @@ -71,12 +71,12 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler return err } c.log.Debugf("Using flags: %+v", flags) - if err := c.checkDirClean(fileHandler); err != nil { + if err := c.checkDirClean(flags.workspace, fileHandler); err != nil { return err } - c.log.Debugf("Loading configuration file from %q", flags.configPath) - conf, err := config.New(fileHandler, flags.configPath, fetcher, flags.force) + c.log.Debugf("Loading configuration file from %q", configPath(flags.workspace)) + conf, err := config.New(fileHandler, constants.ConfigFilename, fetcher, flags.force) c.log.Debugf("Configuration file loaded: %+v", conf) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { @@ -160,18 +160,19 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler spinner.Start("Creating", false) opts := cloudcmd.CreateOptions{ - Provider: provider, - Config: conf, - TFLogLevel: flags.tfLogLevel, + Provider: provider, + Config: conf, + TFLogLevel: flags.tfLogLevel, + TFWorkspace: constants.TerraformWorkingDir, } idFile, err := creator.Create(cmd.Context(), opts) spinner.Stop() if err != nil { - return translateCreateErrors(cmd, err) + return translateCreateErrors(cmd, flags.workspace, err) } c.log.Debugf("Successfully created the cloud resources for the cluster") - if err := fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone); err != nil { + if err := fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptNone); err != nil { return err } @@ -187,11 +188,11 @@ func (c *createCmd) parseCreateFlags(cmd *cobra.Command) (createFlags, error) { } c.log.Debugf("Yes flag is %t", yes) - configPath, err := cmd.Flags().GetString("config") + workspace, err := cmd.Flags().GetString("workspace") if err != nil { return createFlags{}, fmt.Errorf("parsing config path argument: %w", err) } - c.log.Debugf("Configuration path flag is %q", configPath) + c.log.Debugf("Workspace set to %q", workspace) force, err := cmd.Flags().GetBool("force") if err != nil { @@ -207,10 +208,10 @@ func (c *createCmd) parseCreateFlags(cmd *cobra.Command) (createFlags, error) { if err != nil { return createFlags{}, fmt.Errorf("parsing Terraform log level %s: %w", logLevelString, err) } - c.log.Debugf("Terraform logs will be written into %s at level %s", constants.TerraformLogFile, logLevel.String()) + c.log.Debugf("Terraform logs will be written into %s at level %s", terraformLogPath(workspace), logLevel.String()) return createFlags{ - configPath: configPath, + workspace: workspace, tfLogLevel: logLevel, force: force, yes: yes, @@ -219,44 +220,44 @@ func (c *createCmd) parseCreateFlags(cmd *cobra.Command) (createFlags, error) { // createFlags contains the parsed flags of the create command. type createFlags struct { - configPath string + workspace string tfLogLevel terraform.LogLevel force bool yes bool } // checkDirClean checks if files of a previous Constellation are left in the current working dir. -func (c *createCmd) checkDirClean(fileHandler file.Handler) error { +func (c *createCmd) checkDirClean(workspace string, fileHandler file.Handler) error { c.log.Debugf("Checking admin configuration file") if _, err := fileHandler.Stat(constants.AdminConfFilename); !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("file '%s' already exists in working directory, run 'constellation terminate' before creating a new one", constants.AdminConfFilename) + return fmt.Errorf("file '%s' already exists in working directory, run 'constellation terminate' before creating a new one", adminConfPath(workspace)) } c.log.Debugf("Checking master secrets file") if _, err := fileHandler.Stat(constants.MasterSecretFilename); !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("file '%s' already exists in working directory. Constellation won't overwrite previous master secrets. Move it somewhere or delete it before creating a new cluster", constants.MasterSecretFilename) + return fmt.Errorf("file '%s' already exists in working directory. Constellation won't overwrite previous master secrets. Move it somewhere or delete it before creating a new cluster", masterSecretPath(workspace)) } c.log.Debugf("Checking cluster IDs file") - if _, err := fileHandler.Stat(constants.ClusterIDsFileName); !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("file '%s' already exists in working directory. Constellation won't overwrite previous cluster IDs. Move it somewhere or delete it before creating a new cluster", constants.ClusterIDsFileName) + if _, err := fileHandler.Stat(constants.ClusterIDsFilename); !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("file '%s' already exists in working directory. Constellation won't overwrite previous cluster IDs. Move it somewhere or delete it before creating a new cluster", clusterIDsPath(workspace)) } return nil } -func translateCreateErrors(cmd *cobra.Command, err error) error { +func translateCreateErrors(cmd *cobra.Command, workspace string, err error) error { switch { case errors.Is(err, terraform.ErrTerraformWorkspaceDifferentFiles): cmd.PrintErrln("\nYour current working directory contains an existing Terraform workspace which does not match the expected state.") cmd.PrintErrln("This can be due to a mix up between providers, versions or an otherwise corrupted workspace.") cmd.PrintErrln("Before creating a new cluster, try \"constellation terminate\".") - cmd.PrintErrf("If this does not work, either move or delete the directory %q.\n", constants.TerraformWorkingDir) + cmd.PrintErrf("If this does not work, either move or delete the directory %q.\n", terraformClusterWorkspace(workspace)) cmd.PrintErrln("Please only delete the directory if you made sure that all created cloud resources have been terminated.") return err case errors.Is(err, terraform.ErrTerraformWorkspaceExistsWithDifferentVariables): cmd.PrintErrln("\nYour current working directory contains an existing Terraform workspace which was initiated with different input variables.") cmd.PrintErrln("This can be the case if you have tried to create a cluster before with different options which did not complete, or the workspace is corrupted.") cmd.PrintErrln("Before creating a new cluster, try \"constellation terminate\".") - cmd.PrintErrf("If this does not work, either move or delete the directory %q.\n", constants.TerraformWorkingDir) + cmd.PrintErrf("If this does not work, either move or delete the directory %q.\n", terraformClusterWorkspace(workspace)) cmd.PrintErrln("Please only delete the directory if you made sure that all created cloud resources have been terminated.") return err default: diff --git a/cli/internal/cmd/create_test.go b/cli/internal/cmd/create_test.go index bbdaae424..e4dacafe6 100644 --- a/cli/internal/cmd/create_test.go +++ b/cli/internal/cmd/create_test.go @@ -41,7 +41,6 @@ func TestCreate(t *testing.T) { yesFlag bool controllerCountFlag *int workerCountFlag *int - configFlag string stdin string wantErr bool wantAbort bool @@ -141,13 +140,12 @@ func TestCreate(t *testing.T) { wantErr: true, }, "config does not exist": { - setupFs: fsWithDefaultConfig, + setupFs: func(a *require.Assertions, p cloudprovider.Provider) afero.Fs { return afero.NewMemMapFs() }, creator: &stubCloudCreator{}, provider: cloudprovider.GCP, controllerCountFlag: intPtr(1), workerCountFlag: intPtr(1), yesFlag: true, - configFlag: "/does/not/exist", wantErr: true, }, "create error": { @@ -184,16 +182,13 @@ func TestCreate(t *testing.T) { cmd.SetOut(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{}) cmd.SetIn(bytes.NewBufferString(tc.stdin)) - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("force", true, "") // register persistent flag manually - cmd.Flags().String("tf-log", "NONE", "") // register persistent flag manually + cmd.Flags().String("workspace", "", "") // register persistent flag manually + cmd.Flags().Bool("force", true, "") // register persistent flag manually + cmd.Flags().String("tf-log", "NONE", "") // register persistent flag manually if tc.yesFlag { require.NoError(cmd.Flags().Set("yes", "true")) } - if tc.configFlag != "" { - require.NoError(cmd.Flags().Set("config", tc.configFlag)) - } if tc.controllerCountFlag != nil { require.NoError(cmd.Flags().Set("control-plane-nodes", strconv.Itoa(*tc.controllerCountFlag))) } @@ -214,7 +209,7 @@ func TestCreate(t *testing.T) { } else { assert.True(tc.creator.createCalled) var gotIDFile clusterid.File - require.NoError(fileHandler.ReadJSON(constants.ClusterIDsFileName, &gotIDFile)) + require.NoError(fileHandler.ReadJSON(constants.ClusterIDsFilename, &gotIDFile)) assert.Equal(gotIDFile, clusterid.File{ IP: idFile.IP, CloudProvider: tc.provider, @@ -260,7 +255,7 @@ func TestCheckDirClean(t *testing.T) { require.NoError(tc.fileHandler.Write(f, []byte{1, 2, 3}, file.OptNone)) } c := &createCmd{log: logger.NewTest(t)} - err := c.checkDirClean(tc.fileHandler) + err := c.checkDirClean("", tc.fileHandler) if tc.wantErr { assert.Error(err) diff --git a/cli/internal/cmd/iamcreate.go b/cli/internal/cmd/iamcreate.go index d651e4a86..ff75de87f 100644 --- a/cli/internal/cmd/iamcreate.go +++ b/cli/internal/cmd/iamcreate.go @@ -130,14 +130,16 @@ func newIAMCreateGCPCmd() *cobra.Command { // createRunIAMFunc is the entrypoint for the iam create command. It sets up the iamCreator // and starts IAM creation for the specific cloud provider. func createRunIAMFunc(provider cloudprovider.Provider) func(cmd *cobra.Command, args []string) error { - var providerCreator providerIAMCreator + var providerCreator func(workspace string) providerIAMCreator switch provider { case cloudprovider.AWS: - providerCreator = &awsIAMCreator{} + providerCreator = func(string) providerIAMCreator { return &awsIAMCreator{} } case cloudprovider.Azure: - providerCreator = &azureIAMCreator{} + providerCreator = func(string) providerIAMCreator { return &azureIAMCreator{} } case cloudprovider.GCP: - providerCreator = &gcpIAMCreator{} + providerCreator = func(workspace string) providerIAMCreator { + return &gcpIAMCreator{workspace} + } default: return func(cmd *cobra.Command, args []string) error { return fmt.Errorf("unknown provider %s", provider) @@ -153,21 +155,25 @@ func createRunIAMFunc(provider cloudprovider.Provider) func(cmd *cobra.Command, if err != nil { return fmt.Errorf("parsing Terraform log level %s: %w", logLevelString, err) } + workspace, err := cmd.Flags().GetString("workspace") + if err != nil { + return fmt.Errorf("parsing workspace string: %w", err) + } - iamCreator, err := newIAMCreator(cmd, logLevel) + iamCreator, err := newIAMCreator(cmd, workspace, logLevel) if err != nil { return fmt.Errorf("creating iamCreator: %w", err) } defer iamCreator.spinner.Stop() defer iamCreator.log.Sync() iamCreator.provider = provider - iamCreator.providerCreator = providerCreator + iamCreator.providerCreator = providerCreator(workspace) return iamCreator.create(cmd.Context()) } } // newIAMCreator creates a new iamiamCreator. -func newIAMCreator(cmd *cobra.Command, logLevel terraform.LogLevel) (*iamCreator, error) { +func newIAMCreator(cmd *cobra.Command, workspace string, logLevel terraform.LogLevel) (*iamCreator, error) { spinner, err := newSpinnerOrStderr(cmd) if err != nil { return nil, fmt.Errorf("creating spinner: %w", err) @@ -176,7 +182,7 @@ func newIAMCreator(cmd *cobra.Command, logLevel terraform.LogLevel) (*iamCreator if err != nil { return nil, fmt.Errorf("creating logger: %w", err) } - log.Debugf("Terraform logs will be written into %s at level %s", constants.TerraformLogFile, logLevel.String()) + log.Debugf("Terraform logs will be written into %s at level %s", terraformLogPath(workspace), logLevel.String()) return &iamCreator{ cmd: cmd, @@ -185,7 +191,8 @@ func newIAMCreator(cmd *cobra.Command, logLevel terraform.LogLevel) (*iamCreator creator: cloudcmd.NewIAMCreator(spinner), fileHandler: file.NewHandler(afero.NewOsFs()), iamConfig: &cloudcmd.IAMConfigOptions{ - TFLogLevel: logLevel, + TFWorkspace: constants.TerraformIAMWorkingDir, + TFLogLevel: logLevel, }, }, nil } @@ -210,7 +217,7 @@ func (c *iamCreator) create(ctx context.Context) error { } c.log.Debugf("Using flags: %+v", flags) - if err := c.checkWorkingDir(); err != nil { + if err := c.checkWorkingDir(flags.workspace); err != nil { return err } @@ -229,14 +236,14 @@ func (c *iamCreator) create(ctx context.Context) error { var conf config.Config if flags.updateConfig { - c.log.Debugf("Parsing config %s", flags.configPath) - if err = c.fileHandler.ReadYAML(flags.configPath, &conf); err != nil { + c.log.Debugf("Parsing config %s", configPath(flags.workspace)) + if err = c.fileHandler.ReadYAML(constants.ConfigFilename, &conf); err != nil { return fmt.Errorf("error reading the configuration file: %w", err) } if err := validateConfigWithFlagCompatibility(c.provider, conf, flags); err != nil { return err } - c.cmd.Printf("The configuration file %q will be automatically updated with the IAM values and zone/region information.\n", flags.configPath) + c.cmd.Printf("The configuration file %q will be automatically updated with the IAM values and zone/region information.\n", configPath(flags.workspace)) } c.spinner.Start("Creating", false) @@ -254,12 +261,12 @@ func (c *iamCreator) create(ctx context.Context) error { } if flags.updateConfig { - c.log.Debugf("Writing IAM configuration to %s", flags.configPath) + c.log.Debugf("Writing IAM configuration to %s", configPath(flags.workspace)) c.providerCreator.writeOutputValuesToConfig(&conf, flags, iamFile) - if err := c.fileHandler.WriteYAML(flags.configPath, conf, file.OptOverwrite); err != nil { + if err := c.fileHandler.WriteYAML(constants.ConfigFilename, conf, file.OptOverwrite); err != nil { return err } - c.cmd.Printf("Your IAM configuration was created and filled into %s successfully.\n", flags.configPath) + c.cmd.Printf("Your IAM configuration was created and filled into %s successfully.\n", configPath(flags.workspace)) return nil } @@ -271,7 +278,7 @@ func (c *iamCreator) create(ctx context.Context) error { // parseFlagsAndSetupConfig parses the flags of the iam create command and fills the values into the IAM config (output values of the command). func (c *iamCreator) parseFlagsAndSetupConfig() (iamFlags, error) { - configPath, err := c.cmd.Flags().GetString("config") + cwd, err := c.cmd.Flags().GetString("workspace") if err != nil { return iamFlags{}, fmt.Errorf("parsing config string: %w", err) } @@ -285,7 +292,7 @@ func (c *iamCreator) parseFlagsAndSetupConfig() (iamFlags, error) { } flags := iamFlags{ - configPath: configPath, + workspace: cwd, yesFlag: yesFlag, updateConfig: updateConfig, } @@ -299,9 +306,9 @@ func (c *iamCreator) parseFlagsAndSetupConfig() (iamFlags, error) { } // checkWorkingDir checks if the current working directory already contains a Terraform dir. -func (c *iamCreator) checkWorkingDir() error { +func (c *iamCreator) checkWorkingDir(workspace string) error { if _, err := c.fileHandler.Stat(constants.TerraformIAMWorkingDir); err == nil { - return fmt.Errorf("the current working directory already contains the Terraform workspace directory %q. Please run the command in a different directory or destroy the existing workspace", constants.TerraformIAMWorkingDir) + return fmt.Errorf("the current working directory already contains the Terraform workspace directory %q. Please run the command in a different directory or destroy the existing workspace", terraformIAMWorkspace(workspace)) } return nil } @@ -311,7 +318,7 @@ type iamFlags struct { aws awsFlags azure azureFlags gcp gcpFlags - configPath string + workspace string yesFlag bool updateConfig bool } @@ -481,7 +488,9 @@ func (c *azureIAMCreator) parseAndWriteIDFile(_ iamid.File, _ file.Handler) erro } // gcpIAMCreator implements the providerIAMCreator interface for GCP. -type gcpIAMCreator struct{} +type gcpIAMCreator struct { + workspace string +} func (c *gcpIAMCreator) parseFlagsAndSetupConfig(cmd *cobra.Command, flags iamFlags, iamConfig *cloudcmd.IAMConfigOptions) (iamFlags, error) { zone, err := cmd.Flags().GetString("zone") @@ -540,16 +549,16 @@ func (c *gcpIAMCreator) printConfirmValues(cmd *cobra.Command, flags iamFlags) { cmd.Printf("Zone:\t\t\t%s\n\n", flags.gcp.zone) } -func (c *gcpIAMCreator) printOutputValues(cmd *cobra.Command, _ iamFlags, _ iamid.File) { - cmd.Printf("projectID:\t\t%s\n", constants.GCPServiceAccountKeyFile) - cmd.Printf("region:\t\t\t%s\n", constants.GCPServiceAccountKeyFile) - cmd.Printf("zone:\t\t\t%s\n", constants.GCPServiceAccountKeyFile) - cmd.Printf("serviceAccountKeyPath:\t%s\n\n", constants.GCPServiceAccountKeyFile) +func (c *gcpIAMCreator) printOutputValues(cmd *cobra.Command, flags iamFlags, _ iamid.File) { + cmd.Printf("projectID:\t\t%s\n", flags.gcp.projectID) + cmd.Printf("region:\t\t\t%s\n", flags.gcp.region) + cmd.Printf("zone:\t\t\t%s\n", flags.gcp.zone) + cmd.Printf("serviceAccountKeyPath:\t%s\n\n", gcpServiceAccountKeyPath(c.workspace)) } func (c *gcpIAMCreator) writeOutputValuesToConfig(conf *config.Config, flags iamFlags, _ iamid.File) { conf.Provider.GCP.Project = flags.gcp.projectID - conf.Provider.GCP.ServiceAccountKeyPath = constants.GCPServiceAccountKeyFile + conf.Provider.GCP.ServiceAccountKeyPath = gcpServiceAccountKeyFile // File was created in workspace, so only the filename is needed. conf.Provider.GCP.Region = flags.gcp.region conf.Provider.GCP.Zone = flags.gcp.zone for groupName, group := range conf.NodeGroups { @@ -565,7 +574,7 @@ func (c *gcpIAMCreator) parseAndWriteIDFile(iamFile iamid.File, fileHandler file return err } - return fileHandler.WriteJSON(constants.GCPServiceAccountKeyFile, tmpOut, file.OptNone) + return fileHandler.WriteJSON(gcpServiceAccountKeyFile, tmpOut, file.OptNone) } // parseIDFile parses the given base64 encoded JSON string of the GCP service account key and returns a map. diff --git a/cli/internal/cmd/iamcreate_test.go b/cli/internal/cmd/iamcreate_test.go index 8231b384d..ab2920ba2 100644 --- a/cli/internal/cmd/iamcreate_test.go +++ b/cli/internal/cmd/iamcreate_test.go @@ -88,7 +88,6 @@ func TestIAMCreateAWS(t *testing.T) { prefixFlag string yesFlag bool updateConfigFlag bool - configFlag string existingConfigFiles []string existingDirs []string stdin string @@ -121,7 +120,6 @@ func TestIAMCreateAWS(t *testing.T) { zoneFlag: "us-east-2a", prefixFlag: "test", yesFlag: true, - configFlag: constants.ConfigFilename, updateConfigFlag: true, existingConfigFiles: []string{constants.ConfigFilename}, }, @@ -160,28 +158,6 @@ func TestIAMCreateAWS(t *testing.T) { prefixFlag: "test", yesFlag: true, }, - "iam create aws --update-config with --config": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - provider: cloudprovider.AWS, - zoneFlag: "us-east-2a", - prefixFlag: "test", - yesFlag: true, - updateConfigFlag: true, - configFlag: "custom-config.yaml", - existingConfigFiles: []string{"custom-config.yaml"}, - }, - "iam create aws --update-config --config path doesn't exist": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - provider: cloudprovider.AWS, - zoneFlag: "us-east-2a", - prefixFlag: "test", - yesFlag: true, - updateConfigFlag: true, - wantErr: true, - configFlag: constants.ConfigFilename, - }, "iam create aws existing terraform dir": { setupFs: defaultFs, creator: &stubIAMCreator{id: validIAMIDFile}, @@ -207,7 +183,6 @@ func TestIAMCreateAWS(t *testing.T) { zoneFlag: "us-east-2a", prefixFlag: "test", stdin: "yes\n", - configFlag: constants.ConfigFilename, updateConfigFlag: true, existingConfigFiles: []string{constants.ConfigFilename}, }, @@ -228,7 +203,6 @@ func TestIAMCreateAWS(t *testing.T) { prefixFlag: "test", stdin: "no\n", updateConfigFlag: true, - configFlag: constants.ConfigFilename, wantAbort: true, existingConfigFiles: []string{constants.ConfigFilename}, }, @@ -250,7 +224,6 @@ func TestIAMCreateAWS(t *testing.T) { yesFlag: true, updateConfigFlag: true, wantErr: true, - configFlag: constants.ConfigFilename, }, } @@ -265,7 +238,7 @@ func TestIAMCreateAWS(t *testing.T) { cmd.SetIn(bytes.NewBufferString(tc.stdin)) // register persistent flags manually - cmd.Flags().String("config", constants.ConfigFilename, "") + cmd.Flags().String("workspace", "", "") cmd.Flags().Bool("update-config", false, "") cmd.Flags().Bool("yes", false, "") cmd.Flags().String("name", "constell", "") @@ -283,9 +256,6 @@ func TestIAMCreateAWS(t *testing.T) { if tc.updateConfigFlag { require.NoError(cmd.Flags().Set("update-config", "true")) } - if tc.configFlag != "" { - require.NoError(cmd.Flags().Set("config", tc.configFlag)) - } fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingConfigFiles, tc.existingDirs)) @@ -314,7 +284,7 @@ func TestIAMCreateAWS(t *testing.T) { if tc.updateConfigFlag { readConfig := &config.Config{} - readErr := fileHandler.ReadYAML(tc.configFlag, readConfig) + readErr := fileHandler.ReadYAML(constants.ConfigFilename, readConfig) require.NoError(readErr) assert.Equal(tc.creator.id.AWSOutput.ControlPlaneInstanceProfile, readConfig.Provider.AWS.IAMProfileControlPlane) assert.Equal(tc.creator.id.AWSOutput.WorkerNodeInstanceProfile, readConfig.Provider.AWS.IAMProfileWorkerNodes) @@ -329,17 +299,7 @@ func TestIAMCreateAWS(t *testing.T) { } func TestIAMCreateAzure(t *testing.T) { - defaultFs := func(require *require.Assertions, provider cloudprovider.Provider, existingConfigFiles []string, existingDirs []string) afero.Fs { - fs := afero.NewMemMapFs() - fileHandler := file.NewHandler(fs) - for _, f := range existingConfigFiles { - require.NoError(fileHandler.WriteYAML(f, createConfig(cloudprovider.Azure), file.OptNone)) - } - for _, d := range existingDirs { - require.NoError(fs.MkdirAll(d, 0o755)) - } - return fs - } + defaultFs := createFSWithConfig(*createConfig(cloudprovider.Azure)) readOnlyFs := func(require *require.Assertions, provider cloudprovider.Provider, existingConfigFiles []string, existingDirs []string) afero.Fs { fs := afero.NewReadOnlyFs(afero.NewMemMapFs()) return fs @@ -362,7 +322,6 @@ func TestIAMCreateAzure(t *testing.T) { resourceGroupFlag string yesFlag bool updateConfigFlag bool - configFlag string existingConfigFiles []string existingDirs []string stdin string @@ -396,46 +355,9 @@ func TestIAMCreateAzure(t *testing.T) { servicePrincipalFlag: "constell-test-sp", resourceGroupFlag: "constell-test-rg", updateConfigFlag: true, - configFlag: constants.ConfigFilename, yesFlag: true, existingConfigFiles: []string{constants.ConfigFilename}, }, - "iam create azure --update-config with --config": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - provider: cloudprovider.Azure, - regionFlag: "westus", - servicePrincipalFlag: "constell-test-sp", - resourceGroupFlag: "constell-test-rg", - updateConfigFlag: true, - configFlag: "custom-config.yaml", - yesFlag: true, - existingConfigFiles: []string{"custom-config.yaml"}, - }, - "iam create azure --update-config custom --config path doesn't exist": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - provider: cloudprovider.Azure, - regionFlag: "westus", - servicePrincipalFlag: "constell-test-sp", - resourceGroupFlag: "constell-test-rg", - updateConfigFlag: true, - yesFlag: true, - wantErr: true, - configFlag: "custom-config.yaml", - }, - "iam create azur --update-config --config path doesn't exists": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - provider: cloudprovider.Azure, - regionFlag: "westus", - servicePrincipalFlag: "constell-test-sp", - resourceGroupFlag: "constell-test-rg", - updateConfigFlag: true, - configFlag: constants.ConfigFilename, - yesFlag: true, - wantErr: true, - }, "iam create azure existing terraform dir": { setupFs: defaultFs, creator: &stubIAMCreator{id: validIAMIDFile}, @@ -465,7 +387,6 @@ func TestIAMCreateAzure(t *testing.T) { resourceGroupFlag: "constell-test-rg", stdin: "yes\n", updateConfigFlag: true, - configFlag: constants.ConfigFilename, existingConfigFiles: []string{constants.ConfigFilename}, }, "interactive abort": { @@ -499,7 +420,6 @@ func TestIAMCreateAzure(t *testing.T) { resourceGroupFlag: "constell-test-rg", yesFlag: true, updateConfigFlag: true, - configFlag: constants.ConfigFilename, wantErr: true, }, } @@ -515,7 +435,7 @@ func TestIAMCreateAzure(t *testing.T) { cmd.SetIn(bytes.NewBufferString(tc.stdin)) // register persistent flags manually - cmd.Flags().String("config", constants.ConfigFilename, "") + cmd.Flags().String("workspace", "", "") cmd.Flags().Bool("update-config", false, "") cmd.Flags().Bool("yes", false, "") cmd.Flags().String("name", "constell", "") @@ -536,9 +456,6 @@ func TestIAMCreateAzure(t *testing.T) { if tc.updateConfigFlag { require.NoError(cmd.Flags().Set("update-config", "true")) } - if tc.configFlag != "" { - require.NoError(cmd.Flags().Set("config", tc.configFlag)) - } fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingConfigFiles, tc.existingDirs)) @@ -566,7 +483,7 @@ func TestIAMCreateAzure(t *testing.T) { if tc.updateConfigFlag { readConfig := &config.Config{} - readErr := fileHandler.ReadYAML(tc.configFlag, readConfig) + readErr := fileHandler.ReadYAML(constants.ConfigFilename, readConfig) require.NoError(readErr) assert.Equal(tc.creator.id.AzureOutput.SubscriptionID, readConfig.Provider.Azure.SubscriptionID) assert.Equal(tc.creator.id.AzureOutput.TenantID, readConfig.Provider.Azure.TenantID) @@ -582,17 +499,7 @@ func TestIAMCreateAzure(t *testing.T) { } func TestIAMCreateGCP(t *testing.T) { - defaultFs := func(require *require.Assertions, provider cloudprovider.Provider, existingConfigFiles []string, existingDirs []string) afero.Fs { - fs := afero.NewMemMapFs() - fileHandler := file.NewHandler(fs) - for _, f := range existingConfigFiles { - require.NoError(fileHandler.WriteYAML(f, createConfig(cloudprovider.GCP), file.OptNone)) - } - for _, d := range existingDirs { - require.NoError(fs.MkdirAll(d, 0o755)) - } - return fs - } + defaultFs := createFSWithConfig(*createConfig(cloudprovider.GCP)) readOnlyFs := func(require *require.Assertions, provider cloudprovider.Provider, existingConfigFiles []string, existingDirs []string) afero.Fs { fs := afero.NewReadOnlyFs(afero.NewMemMapFs()) return fs @@ -619,7 +526,6 @@ func TestIAMCreateGCP(t *testing.T) { projectIDFlag string yesFlag bool updateConfigFlag bool - configFlag string existingConfigFiles []string existingDirs []string stdin string @@ -653,46 +559,9 @@ func TestIAMCreateGCP(t *testing.T) { serviceAccountIDFlag: "constell-test", projectIDFlag: "constell-1234", updateConfigFlag: true, - configFlag: constants.ConfigFilename, yesFlag: true, existingConfigFiles: []string{constants.ConfigFilename}, }, - "iam create gcp --update-config with --config": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - provider: cloudprovider.GCP, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - updateConfigFlag: true, - configFlag: "custom-config.yaml", - yesFlag: true, - existingConfigFiles: []string{"custom-config.yaml"}, - }, - "iam create gcp --update-config --config path doesn't exists": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - provider: cloudprovider.GCP, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - updateConfigFlag: true, - configFlag: constants.ConfigFilename, - yesFlag: true, - wantErr: true, - }, - "iam create gcp --update-config wrong --config path": { - setupFs: defaultFs, - creator: &stubIAMCreator{id: validIAMIDFile}, - provider: cloudprovider.GCP, - zoneFlag: "europe-west1-a", - serviceAccountIDFlag: "constell-test", - projectIDFlag: "constell-1234", - updateConfigFlag: true, - configFlag: "custom-config.yaml", - yesFlag: true, - wantErr: true, - }, "iam create gcp existing terraform dir": { setupFs: defaultFs, creator: &stubIAMCreator{id: validIAMIDFile}, @@ -740,7 +609,6 @@ func TestIAMCreateGCP(t *testing.T) { serviceAccountIDFlag: "constell-test", projectIDFlag: "constell-1234", stdin: "yes\n", - configFlag: constants.ConfigFilename, updateConfigFlag: true, existingConfigFiles: []string{constants.ConfigFilename}, }, @@ -763,7 +631,6 @@ func TestIAMCreateGCP(t *testing.T) { projectIDFlag: "constell-1234", stdin: "no\n", wantAbort: true, - configFlag: constants.ConfigFilename, updateConfigFlag: true, existingConfigFiles: []string{constants.ConfigFilename}, }, @@ -776,7 +643,6 @@ func TestIAMCreateGCP(t *testing.T) { projectIDFlag: "constell-1234", yesFlag: true, updateConfigFlag: true, - configFlag: constants.ConfigFilename, wantErr: true, }, } @@ -792,7 +658,7 @@ func TestIAMCreateGCP(t *testing.T) { cmd.SetIn(bytes.NewBufferString(tc.stdin)) // register persistent flags manually - cmd.Flags().String("config", constants.ConfigFilename, "") + cmd.Flags().String("workspace", "", "") cmd.Flags().Bool("update-config", false, "") cmd.Flags().Bool("yes", false, "") cmd.Flags().String("name", "constell", "") @@ -813,9 +679,6 @@ func TestIAMCreateGCP(t *testing.T) { if tc.updateConfigFlag { require.NoError(cmd.Flags().Set("update-config", "true")) } - if tc.configFlag != "" { - require.NoError(cmd.Flags().Set("config", tc.configFlag)) - } fileHandler := file.NewHandler(tc.setupFs(require, tc.provider, tc.existingConfigFiles, tc.existingDirs)) @@ -843,15 +706,15 @@ func TestIAMCreateGCP(t *testing.T) { if tc.updateConfigFlag { readConfig := &config.Config{} - readErr := fileHandler.ReadYAML(tc.configFlag, readConfig) + readErr := fileHandler.ReadYAML(constants.ConfigFilename, readConfig) require.NoError(readErr) - assert.Equal(constants.GCPServiceAccountKeyFile, readConfig.Provider.GCP.ServiceAccountKeyPath) + assert.Equal(gcpServiceAccountKeyFile, readConfig.Provider.GCP.ServiceAccountKeyPath) } require.NoError(err) assert.True(tc.creator.createCalled) assert.Equal(tc.creator.id.GCPOutput, validIAMIDFile.GCPOutput) readServiceAccountKey := &map[string]string{} - readErr := fileHandler.ReadJSON(constants.GCPServiceAccountKeyFile, readServiceAccountKey) + readErr := fileHandler.ReadJSON(gcpServiceAccountKeyFile, readServiceAccountKey) require.NoError(readErr) assert.Equal("not_a_secret", (*readServiceAccountKey)["private_key_id"]) }) @@ -939,7 +802,7 @@ func createFSWithConfig(cfg config.Config) func(require *require.Assertions, pro fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) for _, f := range existingConfigFiles { - require.NoError(fileHandler.WriteYAML(f, cfg, file.OptNone)) + require.NoError(fileHandler.WriteYAML(f, cfg, file.OptMkdirAll)) } for _, d := range existingDirs { require.NoError(fs.MkdirAll(d, 0o755)) diff --git a/cli/internal/cmd/iamdestroy.go b/cli/internal/cmd/iamdestroy.go index b1d80deda..09d2e3dd6 100644 --- a/cli/internal/cmd/iamdestroy.go +++ b/cli/internal/cmd/iamdestroy.go @@ -41,10 +41,7 @@ func runIAMDestroy(cmd *cobra.Command, _ []string) error { } defer log.Sync() spinner := newSpinner(cmd.ErrOrStderr()) - destroyer, err := cloudcmd.NewIAMDestroyer(cmd.Context()) - if err != nil { - return err - } + destroyer := cloudcmd.NewIAMDestroyer() fsHandler := file.NewHandler(afero.NewOsFs()) c := &destroyCmd{log: log} @@ -63,27 +60,27 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr } // check if there is a possibility that the cluster is still running by looking out for specific files - c.log.Debugf("Checking if %q exists", constants.AdminConfFilename) + c.log.Debugf("Checking if %q exists", adminConfPath(flags.workspace)) _, err = fsHandler.Stat(constants.AdminConfFilename) if !errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("file %q still exists, please make sure to terminate your cluster before destroying your IAM configuration", constants.AdminConfFilename) + return fmt.Errorf("file %q still exists, please make sure to terminate your cluster before destroying your IAM configuration", adminConfPath(flags.workspace)) } - c.log.Debugf("Checking if %q exists", constants.ClusterIDsFileName) - _, err = fsHandler.Stat(constants.ClusterIDsFileName) + c.log.Debugf("Checking if %q exists", clusterIDsPath(flags.workspace)) + _, err = fsHandler.Stat(constants.ClusterIDsFilename) if !errors.Is(err, os.ErrNotExist) { - return fmt.Errorf("file %q still exists, please make sure to terminate your cluster before destroying your IAM configuration", constants.ClusterIDsFileName) + return fmt.Errorf("file %q still exists, please make sure to terminate your cluster before destroying your IAM configuration", clusterIDsPath(flags.workspace)) } gcpFileExists := false - c.log.Debugf("Checking if %q exists", constants.GCPServiceAccountKeyFile) - _, err = fsHandler.Stat(constants.GCPServiceAccountKeyFile) + c.log.Debugf("Checking if %q exists", gcpServiceAccountKeyPath(flags.workspace)) + _, err = fsHandler.Stat(gcpServiceAccountKeyFile) if err != nil { if !errors.Is(err, os.ErrNotExist) { return err } } else { - c.log.Debugf("%q exists", constants.GCPServiceAccountKeyFile) + c.log.Debugf("%q exists", gcpServiceAccountKeyPath(flags.workspace)) gcpFileExists = true } @@ -91,7 +88,7 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr // Confirmation confirmString := "Do you really want to destroy your IAM configuration? Note that this will remove all resources in the resource group." if gcpFileExists { - confirmString += fmt.Sprintf("\nThis will also delete %q", constants.GCPServiceAccountKeyFile) + confirmString += fmt.Sprintf("\nThis will also delete %q", gcpServiceAccountKeyPath(flags.workspace)) } ok, err := askToConfirm(cmd, confirmString) if err != nil { @@ -104,8 +101,8 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr } if gcpFileExists { - c.log.Debugf("Starting to delete %q", constants.GCPServiceAccountKeyFile) - proceed, err := c.deleteGCPServiceAccountKeyFile(cmd, destroyer, fsHandler) + c.log.Debugf("Starting to delete %q", gcpServiceAccountKeyPath(flags.workspace)) + proceed, err := c.deleteGCPServiceAccountKeyFile(cmd, destroyer, flags.workspace, fsHandler) if err != nil { return err } @@ -119,7 +116,7 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr spinner.Start("Destroying IAM configuration", false) defer spinner.Stop() - if err := destroyer.DestroyIAMConfiguration(cmd.Context(), flags.tfLogLevel); err != nil { + if err := destroyer.DestroyIAMConfiguration(cmd.Context(), constants.TerraformIAMWorkingDir, flags.tfLogLevel); err != nil { return fmt.Errorf("destroying IAM configuration: %w", err) } @@ -128,36 +125,37 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr return nil } -func (c *destroyCmd) deleteGCPServiceAccountKeyFile(cmd *cobra.Command, destroyer iamDestroyer, fsHandler file.Handler) (bool, error) { +func (c *destroyCmd) deleteGCPServiceAccountKeyFile(cmd *cobra.Command, destroyer iamDestroyer, workspace string, fsHandler file.Handler) (bool, error) { var fileSaKey gcpshared.ServiceAccountKey - c.log.Debugf("Parsing %q", constants.GCPServiceAccountKeyFile) - if err := fsHandler.ReadJSON(constants.GCPServiceAccountKeyFile, &fileSaKey); err != nil { + c.log.Debugf("Parsing %q", gcpServiceAccountKeyPath(workspace)) + if err := fsHandler.ReadJSON(gcpServiceAccountKeyFile, &fileSaKey); err != nil { return false, err } c.log.Debugf("Getting service account key from the tfstate") - tfSaKey, err := destroyer.GetTfstateServiceAccountKey(cmd.Context()) + tfSaKey, err := destroyer.GetTfStateServiceAccountKey(cmd.Context(), constants.TerraformIAMWorkingDir) if err != nil { return false, err } c.log.Debugf("Checking if keys are the same") if tfSaKey != fileSaKey { - cmd.Printf("The key in %q don't match up with your Terraform state. %q will not be deleted.\n", constants.GCPServiceAccountKeyFile, constants.GCPServiceAccountKeyFile) + cmd.Printf("The key in %q don't match up with your Terraform state. %q will not be deleted.\n", gcpServiceAccountKeyPath(workspace), gcpServiceAccountKeyPath(workspace)) return true, nil } - if err := fsHandler.Remove(constants.GCPServiceAccountKeyFile); err != nil { + if err := fsHandler.Remove(gcpServiceAccountKeyFile); err != nil { return false, err } - c.log.Debugf("Successfully deleted %q", constants.GCPServiceAccountKeyFile) + c.log.Debugf("Successfully deleted %q", gcpServiceAccountKeyPath(workspace)) return true, nil } type destroyFlags struct { yes bool + workspace string tfLogLevel terraform.LogLevel } @@ -169,6 +167,12 @@ func (c *destroyCmd) parseDestroyFlags(cmd *cobra.Command) (destroyFlags, error) } c.log.Debugf("Yes flag is %t", yes) + workspace, err := cmd.Flags().GetString("workspace") + if err != nil { + return destroyFlags{}, fmt.Errorf("parsing workspace string: %w", err) + } + c.log.Debugf("Workspace set to %q", workspace) + logLevelString, err := cmd.Flags().GetString("tf-log") if err != nil { return destroyFlags{}, fmt.Errorf("parsing tf-log string: %w", err) @@ -177,10 +181,11 @@ func (c *destroyCmd) parseDestroyFlags(cmd *cobra.Command) (destroyFlags, error) if err != nil { return destroyFlags{}, fmt.Errorf("parsing Terraform log level %s: %w", logLevelString, err) } - c.log.Debugf("Terraform logs will be written into %s at level %s", constants.TerraformLogFile, logLevel.String()) + c.log.Debugf("Terraform logs will be written into %s at level %s", terraformLogPath(workspace), logLevel.String()) return destroyFlags{ tfLogLevel: logLevel, + workspace: workspace, yes: yes, }, nil } diff --git a/cli/internal/cmd/iamdestroy_test.go b/cli/internal/cmd/iamdestroy_test.go index 9ea5a2059..5ad4d6249 100644 --- a/cli/internal/cmd/iamdestroy_test.go +++ b/cli/internal/cmd/iamdestroy_test.go @@ -24,7 +24,7 @@ func TestIAMDestroy(t *testing.T) { newFsExists := func() file.Handler { fh := file.NewHandler(afero.NewMemMapFs()) - require.NoError(fh.Write(constants.GCPServiceAccountKeyFile, []byte("{}"))) + require.NoError(fh.Write(gcpServiceAccountKeyFile, []byte("{}"))) return fh } newFsMissing := func() file.Handler { @@ -38,7 +38,7 @@ func TestIAMDestroy(t *testing.T) { } newFsWithClusterIDFile := func() file.Handler { fh := file.NewHandler(afero.NewMemMapFs()) - require.NoError(fh.Write(constants.ClusterIDsFileName, []byte(""))) + require.NoError(fh.Write(constants.ClusterIDsFilename, []byte(""))) return fh } @@ -92,7 +92,7 @@ func TestIAMDestroy(t *testing.T) { "gcp delete error": { fh: newFsExists(), yesFlag: "true", - iamDestroyer: &stubIAMDestroyer{getTfstateKeyErr: someError}, + iamDestroyer: &stubIAMDestroyer{getTfStateKeyErr: someError}, wantErr: true, }, } @@ -108,6 +108,7 @@ func TestIAMDestroy(t *testing.T) { // register persistent flags manually cmd.Flags().String("tf-log", "NONE", "") + cmd.Flags().String("workspace", "", "") assert.NoError(cmd.Flags().Set("yes", tc.yesFlag)) @@ -146,12 +147,12 @@ func TestDeleteGCPServiceAccountKeyFile(t *testing.T) { newFs := func() file.Handler { fs := file.NewHandler(afero.NewMemMapFs()) - require.NoError(fs.Write(constants.GCPServiceAccountKeyFile, []byte(gcpFile))) + require.NoError(fs.Write(gcpServiceAccountKeyFile, []byte(gcpFile))) return fs } newFsInvalidJSON := func() file.Handler { fh := file.NewHandler(afero.NewMemMapFs()) - require.NoError(fh.Write(constants.GCPServiceAccountKeyFile, []byte("asdf"))) + require.NoError(fh.Write(gcpServiceAccountKeyFile, []byte("asdf"))) return fh } @@ -169,7 +170,7 @@ func TestDeleteGCPServiceAccountKeyFile(t *testing.T) { wantErr: true, }, "error getting key terraform": { - destroyer: &stubIAMDestroyer{getTfstateKeyErr: someError}, + destroyer: &stubIAMDestroyer{getTfStateKeyErr: someError}, fsHandler: newFs(), wantErr: true, wantGetSaKeyCalled: true, @@ -201,7 +202,7 @@ func TestDeleteGCPServiceAccountKeyFile(t *testing.T) { c := &destroyCmd{log: logger.NewTest(t)} - proceed, err := c.deleteGCPServiceAccountKeyFile(cmd, tc.destroyer, tc.fsHandler) + proceed, err := c.deleteGCPServiceAccountKeyFile(cmd, tc.destroyer, "", tc.fsHandler) if tc.wantErr { assert.Error(err) } else { @@ -209,7 +210,7 @@ func TestDeleteGCPServiceAccountKeyFile(t *testing.T) { } assert.Equal(tc.wantProceed, proceed) - assert.Equal(tc.wantGetSaKeyCalled, tc.destroyer.getTfstateKeyCalled) + assert.Equal(tc.wantGetSaKeyCalled, tc.destroyer.getTfStateKeyCalled) }) } } diff --git a/cli/internal/cmd/iamupgradeapply.go b/cli/internal/cmd/iamupgradeapply.go index 05c0720f2..1c694a9fa 100644 --- a/cli/internal/cmd/iamupgradeapply.go +++ b/cli/internal/cmd/iamupgradeapply.go @@ -16,6 +16,7 @@ import ( "github.com/edgelesssys/constellation/v2/internal/api/attestationconfigapi" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" + "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/google/uuid" "github.com/spf13/afero" @@ -50,23 +51,18 @@ func newIAMUpgradeApplyCmd() *cobra.Command { Args: cobra.NoArgs, RunE: runIAMUpgradeApply, } - cmd.Flags().BoolP("yes", "y", false, "run upgrades without further confirmation\n") + cmd.Flags().BoolP("yes", "y", false, "run upgrades without further confirmation") return cmd } func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error { - configPath, err := cmd.Flags().GetString("config") - if err != nil { - return err - } - force, err := cmd.Flags().GetBool("force") if err != nil { return fmt.Errorf("parsing force argument: %w", err) } fileHandler := file.NewHandler(afero.NewOsFs()) configFetcher := attestationconfigapi.NewFetcher() - conf, err := config.New(fileHandler, configPath, configFetcher, force) + conf, err := config.New(fileHandler, constants.ConfigFilename, configFetcher, force) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { cmd.PrintErrln(configValidationErr.LongMessage()) @@ -75,7 +71,7 @@ func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error { return err } upgradeID := "iam-" + time.Now().Format("20060102150405") + "-" + strings.Split(uuid.New().String(), "-")[0] - iamMigrateCmd, err := upgrade.NewIAMMigrateCmd(cmd.Context(), upgradeID, conf.GetProvider(), terraform.LogLevelDebug) + iamMigrateCmd, err := upgrade.NewIAMMigrateCmd(cmd.Context(), constants.TerraformIAMWorkingDir, constants.UpgradeDir, upgradeID, conf.GetProvider(), terraform.LogLevelDebug) if err != nil { return fmt.Errorf("setting up IAM migration command: %w", err) } @@ -90,7 +86,7 @@ func runIAMUpgradeApply(cmd *cobra.Command, _ []string) error { if err != nil { return err } - err = migrator.applyMigration(cmd, file.NewHandler(afero.NewOsFs()), iamMigrateCmd, yes) + err = migrator.applyMigration(cmd, constants.UpgradeDir, file.NewHandler(afero.NewOsFs()), iamMigrateCmd, yes) if err != nil { return fmt.Errorf("applying IAM migration: %w", err) } diff --git a/cli/internal/cmd/init.go b/cli/internal/cmd/init.go index 7cb4f2dc3..58645323e 100644 --- a/cli/internal/cmd/init.go +++ b/cli/internal/cmd/init.go @@ -15,6 +15,7 @@ import ( "io" "net" "os" + "path/filepath" "strconv" "sync" "text/tabwriter" @@ -65,7 +66,6 @@ func NewInitCmd() *cobra.Command { Args: cobra.ExactArgs(0), RunE: runInitialize, } - cmd.Flags().String("master-secret", "", "path to base64-encoded master secret") cmd.Flags().Bool("conformance", false, "enable conformance mode") cmd.Flags().Bool("skip-helm-wait", false, "install helm charts without waiting for deployments to be ready") cmd.Flags().Bool("merge-kubeconfig", false, "merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config") @@ -76,9 +76,8 @@ type initCmd struct { log debugLog merger configMerger spinner spinnerInterf - masterSecret uri.MasterSecret fileHandler file.Handler - helmInstaller helm.Initializer + helmInstaller initializer clusterShower clusterShower } @@ -87,7 +86,7 @@ type clusterShower interface { } func newInitCmd( - clusterShower clusterShower, helmInstaller helm.Initializer, fileHandler file.Handler, + clusterShower clusterShower, helmInstaller initializer, fileHandler file.Handler, spinner spinnerInterf, merger configMerger, log debugLog, ) *initCmd { return &initCmd{ @@ -121,14 +120,15 @@ func runInitialize(cmd *cobra.Command, _ []string) error { ctx, cancel := context.WithTimeout(cmd.Context(), time.Hour) defer cancel() cmd.SetContext(ctx) - helmInstaller, err := helm.NewInitializer(log) - if err != nil { - return fmt.Errorf("creating Helm installer: %w", err) - } + tfClient, err := terraform.New(ctx, constants.TerraformWorkingDir) if err != nil { return fmt.Errorf("creating Terraform client: %w", err) } + helmInstaller, err := helm.NewInitializer(log, constants.AdminConfFilename) + if err != nil { + return fmt.Errorf("creating Helm installer: %w", err) + } i := newInitCmd(tfClient, helmInstaller, fileHandler, spinner, &kubeconfigMerger{log: log}, log) fetcher := attestationconfigapi.NewFetcher() return i.initialize(cmd, newDialer, license.NewClient(), fetcher) @@ -143,8 +143,8 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.V return err } i.log.Debugf("Using flags: %+v", flags) - i.log.Debugf("Loading configuration file from %q", flags.configPath) - conf, err := config.New(i.fileHandler, flags.configPath, configFetcher, flags.force) + i.log.Debugf("Loading configuration file from %q", configPath(flags.workspace)) + conf, err := config.New(i.fileHandler, constants.ConfigFilename, configFetcher, flags.force) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { cmd.PrintErrln(configValidationErr.LongMessage()) @@ -163,7 +163,7 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.V i.log.Debugf("Checking cluster ID file") var idFile clusterid.File - if err := i.fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { + if err := i.fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { return fmt.Errorf("reading cluster ID file: %w", err) } @@ -193,15 +193,14 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.V return fmt.Errorf("creating new validator: %w", err) } i.log.Debugf("Created a new validator") - serviceAccURI, err := i.getMarshaledServiceAccountURI(provider, conf) + serviceAccURI, err := i.getMarshaledServiceAccountURI(provider, conf, flags.workspace) if err != nil { return err } i.log.Debugf("Successfully marshaled service account URI") - masterSecret, err := i.readOrGenerateMasterSecret(cmd.OutOrStdout(), flags.masterSecretPath) - i.masterSecret = masterSecret + masterSecret, err := i.generateMasterSecret(cmd.OutOrStdout(), flags.workspace) if err != nil { - return fmt.Errorf("parsing or generating master secret from file %s: %w", flags.masterSecretPath, err) + return fmt.Errorf("generating master secret: %w", err) } clusterName := clusterid.GetClusterName(conf, idFile) @@ -239,8 +238,7 @@ func (i *initCmd) initialize(cmd *cobra.Command, newDialer func(validator atls.V idFile.CloudProvider = provider bufferedOutput := &bytes.Buffer{} - err = i.writeOutput(idFile, resp, flags.mergeConfigs, bufferedOutput) - if err != nil { + if err := i.writeOutput(idFile, resp, flags.mergeConfigs, bufferedOutput, flags.workspace); err != nil { return err } @@ -388,7 +386,8 @@ func (d *initDoer) handleGRPCStateChanges(ctx context.Context, wg *sync.WaitGrou } func (i *initCmd) writeOutput( - idFile clusterid.File, initResp *initproto.InitSuccessResponse, mergeConfig bool, wr io.Writer, + idFile clusterid.File, initResp *initproto.InitSuccessResponse, + mergeConfig bool, wr io.Writer, workspace string, ) error { fmt.Fprint(wr, "Your Constellation cluster was successfully initialized.\n\n") @@ -399,14 +398,14 @@ func (i *initCmd) writeOutput( tw := tabwriter.NewWriter(wr, 0, 0, 2, ' ', 0) // writeRow(tw, "Constellation cluster's owner identifier", ownerID) writeRow(tw, "Constellation cluster identifier", clusterID) - writeRow(tw, "Kubernetes configuration", constants.AdminConfFilename) + writeRow(tw, "Kubernetes configuration", adminConfPath(workspace)) tw.Flush() fmt.Fprintln(wr) if err := i.fileHandler.Write(constants.AdminConfFilename, initResp.GetKubeconfig(), file.OptNone); err != nil { return fmt.Errorf("writing kubeconfig: %w", err) } - i.log.Debugf("Kubeconfig written to %s", constants.AdminConfFilename) + i.log.Debugf("Kubeconfig written to %s", adminConfPath(workspace)) if mergeConfig { if err := i.merger.mergeConfigs(constants.AdminConfFilename, i.fileHandler); err != nil { @@ -420,14 +419,14 @@ func (i *initCmd) writeOutput( idFile.OwnerID = ownerID idFile.ClusterID = clusterID - if err := i.fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptOverwrite); err != nil { + if err := i.fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptOverwrite); err != nil { return fmt.Errorf("writing Constellation ID file: %w", err) } - i.log.Debugf("Constellation ID file written to %s", constants.ClusterIDsFileName) + i.log.Debugf("Constellation ID file written to %s", clusterIDsPath(workspace)) if !mergeConfig { fmt.Fprintln(wr, "You can now connect to your cluster by executing:") - fmt.Fprintf(wr, "\texport KUBECONFIG=\"$PWD/%s\"\n", constants.AdminConfFilename) + fmt.Fprintf(wr, "\texport KUBECONFIG=\"$PWD/%s\"\n", adminConfPath(workspace)) } else { fmt.Fprintln(wr, "Constellation kubeconfig merged with default config.") @@ -448,11 +447,6 @@ func writeRow(wr io.Writer, col1 string, col2 string) { // evalFlagArgs gets the flag values and does preprocessing of these values like // reading the content from file path flags and deriving other values from flag combinations. func (i *initCmd) evalFlagArgs(cmd *cobra.Command) (initFlags, error) { - masterSecretPath, err := cmd.Flags().GetString("master-secret") - if err != nil { - return initFlags{}, fmt.Errorf("parsing master-secret path flag: %w", err) - } - i.log.Debugf("Master secret path flag value is %q", masterSecretPath) conformance, err := cmd.Flags().GetBool("conformance") if err != nil { return initFlags{}, fmt.Errorf("parsing conformance flag: %w", err) @@ -467,7 +461,7 @@ func (i *initCmd) evalFlagArgs(cmd *cobra.Command) (initFlags, error) { helmWaitMode = helm.WaitModeNone } i.log.Debugf("Helm wait flag is %t", skipHelmWait) - configPath, err := cmd.Flags().GetString("config") + workspace, err := cmd.Flags().GetString("workspace") if err != nil { return initFlags{}, fmt.Errorf("parsing config path flag: %w", err) } @@ -485,43 +479,25 @@ func (i *initCmd) evalFlagArgs(cmd *cobra.Command) (initFlags, error) { i.log.Debugf("force flag is %t", force) return initFlags{ - configPath: configPath, - conformance: conformance, - helmWaitMode: helmWaitMode, - masterSecretPath: masterSecretPath, - force: force, - mergeConfigs: mergeConfigs, + workspace: workspace, + conformance: conformance, + helmWaitMode: helmWaitMode, + force: force, + mergeConfigs: mergeConfigs, }, nil } // initFlags are the resulting values of flag preprocessing. type initFlags struct { - configPath string - masterSecretPath string - conformance bool - helmWaitMode helm.WaitMode - force bool - mergeConfigs bool + workspace string + conformance bool + helmWaitMode helm.WaitMode + force bool + mergeConfigs bool } // readOrGenerateMasterSecret reads a base64 encoded master secret from file or generates a new 32 byte secret. -func (i *initCmd) readOrGenerateMasterSecret(outWriter io.Writer, filename string) (uri.MasterSecret, error) { - if filename != "" { - i.log.Debugf("Reading master secret from file %q", filename) - var secret uri.MasterSecret - if err := i.fileHandler.ReadJSON(filename, &secret); err != nil { - return uri.MasterSecret{}, err - } - - if len(secret.Key) < crypto.MasterSecretLengthMin { - return uri.MasterSecret{}, fmt.Errorf("provided master secret is smaller than the required minimum of %d Bytes", crypto.MasterSecretLengthMin) - } - if len(secret.Salt) < crypto.RNGLengthDefault { - return uri.MasterSecret{}, fmt.Errorf("provided salt is smaller than the required minimum of %d Bytes", crypto.RNGLengthDefault) - } - return secret, nil - } - +func (i *initCmd) generateMasterSecret(outWriter io.Writer, workspace string) (uri.MasterSecret, error) { // No file given, generate a new secret, and save it to disk i.log.Debugf("Generating new master secret") key, err := crypto.GenerateRandomBytes(crypto.MasterSecretLengthDefault) @@ -540,21 +516,21 @@ func (i *initCmd) readOrGenerateMasterSecret(outWriter io.Writer, filename strin if err := i.fileHandler.WriteJSON(constants.MasterSecretFilename, secret, file.OptNone); err != nil { return uri.MasterSecret{}, err } - fmt.Fprintf(outWriter, "Your Constellation master secret was successfully written to ./%s\n", constants.MasterSecretFilename) + fmt.Fprintf(outWriter, "Your Constellation master secret was successfully written to %q\n", masterSecretPath(workspace)) return secret, nil } -func (i *initCmd) getMarshaledServiceAccountURI(provider cloudprovider.Provider, config *config.Config) (string, error) { +func (i *initCmd) getMarshaledServiceAccountURI(provider cloudprovider.Provider, config *config.Config, workspace string, +) (string, error) { i.log.Debugf("Getting service account URI") switch provider { case cloudprovider.GCP: i.log.Debugf("Handling case for GCP") - path := config.Provider.GCP.ServiceAccountKeyPath - i.log.Debugf("GCP service account key path %s", path) + i.log.Debugf("GCP service account key path %s", filepath.Join(workspace, config.Provider.GCP.ServiceAccountKeyPath)) var key gcpshared.ServiceAccountKey - if err := i.fileHandler.ReadJSON(path, &key); err != nil { - return "", fmt.Errorf("reading service account key from path %q: %w", path, err) + if err := i.fileHandler.ReadJSON(config.Provider.GCP.ServiceAccountKeyPath, &key); err != nil { + return "", fmt.Errorf("reading service account key from path %q: %w", filepath.Join(workspace, config.Provider.GCP.ServiceAccountKeyPath), err) } i.log.Debugf("Read GCP service account key from path") return key.ToCloudServiceAccountURI(), nil @@ -667,3 +643,7 @@ func (e *nonRetriableError) Error() string { func (e *nonRetriableError) Unwrap() error { return e.err } + +type initializer interface { + Install(ctx context.Context, releases *helm.Releases) error +} diff --git a/cli/internal/cmd/init_test.go b/cli/internal/cmd/init_test.go index 4cf643325..3cc92a6ce 100644 --- a/cli/internal/cmd/init_test.go +++ b/cli/internal/cmd/init_test.go @@ -162,8 +162,8 @@ func TestInitialize(t *testing.T) { cmd.SetErr(&errOut) // Flags - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("force", true, "") // register persistent flag manually + cmd.Flags().String("workspace", "", "") // register persistent flag manually + cmd.Flags().Bool("force", true, "") // register persistent flag manually // File system preparation fs := afero.NewMemMapFs() @@ -175,7 +175,7 @@ func TestInitialize(t *testing.T) { require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptNone)) if tc.idFile != nil { tc.idFile.CloudProvider = tc.provider - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.idFile, file.OptNone)) + require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, tc.idFile, file.OptNone)) } if tc.serviceAccKey != nil { require.NoError(fileHandler.WriteJSON(serviceAccPath, tc.serviceAccKey, file.OptNone)) @@ -301,9 +301,8 @@ func TestWriteOutput(t *testing.T) { UID: "test-uid", IP: "cluster-ip", } - i := newInitCmd(nil, nil, fileHandler, nil, &stubMerger{}, logger.NewTest(t)) - err := i.writeOutput(idFile, resp.GetInitSuccess(), false, &out) + err := i.writeOutput(idFile, resp.GetInitSuccess(), false, &out, "") require.NoError(err) // assert.Contains(out.String(), ownerID) assert.Contains(out.String(), clusterID) @@ -314,29 +313,39 @@ func TestWriteOutput(t *testing.T) { assert.NoError(err) assert.Equal(string(resp.GetInitSuccess().GetKubeconfig()), string(adminConf)) - idsFile, err := afs.ReadFile(constants.ClusterIDsFileName) + idsFile, err := afs.ReadFile(constants.ClusterIDsFilename) assert.NoError(err) var testIDFile clusterid.File err = json.Unmarshal(idsFile, &testIDFile) assert.NoError(err) assert.Equal(expectedIDFile, testIDFile) - - // test config merging out.Reset() require.NoError(afs.Remove(constants.AdminConfFilename)) - err = i.writeOutput(idFile, resp.GetInitSuccess(), true, &out) + + // test custom workspace + err = i.writeOutput(idFile, resp.GetInitSuccess(), true, &out, "some/path") + require.NoError(err) + // assert.Contains(out.String(), ownerID) + assert.Contains(out.String(), clusterID) + assert.Contains(out.String(), adminConfPath("some/path")) + out.Reset() + // File is written to current working dir, we simply pass the workspace for generating readable user output + require.NoError(afs.Remove(constants.AdminConfFilename)) + + // test config merging + err = i.writeOutput(idFile, resp.GetInitSuccess(), true, &out, "") require.NoError(err) // assert.Contains(out.String(), ownerID) assert.Contains(out.String(), clusterID) assert.Contains(out.String(), constants.AdminConfFilename) assert.Contains(out.String(), "Constellation kubeconfig merged with default config") assert.Contains(out.String(), "You can now connect to your cluster") + out.Reset() + require.NoError(afs.Remove(constants.AdminConfFilename)) // test config merging with env vars set i.merger = &stubMerger{envVar: "/some/path/to/kubeconfig"} - out.Reset() - require.NoError(afs.Remove(constants.AdminConfFilename)) - err = i.writeOutput(idFile, resp.GetInitSuccess(), true, &out) + err = i.writeOutput(idFile, resp.GetInitSuccess(), true, &out, "") require.NoError(err) // assert.Contains(out.String(), ownerID) assert.Contains(out.String(), clusterID) @@ -345,79 +354,29 @@ func TestWriteOutput(t *testing.T) { assert.Contains(out.String(), "Warning: KUBECONFIG environment variable is set") } -func TestReadOrGenerateMasterSecret(t *testing.T) { +func TestGenerateMasterSecret(t *testing.T) { testCases := map[string]struct { - filename string createFileFunc func(handler file.Handler) error fs func() afero.Fs wantErr bool }{ - "file with secret exists": { - filename: "someSecret", - fs: afero.NewMemMapFs, + "file already exists": { + fs: afero.NewMemMapFs, createFileFunc: func(handler file.Handler) error { return handler.WriteJSON( - "someSecret", + constants.MasterSecretFilename, uri.MasterSecret{Key: []byte("constellation-master-secret"), Salt: []byte("constellation-32Byte-length-salt")}, file.OptNone, ) }, - wantErr: false, + wantErr: true, }, - "no file given": { - filename: "", + "file does not exist": { createFileFunc: func(handler file.Handler) error { return nil }, fs: afero.NewMemMapFs, wantErr: false, }, - "file does not exist": { - filename: "nonExistingSecret", - createFileFunc: func(handler file.Handler) error { return nil }, - fs: afero.NewMemMapFs, - wantErr: true, - }, - "file is empty": { - filename: "emptySecret", - createFileFunc: func(handler file.Handler) error { - return handler.Write("emptySecret", []byte{}, file.OptNone) - }, - fs: afero.NewMemMapFs, - wantErr: true, - }, - "salt too short": { - filename: "shortSecret", - createFileFunc: func(handler file.Handler) error { - return handler.WriteJSON( - "shortSecret", - uri.MasterSecret{Key: []byte("constellation-master-secret"), Salt: []byte("short")}, - file.OptNone, - ) - }, - fs: afero.NewMemMapFs, - wantErr: true, - }, - "key too short": { - filename: "shortSecret", - createFileFunc: func(handler file.Handler) error { - return handler.WriteJSON( - "shortSecret", - uri.MasterSecret{Key: []byte("short"), Salt: []byte("constellation-32Byte-length-salt")}, - file.OptNone, - ) - }, - fs: afero.NewMemMapFs, - wantErr: true, - }, - "invalid file content": { - filename: "unencodedSecret", - createFileFunc: func(handler file.Handler) error { - return handler.Write("unencodedSecret", []byte("invalid-constellation-master-secret"), file.OptNone) - }, - fs: afero.NewMemMapFs, - wantErr: true, - }, "file not writeable": { - filename: "", createFileFunc: func(handler file.Handler) error { return nil }, fs: func() afero.Fs { return afero.NewReadOnlyFs(afero.NewMemMapFs()) }, wantErr: true, @@ -434,21 +393,17 @@ func TestReadOrGenerateMasterSecret(t *testing.T) { var out bytes.Buffer i := newInitCmd(nil, nil, fileHandler, nil, nil, logger.NewTest(t)) - secret, err := i.readOrGenerateMasterSecret(&out, tc.filename) + secret, err := i.generateMasterSecret(&out, "") if tc.wantErr { assert.Error(err) } else { assert.NoError(err) - if tc.filename == "" { - require.Contains(out.String(), constants.MasterSecretFilename) - filename := strings.Split(out.String(), "./") - tc.filename = strings.Trim(filename[1], "\n") - } + require.Contains(out.String(), constants.MasterSecretFilename) var masterSecret uri.MasterSecret - require.NoError(fileHandler.ReadJSON(tc.filename, &masterSecret)) + require.NoError(fileHandler.ReadJSON(constants.MasterSecretFilename, &masterSecret)) assert.Equal(masterSecret.Key, secret.Key) assert.Equal(masterSecret.Salt, secret.Salt) } @@ -491,8 +446,8 @@ func TestAttestation(t *testing.T) { defer initServer.GracefulStop() cmd := NewInitCmd() - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("force", true, "") // register persistent flag manually + cmd.Flags().String("workspace", "", "") // register persistent flag manually + cmd.Flags().Bool("force", true, "") // register persistent flag manually var out bytes.Buffer cmd.SetOut(&out) var errOut bytes.Buffer @@ -500,7 +455,7 @@ func TestAttestation(t *testing.T) { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, existingIDFile, file.OptNone)) + require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, existingIDFile, file.OptNone)) cfg := config.Default() cfg.Image = "v0.0.0" // is the default version of the the CLI (before build injects the real version) diff --git a/cli/internal/cmd/minidown.go b/cli/internal/cmd/minidown.go index a869536ba..234d828b1 100644 --- a/cli/internal/cmd/minidown.go +++ b/cli/internal/cmd/minidown.go @@ -45,7 +45,7 @@ func runDown(cmd *cobra.Command, args []string) error { func checkForMiniCluster(fileHandler file.Handler) error { var idFile clusterid.File - if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { + if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { return err } if idFile.CloudProvider != cloudprovider.QEMU { diff --git a/cli/internal/cmd/miniup.go b/cli/internal/cmd/miniup.go index f07b67bac..0000a8dcb 100644 --- a/cli/internal/cmd/miniup.go +++ b/cli/internal/cmd/miniup.go @@ -39,8 +39,6 @@ func newMiniUpCmd() *cobra.Command { RunE: runUp, } - // override global flag so we don't have a default value for the config - cmd.Flags().String("config", "", "path to the configuration file to use for the cluster") cmd.Flags().Bool("merge-kubeconfig", true, "merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config") return cmd @@ -88,7 +86,7 @@ func (m *miniUpCmd) up(cmd *cobra.Command, creator cloudCreator, spinner spinner // create cluster spinner.Start("Creating cluster in QEMU ", false) - err = m.createMiniCluster(cmd.Context(), fileHandler, creator, config, flags.tfLogLevel) + err = m.createMiniCluster(cmd.Context(), fileHandler, creator, config, flags) spinner.Stop() if err != nil { return fmt.Errorf("creating cluster: %w", err) @@ -112,38 +110,28 @@ func (m *miniUpCmd) up(cmd *cobra.Command, creator cloudCreator, spinner spinner // prepareConfig reads a given config, or creates a new minimal QEMU config. func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler, flags upFlags) (*config.Config, error) { - // check for existing config - if flags.configPath != "" { - conf, err := config.New(fileHandler, flags.configPath, m.configFetcher, flags.force) - var configValidationErr *config.ValidationError - if errors.As(err, &configValidationErr) { - cmd.PrintErrln(configValidationErr.LongMessage()) - } - if err != nil { - return nil, err - } - if conf.GetProvider() != cloudprovider.QEMU { - return nil, errors.New("invalid provider for MiniConstellation cluster") - } - return conf, nil - } - m.log.Debugf("Configuration path is %q", flags.configPath) - if err := cmd.Flags().Set("config", constants.ConfigFilename); err != nil { - return nil, err - } _, err := fileHandler.Stat(constants.ConfigFilename) if err == nil { - // config already exists, prompt user to overwrite - cmd.PrintErrln("A config file already exists in the current workspace. Use --config to use an existing config file.") - ok, err := askToConfirm(cmd, "Do you want to overwrite it?") + // config already exists, prompt user if they want to use this file + cmd.PrintErrln("A config file already exists in the configured workspace.") + ok, err := askToConfirm(cmd, "Do you want to create the Constellation using that config?") if err != nil { return nil, err } + if ok { + return m.prepareExistingConfig(cmd, fileHandler, flags) + } + // user declined to reuse config file, prompt if they want to overwrite it + ok, err = askToConfirm(cmd, "Do you want to overwrite it and create a new config?") + if err != nil { + return nil, err + } if !ok { return nil, errors.New("not overwriting existing config") } } + if !featureset.CanUseEmbeddedMeasurmentsAndImage { cmd.PrintErrln("Generating a valid default config is not supported in the OSS build of the Constellation CLI. Consult the documentation for instructions on where to download the enterprise version.") return nil, errors.New("cannot create a mini cluster without a config file in the OSS build") @@ -157,13 +145,29 @@ func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler, return config, fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptOverwrite) } +func (m *miniUpCmd) prepareExistingConfig(cmd *cobra.Command, fileHandler file.Handler, flags upFlags) (*config.Config, error) { + conf, err := config.New(fileHandler, constants.ConfigFilename, m.configFetcher, flags.force) + var configValidationErr *config.ValidationError + if errors.As(err, &configValidationErr) { + cmd.PrintErrln(configValidationErr.LongMessage()) + } + if err != nil { + return nil, err + } + if conf.GetProvider() != cloudprovider.QEMU { + return nil, errors.New("invalid provider for MiniConstellation cluster") + } + return conf, nil +} + // createMiniCluster creates a new cluster using the given config. -func (m *miniUpCmd) createMiniCluster(ctx context.Context, fileHandler file.Handler, creator cloudCreator, config *config.Config, tfLogLevel terraform.LogLevel) error { +func (m *miniUpCmd) createMiniCluster(ctx context.Context, fileHandler file.Handler, creator cloudCreator, config *config.Config, flags upFlags) error { m.log.Debugf("Creating mini cluster") opts := cloudcmd.CreateOptions{ - Provider: cloudprovider.QEMU, - Config: config, - TFLogLevel: tfLogLevel, + Provider: cloudprovider.QEMU, + Config: config, + TFWorkspace: constants.TerraformWorkingDir, + TFLogLevel: flags.tfLogLevel, } idFile, err := creator.Create(ctx, opts) if err != nil { @@ -172,7 +176,7 @@ func (m *miniUpCmd) createMiniCluster(ctx context.Context, fileHandler file.Hand idFile.UID = constants.MiniConstellationUID // use UID "mini" to identify MiniConstellation clusters. m.log.Debugf("Cluster id file contains %v", idFile) - return fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone) + return fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptNone) } // initializeMiniCluster initializes a QEMU cluster. @@ -191,7 +195,6 @@ func (m *miniUpCmd) initializeMiniCluster(cmd *cobra.Command, fileHandler file.H return dialer.New(nil, validator, &net.Dialer{}) } m.log.Debugf("Created new dialer") - cmd.Flags().String("master-secret", "", "") cmd.Flags().String("endpoint", "", "") cmd.Flags().Bool("conformance", false, "") cmd.Flags().Bool("skip-helm-wait", false, "install helm charts without waiting for deployments to be ready") @@ -202,7 +205,7 @@ func (m *miniUpCmd) initializeMiniCluster(cmd *cobra.Command, fileHandler file.H m.log.Debugf("Created new logger") defer log.Sync() - helmInstaller, err := helm.NewInitializer(log) + helmInstaller, err := helm.NewInitializer(log, constants.AdminConfFilename) if err != nil { return fmt.Errorf("creating Helm installer: %w", err) } @@ -220,14 +223,13 @@ func (m *miniUpCmd) initializeMiniCluster(cmd *cobra.Command, fileHandler file.H } type upFlags struct { - configPath string force bool tfLogLevel terraform.LogLevel } func (m *miniUpCmd) parseUpFlags(cmd *cobra.Command) (upFlags, error) { m.log.Debugf("Preparing configuration") - configPath, err := cmd.Flags().GetString("config") + workspace, err := cmd.Flags().GetString("workspace") if err != nil { return upFlags{}, fmt.Errorf("parsing config string: %w", err) } @@ -246,10 +248,9 @@ func (m *miniUpCmd) parseUpFlags(cmd *cobra.Command) (upFlags, error) { if err != nil { return upFlags{}, fmt.Errorf("parsing Terraform log level %s: %w", logLevelString, err) } - m.log.Debugf("Terraform logs will be written into %s at level %s", constants.TerraformLogFile, logLevel.String()) + m.log.Debugf("Terraform logs will be written into %s at level %s", terraformLogPath(workspace), logLevel.String()) return upFlags{ - configPath: configPath, force: force, tfLogLevel: logLevel, }, nil diff --git a/cli/internal/cmd/recover.go b/cli/internal/cmd/recover.go index 67e3b4a3e..5c97f679c 100644 --- a/cli/internal/cmd/recover.go +++ b/cli/internal/cmd/recover.go @@ -44,7 +44,6 @@ func NewRecoverCmd() *cobra.Command { RunE: runRecover, } cmd.Flags().StringP("endpoint", "e", "", "endpoint of the instance, passed as HOST[:PORT]") - cmd.Flags().String("master-secret", constants.MasterSecretFilename, "path to master secret file") return cmd } @@ -78,13 +77,13 @@ func (r *recoverCmd) recover( r.log.Debugf("Using flags: %+v", flags) var masterSecret uri.MasterSecret - r.log.Debugf("Loading master secret file from %s", flags.secretPath) - if err := fileHandler.ReadJSON(flags.secretPath, &masterSecret); err != nil { + r.log.Debugf("Loading master secret file from %s", masterSecretPath(flags.workspace)) + if err := fileHandler.ReadJSON(constants.MasterSecretFilename, &masterSecret); err != nil { return err } - r.log.Debugf("Loading configuration file from %q", flags.configPath) - conf, err := config.New(fileHandler, flags.configPath, r.configFetcher, flags.force) + r.log.Debugf("Loading configuration file from %q", configPath(flags.workspace)) + conf, err := config.New(fileHandler, constants.ConfigFilename, r.configFetcher, flags.force) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { cmd.PrintErrln(configValidationErr.LongMessage()) @@ -211,16 +210,21 @@ func (d *recoverDoer) setURIs(kmsURI, storageURI string) { } type recoverFlags struct { - endpoint string - secretPath string - configPath string - maaURL string - force bool + endpoint string + workspace string + maaURL string + force bool } func (r *recoverCmd) parseRecoverFlags(cmd *cobra.Command, fileHandler file.Handler) (recoverFlags, error) { + workspace, err := cmd.Flags().GetString("workspace") + if err != nil { + return recoverFlags{}, fmt.Errorf("parsing config path argument: %w", err) + } + r.log.Debugf("Workspace set to %q", workspace) + var idFile clusterid.File - if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) { + if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) { return recoverFlags{}, err } @@ -237,16 +241,6 @@ func (r *recoverCmd) parseRecoverFlags(cmd *cobra.Command, fileHandler file.Hand return recoverFlags{}, fmt.Errorf("validating endpoint argument: %w", err) } r.log.Debugf("Endpoint value after parsing is %s", endpoint) - masterSecretPath, err := cmd.Flags().GetString("master-secret") - if err != nil { - return recoverFlags{}, fmt.Errorf("parsing master-secret path argument: %w", err) - } - r.log.Debugf("Master secret flag is %s", masterSecretPath) - configPath, err := cmd.Flags().GetString("config") - if err != nil { - return recoverFlags{}, fmt.Errorf("parsing config path argument: %w", err) - } - r.log.Debugf("Configuration path flag is %s", configPath) force, err := cmd.Flags().GetBool("force") if err != nil { @@ -254,11 +248,10 @@ func (r *recoverCmd) parseRecoverFlags(cmd *cobra.Command, fileHandler file.Hand } return recoverFlags{ - endpoint: endpoint, - secretPath: masterSecretPath, - configPath: configPath, - maaURL: idFile.AttestationURL, - force: force, + endpoint: endpoint, + workspace: workspace, + maaURL: idFile.AttestationURL, + force: force, }, nil } diff --git a/cli/internal/cmd/recover_test.go b/cli/internal/cmd/recover_test.go index 15d345d86..f443ee739 100644 --- a/cli/internal/cmd/recover_test.go +++ b/cli/internal/cmd/recover_test.go @@ -67,12 +67,12 @@ func TestRecover(t *testing.T) { lbErr := grpcstatus.Error(codes.Unavailable, `connection error: desc = "transport: authentication handshake failed: read tcp`) testCases := map[string]struct { - doer *stubDoer - masterSecret testvector.HKDF - endpoint string - configFlag string - successfulCalls int - wantErr bool + doer *stubDoer + masterSecret testvector.HKDF + endpoint string + successfulCalls int + skipConfigCreation bool + wantErr bool }{ "works": { doer: &stubDoer{returns: []error{nil}}, @@ -81,11 +81,11 @@ func TestRecover(t *testing.T) { successfulCalls: 1, }, "missing config": { - doer: &stubDoer{returns: []error{nil}}, - endpoint: "192.0.2.89", - masterSecret: testvector.HKDFZero, - configFlag: "nonexistent-config", - wantErr: true, + doer: &stubDoer{returns: []error{nil}}, + endpoint: "192.0.2.89", + masterSecret: testvector.HKDFZero, + skipConfigCreation: true, + wantErr: true, }, "success multiple nodes": { doer: &stubDoer{returns: []error{nil, nil}}, @@ -139,22 +139,20 @@ func TestRecover(t *testing.T) { cmd := NewRecoverCmd() cmd.SetContext(context.Background()) - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("force", true, "") // register persistent flag manually + cmd.Flags().String("workspace", "", "") // register persistent flag manually + cmd.Flags().Bool("force", true, "") // register persistent flag manually out := &bytes.Buffer{} cmd.SetOut(out) cmd.SetErr(out) require.NoError(cmd.Flags().Set("endpoint", tc.endpoint)) - if tc.configFlag != "" { - require.NoError(cmd.Flags().Set("config", tc.configFlag)) - } - fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) - config := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.GCP) - require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config)) + if !tc.skipConfigCreation { + config := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.GCP) + require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config)) + } require.NoError(fileHandler.WriteJSON( "constellation-mastersecret.json", @@ -193,15 +191,13 @@ func TestParseRecoverFlags(t *testing.T) { }{ "no flags": { wantFlags: recoverFlags{ - endpoint: "192.0.2.42:9999", - secretPath: "constellation-mastersecret.json", + endpoint: "192.0.2.42:9999", }, writeIDFile: true, }, "no flags, no ID file": { wantFlags: recoverFlags{ - endpoint: "192.0.2.42:9999", - secretPath: "constellation-mastersecret.json", + endpoint: "192.0.2.42:9999", }, wantErr: true, }, @@ -210,11 +206,10 @@ func TestParseRecoverFlags(t *testing.T) { wantErr: true, }, "all args set": { - args: []string{"-e", "192.0.2.42:2", "--config", "config-path", "--master-secret", "/path/super-secret.json"}, + args: []string{"-e", "192.0.2.42:2", "--workspace", "./constellation-workspace"}, wantFlags: recoverFlags{ - endpoint: "192.0.2.42:2", - secretPath: "/path/super-secret.json", - configPath: "config-path", + endpoint: "192.0.2.42:2", + workspace: "./constellation-workspace", }, }, } @@ -225,13 +220,13 @@ func TestParseRecoverFlags(t *testing.T) { require := require.New(t) cmd := NewRecoverCmd() - cmd.Flags().String("config", "", "") // register persistent flag manually - cmd.Flags().Bool("force", false, "") // register persistent flag manually + cmd.Flags().String("workspace", "", "") // register persistent flag manually + cmd.Flags().Bool("force", false, "") // register persistent flag manually require.NoError(cmd.ParseFlags(tc.args)) fileHandler := file.NewHandler(afero.NewMemMapFs()) if tc.writeIDFile { - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, &clusterid.File{IP: "192.0.2.42"})) + require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, &clusterid.File{IP: "192.0.2.42"})) } r := &recoverCmd{log: logger.NewTest(t)} flags, err := r.parseRecoverFlags(cmd, fileHandler) diff --git a/cli/internal/cmd/status.go b/cli/internal/cmd/status.go index 1c00caaa0..9827f428a 100644 --- a/cli/internal/cmd/status.go +++ b/cli/internal/cmd/status.go @@ -52,6 +52,11 @@ func runStatus(cmd *cobra.Command, _ []string) error { kubeClient := kubectl.New() + flags, err := parseStatusFlags(cmd) + if err != nil { + return fmt.Errorf("parsing flags: %w", err) + } + fileHandler := file.NewHandler(afero.NewOsFs()) kubeConfig, err := fileHandler.Read(constants.AdminConfFilename) if err != nil { @@ -74,7 +79,9 @@ func runStatus(cmd *cobra.Command, _ []string) error { } // need helm client to fetch service versions. - helmClient, err := helm.NewUpgradeClient(kubectl.New(), constants.AdminConfFilename, constants.HelmNamespace, log) + // The client used here, doesn't need to know the current workspace. + // It may be refactored in the future for easier usage. + helmClient, err := helm.NewUpgradeClient(kubectl.New(), constants.UpgradeDir, constants.AdminConfFilename, constants.HelmNamespace, log) if err != nil { return fmt.Errorf("setting up helm client: %w", err) } @@ -84,16 +91,8 @@ func runStatus(cmd *cobra.Command, _ []string) error { stableClient := kubernetes.NewStableClient(kubeClient) - configPath, err := cmd.Flags().GetString("config") - if err != nil { - return fmt.Errorf("getting config flag: %w", err) - } - force, err := cmd.Flags().GetBool("force") - if err != nil { - return fmt.Errorf("getting config flag: %w", err) - } fetcher := attestationconfigapi.NewFetcher() - conf, err := config.New(fileHandler, configPath, fetcher, force) + conf, err := config.New(fileHandler, constants.ConfigFilename, fetcher, flags.force) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { cmd.PrintErrln(configValidationErr.LongMessage()) @@ -213,6 +212,26 @@ func targetVersionsString(target kubernetes.TargetVersions) string { return builder.String() } +type statusFlags struct { + workspace string + force bool +} + +func parseStatusFlags(cmd *cobra.Command) (statusFlags, error) { + workspace, err := cmd.Flags().GetString("workspace") + if err != nil { + return statusFlags{}, fmt.Errorf("getting config flag: %w", err) + } + force, err := cmd.Flags().GetBool("force") + if err != nil { + return statusFlags{}, fmt.Errorf("getting config flag: %w", err) + } + return statusFlags{ + workspace: workspace, + force: force, + }, nil +} + type kubeClient interface { GetNodes(ctx context.Context) ([]corev1.Node, error) } diff --git a/cli/internal/cmd/terminate.go b/cli/internal/cmd/terminate.go index ff50c924f..2326e6e1e 100644 --- a/cli/internal/cmd/terminate.go +++ b/cli/internal/cmd/terminate.go @@ -69,7 +69,7 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file. } spinner.Start("Terminating", false) - err = terminator.Terminate(cmd.Context(), flags.logLevel) + err = terminator.Terminate(cmd.Context(), constants.TerraformWorkingDir, flags.logLevel) spinner.Stop() if err != nil { return fmt.Errorf("terminating Constellation cluster: %w", err) @@ -79,19 +79,20 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file. var removeErr error if err := fileHandler.Remove(constants.AdminConfFilename); err != nil && !errors.Is(err, fs.ErrNotExist) { - removeErr = errors.Join(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.AdminConfFilename)) + removeErr = errors.Join(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", adminConfPath(flags.workspace))) } - if err := fileHandler.Remove(constants.ClusterIDsFileName); err != nil && !errors.Is(err, fs.ErrNotExist) { - removeErr = errors.Join(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.ClusterIDsFileName)) + if err := fileHandler.Remove(constants.ClusterIDsFilename); err != nil && !errors.Is(err, fs.ErrNotExist) { + removeErr = errors.Join(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", clusterIDsPath(flags.workspace))) } return removeErr } type terminateFlags struct { - yes bool - logLevel terraform.LogLevel + yes bool + workspace string + logLevel terraform.LogLevel } func parseTerminateFlags(cmd *cobra.Command) (terminateFlags, error) { @@ -107,9 +108,14 @@ func parseTerminateFlags(cmd *cobra.Command) (terminateFlags, error) { if err != nil { return terminateFlags{}, fmt.Errorf("parsing Terraform log level %s: %w", logLevelString, err) } + workspace, err := cmd.Flags().GetString("workspace") + if err != nil { + return terminateFlags{}, fmt.Errorf("parsing workspace string: %w", err) + } return terminateFlags{ - yes: yes, - logLevel: logLevel, + yes: yes, + workspace: workspace, + logLevel: logLevel, }, nil } diff --git a/cli/internal/cmd/terminate_test.go b/cli/internal/cmd/terminate_test.go index 504b98ef8..1f47b1456 100644 --- a/cli/internal/cmd/terminate_test.go +++ b/cli/internal/cmd/terminate_test.go @@ -51,7 +51,7 @@ func TestTerminate(t *testing.T) { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone)) - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone)) + require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptNone)) return fs } someErr := errors.New("failed") @@ -89,7 +89,7 @@ func TestTerminate(t *testing.T) { setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone)) + require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, idFile, file.OptNone)) return fs }, terminator: &stubCloudTerminator{}, @@ -137,6 +137,7 @@ func TestTerminate(t *testing.T) { // register persistent flags manually cmd.Flags().String("tf-log", "NONE", "") + cmd.Flags().String("workspace", "", "") require.NotNil(tc.setupFs) fileHandler := file.NewHandler(tc.setupFs(require, tc.idFile)) @@ -157,7 +158,7 @@ func TestTerminate(t *testing.T) { assert.True(tc.terminator.Called()) _, err = fileHandler.Stat(constants.AdminConfFilename) assert.Error(err) - _, err = fileHandler.Stat(constants.ClusterIDsFileName) + _, err = fileHandler.Stat(constants.ClusterIDsFilename) assert.Error(err) } } diff --git a/cli/internal/cmd/tfmigrationclient.go b/cli/internal/cmd/tfmigrationclient.go index 425552490..5af9d03a3 100644 --- a/cli/internal/cmd/tfmigrationclient.go +++ b/cli/internal/cmd/tfmigrationclient.go @@ -7,7 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only package cmd import ( + "context" "fmt" + "io" "github.com/edgelesssys/constellation/v2/cli/internal/upgrade" "github.com/edgelesssys/constellation/v2/internal/file" @@ -21,7 +23,7 @@ type tfMigrationClient struct { // planMigration checks for Terraform migrations and asks for confirmation if there are any. The user input is returned as confirmedDiff. // adapted from migrateTerraform(). -func (u *tfMigrationClient) planMigration(cmd *cobra.Command, file file.Handler, migrateCmd upgrade.TfMigrationCmd) (hasDiff bool, err error) { +func (u *tfMigrationClient) planMigration(cmd *cobra.Command, file file.Handler, migrateCmd tfMigrationCmd) (hasDiff bool, err error) { u.log.Debugf("Planning %s", migrateCmd.String()) if err := migrateCmd.CheckTerraformMigrations(file); err != nil { return false, fmt.Errorf("checking workspace: %w", err) @@ -35,7 +37,7 @@ func (u *tfMigrationClient) planMigration(cmd *cobra.Command, file file.Handler, // applyMigration plans and then applies the Terraform migration. The user is asked for confirmation if there are any changes. // adapted from migrateTerraform(). -func (u *tfMigrationClient) applyMigration(cmd *cobra.Command, file file.Handler, migrateCmd upgrade.TfMigrationCmd, yesFlag bool) error { +func (u *tfMigrationClient) applyMigration(cmd *cobra.Command, upgradeWorkspace string, file file.Handler, migrateCmd tfMigrationCmd, yesFlag bool) error { hasDiff, err := u.planMigration(cmd, file, migrateCmd) if err != nil { return err @@ -50,7 +52,7 @@ func (u *tfMigrationClient) applyMigration(cmd *cobra.Command, file file.Handler } if !ok { cmd.Println("Aborting upgrade.") - if err := upgrade.CleanUpTerraformMigrations(migrateCmd.UpgradeID(), file); err != nil { + if err := upgrade.CleanUpTerraformMigrations(upgradeWorkspace, migrateCmd.UpgradeID(), file); err != nil { return fmt.Errorf("cleaning up workspace: %w", err) } return fmt.Errorf("aborted by user") @@ -66,3 +68,12 @@ func (u *tfMigrationClient) applyMigration(cmd *cobra.Command, file file.Handler } return nil } + +// tfMigrationCmd is an interface for all terraform upgrade / migration commands. +type tfMigrationCmd interface { + CheckTerraformMigrations(file file.Handler) error + Plan(ctx context.Context, file file.Handler, outWriter io.Writer) (bool, error) + Apply(ctx context.Context, fileHandler file.Handler) error + String() string + UpgradeID() string +} diff --git a/cli/internal/cmd/upgradeapply.go b/cli/internal/cmd/upgradeapply.go index 3177e123c..4ae2bb2e0 100644 --- a/cli/internal/cmd/upgradeapply.go +++ b/cli/internal/cmd/upgradeapply.go @@ -61,7 +61,12 @@ func runUpgradeApply(cmd *cobra.Command, _ []string) error { } defer log.Sync() fileHandler := file.NewHandler(afero.NewOsFs()) - upgrader, err := kubernetes.NewUpgrader(cmd.Context(), cmd.OutOrStdout(), fileHandler, log, kubernetes.UpgradeCmdKindApply) + + upgrader, err := kubernetes.NewUpgrader( + cmd.Context(), cmd.OutOrStdout(), + constants.UpgradeDir, constants.AdminConfFilename, + fileHandler, log, kubernetes.UpgradeCmdKindApply, + ) if err != nil { return err } @@ -86,7 +91,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand return fmt.Errorf("parsing flags: %w", err) } - conf, err := config.New(fileHandler, flags.configPath, u.configFetcher, flags.force) + conf, err := config.New(fileHandler, constants.ConfigFilename, u.configFetcher, flags.force) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { cmd.PrintErrln(configValidationErr.LongMessage()) @@ -113,7 +118,7 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand } var idFile clusterid.File - if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { + if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { return fmt.Errorf("reading cluster ID file: %w", err) } conf.UpdateMAAURL(idFile.AttestationURL) @@ -123,12 +128,12 @@ func (u *upgradeApplyCmd) upgradeApply(cmd *cobra.Command, fileHandler file.Hand return fmt.Errorf("upgrading measurements: %w", err) } // not moving existing Terraform migrator because of planned apply refactor - if err := u.migrateTerraform(cmd, u.imageFetcher, conf, flags); err != nil { + if err := u.migrateTerraform(cmd, u.imageFetcher, conf, fileHandler, flags); 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 := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { + if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { return fmt.Errorf("reading updated cluster ID file: %w", err) } @@ -177,10 +182,12 @@ func getImage(ctx context.Context, conf *config.Config, fetcher imageFetcher) (s // 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. -func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, fetcher imageFetcher, conf *config.Config, flags upgradeApplyFlags) error { +func (u *upgradeApplyCmd) migrateTerraform( + cmd *cobra.Command, fetcher imageFetcher, conf *config.Config, fileHandler file.Handler, flags upgradeApplyFlags, +) error { u.log.Debugf("Planning Terraform migrations") - if err := u.upgrader.CheckTerraformMigrations(); err != nil { + if err := u.upgrader.CheckTerraformMigrations(constants.UpgradeDir); err != nil { return fmt.Errorf("checking workspace: %w", err) } @@ -207,9 +214,11 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, fetcher imageFetc u.log.Debugf("Using Terraform variables:\n%v", vars) opts := upgrade.TerraformUpgradeOptions{ - LogLevel: flags.terraformLogLevel, - CSP: conf.GetProvider(), - Vars: vars, + LogLevel: flags.terraformLogLevel, + CSP: conf.GetProvider(), + Vars: vars, + TFWorkspace: constants.TerraformWorkingDir, + UpgradeWorkspace: constants.UpgradeDir, } // Check if there are any Terraform migrations to apply @@ -228,20 +237,25 @@ func (u *upgradeApplyCmd) migrateTerraform(cmd *cobra.Command, fetcher imageFetc } if !ok { cmd.Println("Aborting upgrade.") - if err := u.upgrader.CleanUpTerraformMigrations(); err != nil { + if err := u.upgrader.CleanUpTerraformMigrations(constants.UpgradeDir); err != nil { return fmt.Errorf("cleaning up workspace: %w", err) } return fmt.Errorf("aborted by user") } } + u.log.Debugf("Applying Terraform migrations") - err := u.upgrader.ApplyTerraformMigrations(cmd.Context(), opts) + newIDFile, err := u.upgrader.ApplyTerraformMigrations(cmd.Context(), opts) if err != nil { return fmt.Errorf("applying terraform migrations: %w", err) } + if err := mergeClusterIDFile(constants.ClusterIDsFilename, newIDFile, fileHandler); err != nil { + return fmt.Errorf("merging cluster ID files: %w", err) + } + cmd.Printf("Terraform migrations applied successfully and output written to: %s\n"+ "A backup of the pre-upgrade state has been written to: %s\n", - constants.ClusterIDsFileName, filepath.Join(constants.UpgradeDir, constants.TerraformUpgradeBackupDir)) + clusterIDsPath(flags.workspace), filepath.Join(opts.UpgradeWorkspace, u.upgrader.GetUpgradeID(), constants.TerraformUpgradeBackupDir)) } else { u.log.Debugf("No Terraform diff detected") } @@ -327,7 +341,7 @@ func (u *upgradeApplyCmd) handleServiceUpgrade(cmd *cobra.Command, conf *config. } func parseUpgradeApplyFlags(cmd *cobra.Command) (upgradeApplyFlags, error) { - configPath, err := cmd.Flags().GetString("config") + workspace, err := cmd.Flags().GetString("workspace") if err != nil { return upgradeApplyFlags{}, err } @@ -357,7 +371,7 @@ func parseUpgradeApplyFlags(cmd *cobra.Command) (upgradeApplyFlags, error) { } return upgradeApplyFlags{ - configPath: configPath, + workspace: workspace, yes: yes, upgradeTimeout: timeout, force: force, @@ -365,8 +379,21 @@ func parseUpgradeApplyFlags(cmd *cobra.Command) (upgradeApplyFlags, error) { }, nil } +func mergeClusterIDFile(clusterIDPath string, newIDFile clusterid.File, fileHandler file.Handler) error { + idFile := &clusterid.File{} + if err := fileHandler.ReadJSON(clusterIDPath, idFile); err != nil { + return fmt.Errorf("reading %s: %w", clusterIDPath, err) + } + + if err := fileHandler.WriteJSON(clusterIDPath, idFile.Merge(newIDFile), file.OptOverwrite); err != nil { + return fmt.Errorf("writing %s: %w", clusterIDPath, err) + } + + return nil +} + type upgradeApplyFlags struct { - configPath string + workspace string yes bool upgradeTimeout time.Duration force bool @@ -380,8 +407,9 @@ type cloudUpgrader interface { ExtendClusterConfigCertSANs(ctx context.Context, alternativeNames []string) error GetClusterAttestationConfig(ctx context.Context, variant variant.Variant) (config.AttestationCfg, *corev1.ConfigMap, error) PlanTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) (bool, error) - ApplyTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) error - CheckTerraformMigrations() error - CleanUpTerraformMigrations() error + ApplyTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) (clusterid.File, error) + CheckTerraformMigrations(upgradeWorkspace string) error + CleanUpTerraformMigrations(upgradeWorkspace string) error AddManualStateMigration(migration terraform.StateMigration) + GetUpgradeID() string } diff --git a/cli/internal/cmd/upgradeapply_test.go b/cli/internal/cmd/upgradeapply_test.go index bc269cee5..c7e9a1bff 100644 --- a/cli/internal/cmd/upgradeapply_test.go +++ b/cli/internal/cmd/upgradeapply_test.go @@ -129,9 +129,9 @@ func TestUpgradeApply(t *testing.T) { require := require.New(t) cmd := newUpgradeApplyCmd() cmd.SetIn(bytes.NewBufferString(tc.stdin)) - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("force", true, "") // register persistent flag manually - cmd.Flags().String("tf-log", "DEBUG", "") // register persistent flag manually + cmd.Flags().String("workspace", "", "") // register persistent flag manually + cmd.Flags().Bool("force", true, "") // register persistent flag manually + cmd.Flags().String("tf-log", "DEBUG", "") // register persistent flag manually if tc.yesFlag { err := cmd.Flags().Set("yes", "true") @@ -141,7 +141,7 @@ func TestUpgradeApply(t *testing.T) { handler := file.NewHandler(afero.NewMemMapFs()) cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.Azure) require.NoError(handler.WriteYAML(constants.ConfigFilename, cfg)) - require.NoError(handler.WriteJSON(constants.ClusterIDsFileName, clusterid.File{})) + require.NoError(handler.WriteJSON(constants.ClusterIDsFilename, clusterid.File{})) upgrader := upgradeApplyCmd{upgrader: tc.upgrader, log: logger.NewTest(t), imageFetcher: tc.fetcher, configFetcher: stubAttestationFetcher{}} @@ -186,11 +186,11 @@ func (u stubUpgrader) GetClusterAttestationConfig(_ context.Context, _ variant.V return u.currentConfig, &corev1.ConfigMap{}, nil } -func (u stubUpgrader) CheckTerraformMigrations() error { +func (u stubUpgrader) CheckTerraformMigrations(_ string) error { return u.checkTerraformErr } -func (u stubUpgrader) CleanUpTerraformMigrations() error { +func (u stubUpgrader) CleanUpTerraformMigrations(_ string) error { return u.cleanTerraformErr } @@ -198,8 +198,8 @@ func (u stubUpgrader) PlanTerraformMigrations(context.Context, upgrade.Terraform return u.terraformDiff, u.planTerraformErr } -func (u stubUpgrader) ApplyTerraformMigrations(context.Context, upgrade.TerraformUpgradeOptions) error { - return u.applyTerraformErr +func (u stubUpgrader) ApplyTerraformMigrations(context.Context, upgrade.TerraformUpgradeOptions) (clusterid.File, error) { + return clusterid.File{}, u.applyTerraformErr } func (u stubUpgrader) ExtendClusterConfigCertSANs(_ context.Context, _ []string) error { diff --git a/cli/internal/cmd/upgradecheck.go b/cli/internal/cmd/upgradecheck.go index 45851c6a5..2169485af 100644 --- a/cli/internal/cmd/upgradecheck.go +++ b/cli/internal/cmd/upgradecheck.go @@ -65,15 +65,22 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error { return fmt.Errorf("creating logger: %w", err) } defer log.Sync() + flags, err := parseUpgradeCheckFlags(cmd) if err != nil { return err } + fileHandler := file.NewHandler(afero.NewOsFs()) - checker, err := kubernetes.NewUpgrader(cmd.Context(), cmd.OutOrStdout(), fileHandler, log, kubernetes.UpgradeCmdKindCheck) + checker, err := kubernetes.NewUpgrader( + cmd.Context(), cmd.OutOrStdout(), + constants.UpgradeDir, constants.AdminConfFilename, + fileHandler, log, kubernetes.UpgradeCmdKindCheck, + ) if err != nil { return fmt.Errorf("setting up Kubernetes upgrader: %w", err) } + versionfetcher := versionsapi.NewFetcher() rekor, err := sigstore.NewRekor() if err != nil { @@ -102,10 +109,6 @@ func runUpgradeCheck(cmd *cobra.Command, _ []string) error { } func parseUpgradeCheckFlags(cmd *cobra.Command) (upgradeCheckFlags, error) { - configPath, err := cmd.Flags().GetString("config") - if err != nil { - return upgradeCheckFlags{}, fmt.Errorf("parsing config string: %w", err) - } force, err := cmd.Flags().GetBool("force") if err != nil { return upgradeCheckFlags{}, fmt.Errorf("parsing force bool: %w", err) @@ -133,7 +136,6 @@ func parseUpgradeCheckFlags(cmd *cobra.Command) (upgradeCheckFlags, error) { } return upgradeCheckFlags{ - configPath: configPath, force: force, updateConfig: updateConfig, ref: ref, @@ -152,7 +154,7 @@ type upgradeCheckCmd struct { // upgradePlan plans an upgrade of a Constellation cluster. func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Handler, fetcher attestationconfigapi.Fetcher, flags upgradeCheckFlags) error { - conf, err := config.New(fileHandler, flags.configPath, fetcher, flags.force) + conf, err := config.New(fileHandler, constants.ConfigFilename, fetcher, flags.force) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { cmd.PrintErrln(configValidationErr.LongMessage()) @@ -160,7 +162,6 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand if err != nil { return err } - u.log.Debugf("Read configuration from %q", flags.configPath) if !u.canUpgradeCheck { cmd.PrintErrln("Planning Constellation upgrades automatically is not supported in the OSS build of the Constellation CLI. Consult the documentation for instructions on where to download the enterprise version.") @@ -205,7 +206,7 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand } u.log.Debugf("Planning Terraform migrations") - if err := u.checker.CheckTerraformMigrations(); err != nil { + if err := u.checker.CheckTerraformMigrations(constants.UpgradeDir); err != nil { // Why is this run twice????? return fmt.Errorf("checking workspace: %w", err) } @@ -220,7 +221,7 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand u.checker.AddManualStateMigration(migration) } - if err := u.checker.CheckTerraformMigrations(); err != nil { + if err := u.checker.CheckTerraformMigrations(constants.UpgradeDir); err != nil { // Why is this run twice????? return fmt.Errorf("checking workspace: %w", err) } @@ -236,9 +237,11 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand u.log.Debugf("Using Terraform variables:\n%v", vars) opts := upgrade.TerraformUpgradeOptions{ - LogLevel: flags.terraformLogLevel, - CSP: conf.GetProvider(), - Vars: vars, + LogLevel: flags.terraformLogLevel, + CSP: conf.GetProvider(), + Vars: vars, + TFWorkspace: constants.TerraformWorkingDir, + UpgradeWorkspace: constants.UpgradeDir, } cmd.Println("The following Terraform migrations are available with this CLI:") @@ -249,7 +252,7 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand return fmt.Errorf("planning terraform migrations: %w", err) } defer func() { - if err := u.checker.CleanUpTerraformMigrations(); err != nil { + if err := u.checker.CleanUpTerraformMigrations(constants.UpgradeDir); err != nil { u.log.Debugf("Failed to clean up Terraform migrations: %v", err) } }() @@ -278,7 +281,7 @@ func (u *upgradeCheckCmd) upgradeCheck(cmd *cobra.Command, fileHandler file.Hand cmd.Print(updateMsg) if flags.updateConfig { - if err := upgrade.writeConfig(conf, fileHandler, flags.configPath); err != nil { + if err := upgrade.writeConfig(conf, fileHandler, constants.ConfigFilename); err != nil { return fmt.Errorf("writing config: %w", err) } cmd.Println("Config updated successfully.") @@ -383,7 +386,7 @@ type currentVersionInfo struct { } func (v *versionCollector) currentVersions(ctx context.Context) (currentVersionInfo, error) { - helmClient, err := helm.NewUpgradeClient(kubectl.New(), constants.AdminConfFilename, constants.HelmNamespace, v.log) + helmClient, err := helm.NewUpgradeClient(kubectl.New(), constants.UpgradeDir, constants.AdminConfFilename, constants.HelmNamespace, v.log) if err != nil { return currentVersionInfo{}, fmt.Errorf("setting up helm client: %w", err) } @@ -749,7 +752,6 @@ func (v *versionCollector) filterCompatibleCLIVersions(ctx context.Context, cliP } type upgradeCheckFlags struct { - configPath string force bool updateConfig bool ref string @@ -761,8 +763,8 @@ type upgradeChecker interface { CurrentImage(ctx context.Context) (string, error) CurrentKubernetesVersion(ctx context.Context) (string, error) PlanTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) (bool, error) - CheckTerraformMigrations() error - CleanUpTerraformMigrations() error + CheckTerraformMigrations(upgradeWorkspace string) error + CleanUpTerraformMigrations(upgradeWorkspace string) error AddManualStateMigration(migration terraform.StateMigration) } diff --git a/cli/internal/cmd/upgradecheck_test.go b/cli/internal/cmd/upgradecheck_test.go index b87b1d119..d7d711aee 100644 --- a/cli/internal/cmd/upgradecheck_test.go +++ b/cli/internal/cmd/upgradecheck_test.go @@ -215,7 +215,6 @@ func TestUpgradeCheck(t *testing.T) { testCases := map[string]struct { collector stubVersionCollector - flags upgradeCheckFlags csp cloudprovider.Provider checker stubUpgradeChecker imagefetcher stubImageFetcher @@ -226,11 +225,8 @@ func TestUpgradeCheck(t *testing.T) { collector: collector, checker: stubUpgradeChecker{}, imagefetcher: stubImageFetcher{}, - flags: upgradeCheckFlags{ - configPath: constants.ConfigFilename, - }, - csp: cloudprovider.GCP, - cliVersion: "v1.0.0", + csp: cloudprovider.GCP, + cliVersion: "v1.0.0", }, "terraform err": { collector: collector, @@ -238,12 +234,9 @@ func TestUpgradeCheck(t *testing.T) { err: assert.AnError, }, imagefetcher: stubImageFetcher{}, - flags: upgradeCheckFlags{ - configPath: constants.ConfigFilename, - }, - csp: cloudprovider.GCP, - cliVersion: "v1.0.0", - wantError: true, + csp: cloudprovider.GCP, + cliVersion: "v1.0.0", + wantError: true, }, } @@ -253,7 +246,7 @@ func TestUpgradeCheck(t *testing.T) { fileHandler := file.NewHandler(afero.NewMemMapFs()) cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), tc.csp) - require.NoError(fileHandler.WriteYAML(tc.flags.configPath, cfg)) + require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg)) checkCmd := upgradeCheckCmd{ canUpgradeCheck: true, @@ -265,7 +258,7 @@ func TestUpgradeCheck(t *testing.T) { cmd := newUpgradeCheckCmd() - err := checkCmd.upgradeCheck(cmd, fileHandler, stubAttestationFetcher{}, tc.flags) + err := checkCmd.upgradeCheck(cmd, fileHandler, stubAttestationFetcher{}, upgradeCheckFlags{}) if tc.wantError { assert.Error(err) return @@ -348,11 +341,11 @@ func (u stubUpgradeChecker) PlanTerraformMigrations(context.Context, upgrade.Ter return u.tfDiff, u.err } -func (u stubUpgradeChecker) CheckTerraformMigrations() error { +func (u stubUpgradeChecker) CheckTerraformMigrations(_ string) error { return u.err } -func (u stubUpgradeChecker) CleanUpTerraformMigrations() error { +func (u stubUpgradeChecker) CleanUpTerraformMigrations(_ string) error { return u.err } diff --git a/cli/internal/cmd/verify.go b/cli/internal/cmd/verify.go index 86f22d572..2c3d00780 100644 --- a/cli/internal/cmd/verify.go +++ b/cli/internal/cmd/verify.go @@ -43,7 +43,7 @@ func NewVerifyCmd() *cobra.Command { Use: "verify", Short: "Verify the confidential properties of a Constellation cluster", Long: "Verify the confidential properties of a Constellation cluster.\n" + - "If arguments aren't specified, values are read from `" + constants.ClusterIDsFileName + "`.", + "If arguments aren't specified, values are read from `" + constants.ClusterIDsFilename + "`.", Args: cobra.ExactArgs(0), RunE: runVerify, } @@ -85,8 +85,8 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC } c.log.Debugf("Using flags: %+v", flags) - c.log.Debugf("Loading configuration file from %q", flags.configPath) - conf, err := config.New(fileHandler, flags.configPath, configFetcher, flags.force) + c.log.Debugf("Loading configuration file from %q", configPath(flags.workspace)) + conf, err := config.New(fileHandler, constants.ConfigFilename, configFetcher, flags.force) var configValidationErr *config.ValidationError if errors.As(err, &configValidationErr) { cmd.PrintErrln(configValidationErr.LongMessage()) @@ -138,11 +138,11 @@ func (c *verifyCmd) verify(cmd *cobra.Command, fileHandler file.Handler, verifyC } func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handler) (verifyFlags, error) { - configPath, err := cmd.Flags().GetString("config") + workspace, err := cmd.Flags().GetString("workspace") if err != nil { return verifyFlags{}, fmt.Errorf("parsing config path argument: %w", err) } - c.log.Debugf("Flag 'config' set to %q", configPath) + c.log.Debugf("Flag 'workspace' set to %q", workspace) ownerID := "" clusterID, err := cmd.Flags().GetString("cluster-id") @@ -170,7 +170,7 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle c.log.Debugf("Flag 'raw' set to %t", force) var idFile clusterid.File - if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) { + if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil && !errors.Is(err, afero.ErrFileNotFound) { return verifyFlags{}, fmt.Errorf("reading cluster ID file: %w", err) } @@ -178,13 +178,13 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle emptyEndpoint := endpoint == "" emptyIDs := ownerID == "" && clusterID == "" if emptyEndpoint || emptyIDs { - c.log.Debugf("Trying to supplement empty flag values from %q", constants.ClusterIDsFileName) + c.log.Debugf("Trying to supplement empty flag values from %q", clusterIDsPath(workspace)) if emptyEndpoint { - cmd.Printf("Using endpoint from %q. Specify --node-endpoint to override this.\n", constants.ClusterIDsFileName) + cmd.Printf("Using endpoint from %q. Specify --node-endpoint to override this.\n", clusterIDsPath(workspace)) endpoint = idFile.IP } if emptyIDs { - cmd.Printf("Using ID from %q. Specify --cluster-id to override this.\n", constants.ClusterIDsFileName) + cmd.Printf("Using ID from %q. Specify --cluster-id to override this.\n", clusterIDsPath(workspace)) ownerID = idFile.OwnerID clusterID = idFile.ClusterID } @@ -200,24 +200,24 @@ func (c *verifyCmd) parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handle } return verifyFlags{ - endpoint: endpoint, - configPath: configPath, - ownerID: ownerID, - clusterID: clusterID, - maaURL: idFile.AttestationURL, - rawOutput: raw, - force: force, + endpoint: endpoint, + workspace: workspace, + ownerID: ownerID, + clusterID: clusterID, + maaURL: idFile.AttestationURL, + rawOutput: raw, + force: force, }, nil } type verifyFlags struct { - endpoint string - ownerID string - clusterID string - configPath string - maaURL string - rawOutput bool - force bool + endpoint string + ownerID string + clusterID string + workspace string + maaURL string + rawOutput bool + force bool } func addPortIfMissing(endpoint string, defaultPort int) (string, error) { diff --git a/cli/internal/cmd/verify_test.go b/cli/internal/cmd/verify_test.go index 7a0dfbd65..b351e74eb 100644 --- a/cli/internal/cmd/verify_test.go +++ b/cli/internal/cmd/verify_test.go @@ -42,15 +42,15 @@ func TestVerify(t *testing.T) { someErr := errors.New("failed") testCases := map[string]struct { - provider cloudprovider.Provider - protoClient *stubVerifyClient - formatter *stubAttDocFormatter - nodeEndpointFlag string - configFlag string - clusterIDFlag string - idFile *clusterid.File - wantEndpoint string - wantErr bool + provider cloudprovider.Provider + protoClient *stubVerifyClient + formatter *stubAttDocFormatter + nodeEndpointFlag string + clusterIDFlag string + idFile *clusterid.File + wantEndpoint string + skipConfigCreation bool + wantErr bool }{ "gcp": { provider: cloudprovider.GCP, @@ -123,12 +123,12 @@ func TestVerify(t *testing.T) { formatter: &stubAttDocFormatter{}, }, "config file not existing": { - provider: cloudprovider.GCP, - clusterIDFlag: zeroBase64, - nodeEndpointFlag: "192.0.2.1:1234", - configFlag: "./file", - formatter: &stubAttDocFormatter{}, - wantErr: true, + provider: cloudprovider.GCP, + clusterIDFlag: zeroBase64, + nodeEndpointFlag: "192.0.2.1:1234", + formatter: &stubAttDocFormatter{}, + skipConfigCreation: true, + wantErr: true, }, "error protoClient GetState": { provider: cloudprovider.Azure, @@ -163,14 +163,11 @@ func TestVerify(t *testing.T) { require := require.New(t) cmd := NewVerifyCmd() - cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - cmd.Flags().Bool("force", true, "") // register persistent flag manually + cmd.Flags().String("workspace", "", "") // register persistent flag manually + cmd.Flags().Bool("force", true, "") // register persistent flag manually out := &bytes.Buffer{} cmd.SetOut(out) cmd.SetErr(&bytes.Buffer{}) - if tc.configFlag != "" { - require.NoError(cmd.Flags().Set("config", tc.configFlag)) - } if tc.clusterIDFlag != "" { require.NoError(cmd.Flags().Set("cluster-id", tc.clusterIDFlag)) } @@ -179,10 +176,12 @@ func TestVerify(t *testing.T) { } fileHandler := file.NewHandler(afero.NewMemMapFs()) - cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), tc.provider) - require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg)) + if !tc.skipConfigCreation { + cfg := defaultConfigWithExpectedMeasurements(t, config.Default(), tc.provider) + require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, cfg)) + } if tc.idFile != nil { - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.idFile, file.OptNone)) + require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFilename, tc.idFile, file.OptNone)) } v := &verifyCmd{log: logger.NewTest(t)} diff --git a/cli/internal/cmd/workspace.go b/cli/internal/cmd/workspace.go new file mode 100644 index 000000000..5bd187270 --- /dev/null +++ b/cli/internal/cmd/workspace.go @@ -0,0 +1,54 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package cmd + +import ( + "path/filepath" + + "github.com/edgelesssys/constellation/v2/internal/constants" +) + +// Users may override the default workspace using the --workspace flag. +// The default workspace is the current working directory. +// The following functions return paths relative to the set workspace, +// and should be used when printing the path to the user. +// The MUST not be used when accessing files, as the workspace is changed +// using os.Chdir() before the command is executed. + +func adminConfPath(workspace string) string { + return filepath.Join(workspace, constants.AdminConfFilename) +} + +func configPath(workspace string) string { + return filepath.Join(workspace, constants.ConfigFilename) +} + +func clusterIDsPath(workspace string) string { + return filepath.Join(workspace, constants.ClusterIDsFilename) +} + +func masterSecretPath(workspace string) string { + return filepath.Join(workspace, constants.MasterSecretFilename) +} + +func terraformClusterWorkspace(workspace string) string { + return filepath.Join(workspace, constants.TerraformWorkingDir) +} + +func terraformIAMWorkspace(workspace string) string { + return filepath.Join(workspace, constants.TerraformIAMWorkingDir) +} + +func terraformLogPath(workspace string) string { + return filepath.Join(workspace, constants.TerraformLogFile) +} + +const gcpServiceAccountKeyFile = "gcpServiceAccountKey.json" + +func gcpServiceAccountKeyPath(workspace string) string { + return filepath.Join(workspace, gcpServiceAccountKeyFile) +} diff --git a/cli/internal/helm/backup.go b/cli/internal/helm/backup.go index aff984ad9..5954522de 100644 --- a/cli/internal/helm/backup.go +++ b/cli/internal/helm/backup.go @@ -11,7 +11,6 @@ import ( "fmt" "path/filepath" - "github.com/edgelesssys/constellation/v2/internal/constants" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" @@ -100,7 +99,7 @@ func (c *UpgradeClient) backupCRs(ctx context.Context, crds []apiextensionsv1.Cu } func (c *UpgradeClient) backupFolder(upgradeID string) string { - return filepath.Join(constants.UpgradeDir, upgradeID, "backups") + string(filepath.Separator) + return filepath.Join(c.upgradeWorkspace, upgradeID, "backups") + string(filepath.Separator) } func (c *UpgradeClient) crdBackupFolder(upgradeID string) string { diff --git a/cli/internal/helm/init.go b/cli/internal/helm/init.go index 144c63855..de7ece5ae 100644 --- a/cli/internal/helm/init.go +++ b/cli/internal/helm/init.go @@ -13,32 +13,27 @@ import ( "github.com/edgelesssys/constellation/v2/internal/constants" ) -// Initializer installs all Helm charts required for a constellation cluster. -type Initializer interface { - Install(ctx context.Context, releases *Releases) error -} - -type initializationClient struct { +// InitializationClient installs all Helm charts required for a Constellation cluster. +type InitializationClient struct { log debugLog installer installer } // NewInitializer creates a new client to install all Helm charts required for a constellation cluster. -func NewInitializer(log debugLog) (Initializer, error) { - installer, err := NewInstaller(constants.AdminConfFilename, log) +func NewInitializer(log debugLog, adminConfPath string) (*InitializationClient, error) { + installer, err := NewInstaller(adminConfPath, log) if err != nil { return nil, fmt.Errorf("creating Helm installer: %w", err) } - return &initializationClient{log: log, installer: installer}, nil + return &InitializationClient{log: log, installer: installer}, nil } // Install installs all Helm charts required for a constellation cluster. -func (h initializationClient) Install(ctx context.Context, releases *Releases, -) error { - if err := h.installer.InstallChart(ctx, releases.Cilium); err != nil { +func (i InitializationClient) Install(ctx context.Context, releases *Releases) error { + if err := i.installer.InstallChart(ctx, releases.Cilium); err != nil { return fmt.Errorf("installing Cilium: %w", err) } - h.log.Debugf("Waiting for Cilium to become ready") + i.log.Debugf("Waiting for Cilium to become ready") helper, err := newK8sCiliumHelper(constants.AdminConfFilename) if err != nil { return fmt.Errorf("creating Kubernetes client: %w", err) @@ -46,43 +41,43 @@ func (h initializationClient) Install(ctx context.Context, releases *Releases, timeToStartWaiting := time.Now() // TODO(3u13r): Reduce the timeout when we switched the package repository - this is only this high because we once // saw polling times of ~16 minutes when hitting a slow PoP from Fastly (GitHub's / ghcr.io CDN). - if err := helper.WaitForDS(ctx, "kube-system", "cilium", h.log); err != nil { + if err := helper.WaitForDS(ctx, "kube-system", "cilium", i.log); err != nil { return fmt.Errorf("waiting for Cilium to become healthy: %w", err) } timeUntilFinishedWaiting := time.Since(timeToStartWaiting) - h.log.Debugf("Cilium became healthy after %s", timeUntilFinishedWaiting.String()) + i.log.Debugf("Cilium became healthy after %s", timeUntilFinishedWaiting.String()) - h.log.Debugf("Fix Cilium through restart") + i.log.Debugf("Fix Cilium through restart") if err := helper.RestartDS("kube-system", "cilium"); err != nil { return fmt.Errorf("restarting Cilium: %w", err) } - h.log.Debugf("Installing microservices") - if err := h.installer.InstallChart(ctx, releases.ConstellationServices); err != nil { + i.log.Debugf("Installing microservices") + if err := i.installer.InstallChart(ctx, releases.ConstellationServices); err != nil { return fmt.Errorf("installing microservices: %w", err) } - h.log.Debugf("Installing cert-manager") - if err := h.installer.InstallChart(ctx, releases.CertManager); err != nil { + i.log.Debugf("Installing cert-manager") + if err := i.installer.InstallChart(ctx, releases.CertManager); err != nil { return fmt.Errorf("installing cert-manager: %w", err) } if releases.CSI != nil { - h.log.Debugf("Installing CSI deployments") - if err := h.installer.InstallChart(ctx, *releases.CSI); err != nil { + i.log.Debugf("Installing CSI deployments") + if err := i.installer.InstallChart(ctx, *releases.CSI); err != nil { return fmt.Errorf("installing CSI snapshot CRDs: %w", err) } } if releases.AWSLoadBalancerController != nil { - h.log.Debugf("Installing AWS Load Balancer Controller") - if err := h.installer.InstallChart(ctx, *releases.AWSLoadBalancerController); err != nil { + i.log.Debugf("Installing AWS Load Balancer Controller") + if err := i.installer.InstallChart(ctx, *releases.AWSLoadBalancerController); err != nil { return fmt.Errorf("installing AWS Load Balancer Controller: %w", err) } } - h.log.Debugf("Installing constellation operators") - if err := h.installer.InstallChart(ctx, releases.ConstellationOperators); err != nil { + i.log.Debugf("Installing constellation operators") + if err := i.installer.InstallChart(ctx, releases.ConstellationOperators); err != nil { return fmt.Errorf("installing constellation operators: %w", err) } return nil diff --git a/cli/internal/helm/install.go b/cli/internal/helm/install.go index 02851e339..2d6aa9c53 100644 --- a/cli/internal/helm/install.go +++ b/cli/internal/helm/install.go @@ -143,7 +143,7 @@ type installDoer struct { func (i installDoer) Do(ctx context.Context) error { i.log.Debugf("Trying to install Helm chart %s", i.chart.Name()) if _, err := i.Installer.RunWithContext(ctx, i.chart, i.values); err != nil { - i.log.Debugf("Helm chart installation % failed: %v", i.chart.Name(), err) + i.log.Debugf("Helm chart installation %s failed: %v", i.chart.Name(), err) return err } diff --git a/cli/internal/helm/upgrade.go b/cli/internal/helm/upgrade.go index 88d679722..455c584d0 100644 --- a/cli/internal/helm/upgrade.go +++ b/cli/internal/helm/upgrade.go @@ -46,17 +46,18 @@ var errReleaseNotFound = errors.New("release not found") // UpgradeClient handles interaction with helm and the cluster. type UpgradeClient struct { - config *action.Configuration - kubectl crdClient - fs file.Handler - actions actionWrapper - log debugLog + config *action.Configuration + kubectl crdClient + fs file.Handler + actions actionWrapper + upgradeWorkspace string + log debugLog } -// NewUpgradeClient returns a new initializes upgrade client for the given namespace. -func NewUpgradeClient(client crdClient, kubeConfigPath, helmNamespace string, log debugLog) (*UpgradeClient, error) { +// NewUpgradeClient returns a newly initialized UpgradeClient for the given namespace. +func NewUpgradeClient(client crdClient, upgradeWorkspace, kubeConfigPath, helmNamespace string, log debugLog) (*UpgradeClient, error) { settings := cli.New() - settings.KubeConfig = kubeConfigPath // constants.AdminConfFilename + settings.KubeConfig = kubeConfigPath actionConfig := &action.Configuration{} if err := actionConfig.Init(settings.RESTClientGetter(), helmNamespace, "secret", log.Debugf); err != nil { @@ -74,7 +75,13 @@ func NewUpgradeClient(client crdClient, kubeConfigPath, helmNamespace string, lo return nil, fmt.Errorf("initializing kubectl: %w", err) } - return &UpgradeClient{kubectl: client, fs: fileHandler, actions: actions{config: actionConfig}, log: log}, nil + return &UpgradeClient{ + kubectl: client, + fs: fileHandler, + actions: actions{config: actionConfig}, + upgradeWorkspace: upgradeWorkspace, + log: log, + }, nil } func (c *UpgradeClient) shouldUpgrade(releaseName string, newVersion semver.Semver, force bool) error { diff --git a/cli/internal/kubernetes/upgrade.go b/cli/internal/kubernetes/upgrade.go index c50ac9e4e..932aaedf0 100644 --- a/cli/internal/kubernetes/upgrade.go +++ b/cli/internal/kubernetes/upgrade.go @@ -102,7 +102,10 @@ type Upgrader struct { } // NewUpgrader returns a new Upgrader. -func NewUpgrader(ctx context.Context, outWriter io.Writer, fileHandler file.Handler, log debugLog, upgradeCmdKind UpgradeCmdKind) (*Upgrader, error) { +func NewUpgrader( + ctx context.Context, outWriter io.Writer, upgradeWorkspace, kubeConfigPath string, + fileHandler file.Handler, log debugLog, upgradeCmdKind UpgradeCmdKind, +) (*Upgrader, error) { upgradeID := "upgrade-" + time.Now().Format("20060102150405") + "-" + strings.Split(uuid.New().String(), "-")[0] if upgradeCmdKind == UpgradeCmdKindCheck { // When performing an upgrade check, the upgrade directory will only be used temporarily to store the @@ -118,7 +121,7 @@ func NewUpgrader(ctx context.Context, outWriter io.Writer, fileHandler file.Hand upgradeID: upgradeID, } - kubeConfig, err := clientcmd.BuildConfigFromFlags("", constants.AdminConfFilename) + kubeConfig, err := clientcmd.BuildConfigFromFlags("", kubeConfigPath) if err != nil { return nil, fmt.Errorf("building kubernetes config: %w", err) } @@ -136,13 +139,13 @@ func NewUpgrader(ctx context.Context, outWriter io.Writer, fileHandler file.Hand } u.dynamicInterface = &NodeVersionClient{client: unstructuredClient} - helmClient, err := helm.NewUpgradeClient(kubectl.New(), constants.AdminConfFilename, constants.HelmNamespace, log) + helmClient, err := helm.NewUpgradeClient(kubectl.New(), upgradeWorkspace, kubeConfigPath, constants.HelmNamespace, log) if err != nil { return nil, fmt.Errorf("setting up helm client: %w", err) } u.helmClient = helmClient - tfClient, err := terraform.New(ctx, filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformUpgradeWorkingDir)) + tfClient, err := terraform.New(ctx, filepath.Join(upgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir)) if err != nil { return nil, fmt.Errorf("setting up terraform client: %w", err) } @@ -170,14 +173,14 @@ func (u *Upgrader) AddManualStateMigration(migration terraform.StateMigration) { // CheckTerraformMigrations checks whether Terraform migrations are possible in the current workspace. // If the files that will be written during the upgrade already exist, it returns an error. -func (u *Upgrader) CheckTerraformMigrations() error { - return u.tfUpgrader.CheckTerraformMigrations(u.upgradeID, constants.TerraformUpgradeBackupDir) +func (u *Upgrader) CheckTerraformMigrations(upgradeWorkspace string) error { + return u.tfUpgrader.CheckTerraformMigrations(upgradeWorkspace, u.upgradeID, constants.TerraformUpgradeBackupDir) } // CleanUpTerraformMigrations cleans up the Terraform migration workspace, for example when an upgrade is // aborted by the user. -func (u *Upgrader) CleanUpTerraformMigrations() error { - return u.tfUpgrader.CleanUpTerraformMigrations(u.upgradeID) +func (u *Upgrader) CleanUpTerraformMigrations(upgradeWorkspace string) error { + return u.tfUpgrader.CleanUpTerraformMigrations(upgradeWorkspace, u.upgradeID) } // PlanTerraformMigrations prepares the upgrade workspace and plans the Terraform migrations for the Constellation upgrade. @@ -191,7 +194,7 @@ func (u *Upgrader) PlanTerraformMigrations(ctx context.Context, opts upgrade.Ter // If PlanTerraformMigrations has not been executed before, it will return an error. // In case of a successful upgrade, the output will be written to the specified file and the old Terraform directory is replaced // By the new one. -func (u *Upgrader) ApplyTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) error { +func (u *Upgrader) ApplyTerraformMigrations(ctx context.Context, opts upgrade.TerraformUpgradeOptions) (clusterid.File, error) { return u.tfUpgrader.ApplyTerraformMigrations(ctx, opts, u.upgradeID) } diff --git a/cli/internal/terraform/loader_test.go b/cli/internal/terraform/loader_test.go index 0f5cf0d3f..116f115d6 100644 --- a/cli/internal/terraform/loader_test.go +++ b/cli/internal/terraform/loader_test.go @@ -14,7 +14,6 @@ import ( "testing" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" "github.com/spf13/afero" "github.com/stretchr/testify/assert" @@ -104,25 +103,26 @@ func TestPrepareWorkspace(t *testing.T) { require := require.New(t) file := file.NewHandler(afero.NewMemMapFs()) + testWorkspace := "unittest" path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String())) - err := prepareWorkspace(path, file, constants.TerraformWorkingDir) + err := prepareWorkspace(path, file, testWorkspace) require.NoError(err) - checkFiles(t, file, func(err error) { assert.NoError(err) }, constants.TerraformWorkingDir, tc.fileList) + checkFiles(t, file, func(err error) { assert.NoError(err) }, testWorkspace, tc.fileList) if tc.testAlreadyUnpacked { // Let's try the same again and check if we don't get a "file already exists" error. - require.NoError(file.Remove(filepath.Join(constants.TerraformWorkingDir, "variables.tf"))) - err := prepareWorkspace(path, file, constants.TerraformWorkingDir) + require.NoError(file.Remove(filepath.Join(testWorkspace, "variables.tf"))) + err := prepareWorkspace(path, file, testWorkspace) assert.NoError(err) - checkFiles(t, file, func(err error) { assert.NoError(err) }, constants.TerraformWorkingDir, tc.fileList) + checkFiles(t, file, func(err error) { assert.NoError(err) }, testWorkspace, tc.fileList) } - err = cleanUpWorkspace(file, constants.TerraformWorkingDir) + err = cleanUpWorkspace(file, testWorkspace) require.NoError(err) - checkFiles(t, file, func(err error) { assert.ErrorIs(err, fs.ErrNotExist) }, constants.TerraformWorkingDir, tc.fileList) + checkFiles(t, file, func(err error) { assert.ErrorIs(err, fs.ErrNotExist) }, testWorkspace, tc.fileList) }) } } diff --git a/cli/internal/terraform/terraform.go b/cli/internal/terraform/terraform.go index f9a48aeec..2973d1cc1 100644 --- a/cli/internal/terraform/terraform.go +++ b/cli/internal/terraform/terraform.go @@ -38,6 +38,9 @@ import ( const ( tfVersion = ">= 1.4.6" terraformVarsFile = "terraform.tfvars" + + // terraformUpgradePlanFile is the file name of the zipfile created by Terraform plan for Constellation upgrades. + terraformUpgradePlanFile = "plan.zip" ) // ErrTerraformWorkspaceExistsWithDifferentVariables is returned when existing Terraform files differ from the version the CLI wants to extract. @@ -438,9 +441,10 @@ func (c *Client) ApplyIAMConfig(ctx context.Context, provider cloudprovider.Prov return c.ShowIAM(ctx, provider) } -// Plan determines the diff that will be applied by Terraform. The plan output is written to the planFile. +// Plan determines the diff that will be applied by Terraform. +// The plan output is written to the Terraform working directory. // If there is a diff, the returned bool is true. Otherwise, it is false. -func (c *Client) Plan(ctx context.Context, logLevel LogLevel, planFile string) (bool, error) { +func (c *Client) Plan(ctx context.Context, logLevel LogLevel) (bool, error) { if err := c.setLogLevel(logLevel); err != nil { return false, fmt.Errorf("set terraform log level %s: %w", logLevel.String(), err) } @@ -454,18 +458,19 @@ func (c *Client) Plan(ctx context.Context, logLevel LogLevel, planFile string) ( } opts := []tfexec.PlanOption{ - tfexec.Out(planFile), + tfexec.Out(terraformUpgradePlanFile), } return c.tf.Plan(ctx, opts...) } -// ShowPlan formats the diff in planFilePath and writes it to the specified output. -func (c *Client) ShowPlan(ctx context.Context, logLevel LogLevel, planFilePath string, output io.Writer) error { +// ShowPlan formats the diff of a plan file in the Terraform working directory, +// and writes it to the specified output. +func (c *Client) ShowPlan(ctx context.Context, logLevel LogLevel, output io.Writer) error { if err := c.setLogLevel(logLevel); err != nil { return fmt.Errorf("set terraform log level %s: %w", logLevel.String(), err) } - planResult, err := c.tf.ShowPlanFileRaw(ctx, planFilePath) + planResult, err := c.tf.ShowPlanFileRaw(ctx, terraformUpgradePlanFile) if err != nil { return fmt.Errorf("terraform show plan: %w", err) } @@ -575,6 +580,9 @@ func (c *Client) setLogLevel(logLevel LogLevel) error { if err := c.tf.SetLog(logLevel.String()); err != nil { return fmt.Errorf("set log level %s: %w", logLevel.String(), err) } + + // Terraform writes its log to the working directory. + // => Set the log path to the parent directory to have it in the user's working directory. if err := c.tf.SetLogPath(filepath.Join("..", constants.TerraformLogFile)); err != nil { return fmt.Errorf("set log path: %w", err) } diff --git a/cli/internal/terraform/terraform_test.go b/cli/internal/terraform/terraform_test.go index 5b4b6c3fe..689df9880 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -92,7 +92,7 @@ func TestPrepareCluster(t *testing.T) { c := &Client{ tf: &stubTerraform{}, file: file.NewHandler(tc.fs), - workingDir: constants.TerraformWorkingDir, + workingDir: "unittest", } path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String())) @@ -445,7 +445,7 @@ func TestCreateCluster(t *testing.T) { c := &Client{ tf: tc.tf, file: file.NewHandler(tc.fs), - workingDir: constants.TerraformWorkingDir, + workingDir: "unittest", } path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String())) @@ -835,7 +835,7 @@ func TestCleanupWorkspace(t *testing.T) { c := &Client{ file: file, tf: &stubTerraform{}, - workingDir: constants.TerraformWorkingDir, + workingDir: "unittest", } err := c.CleanUpWorkspace() @@ -1019,7 +1019,7 @@ func TestPlan(t *testing.T) { workingDir: tc.pathBase, } - _, err := c.Plan(context.Background(), LogLevelDebug, constants.TerraformUpgradePlanFile) + _, err := c.Plan(context.Background(), LogLevelDebug) if tc.wantErr { require.Error(err) } else { @@ -1078,7 +1078,7 @@ func TestShowPlan(t *testing.T) { workingDir: tc.pathBase, } - err := c.ShowPlan(context.Background(), LogLevelDebug, "", bytes.NewBuffer(nil)) + err := c.ShowPlan(context.Background(), LogLevelDebug, bytes.NewBuffer(nil)) if tc.wantErr { require.Error(err) } else { diff --git a/cli/internal/upgrade/iammigrate.go b/cli/internal/upgrade/iammigrate.go index a8a989b9e..813196794 100644 --- a/cli/internal/upgrade/iammigrate.go +++ b/cli/internal/upgrade/iammigrate.go @@ -19,34 +19,29 @@ import ( "github.com/edgelesssys/constellation/v2/internal/file" ) -// TfMigrationCmd is an interface for all terraform upgrade / migration commands. -type TfMigrationCmd interface { - CheckTerraformMigrations(file file.Handler) error - Plan(ctx context.Context, file file.Handler, outWriter io.Writer) (bool, error) - Apply(ctx context.Context, fileHandler file.Handler) error - String() string - UpgradeID() string -} - // IAMMigrateCmd is a terraform migration command for IAM. Which is used for the tfMigrationClient. type IAMMigrateCmd struct { - tf tfIAMClient - upgradeID string - csp cloudprovider.Provider - logLevel terraform.LogLevel + tf tfIAMClient + upgradeID string + iamWorkspace string + upgradeWorkspace string + csp cloudprovider.Provider + logLevel terraform.LogLevel } // NewIAMMigrateCmd creates a new IAMMigrateCmd. -func NewIAMMigrateCmd(ctx context.Context, upgradeID string, csp cloudprovider.Provider, logLevel terraform.LogLevel) (*IAMMigrateCmd, error) { - tfClient, err := terraform.New(ctx, filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformIAMUpgradeWorkingDir)) +func NewIAMMigrateCmd(ctx context.Context, iamWorkspace, upgradeWorkspace, upgradeID string, csp cloudprovider.Provider, logLevel terraform.LogLevel) (*IAMMigrateCmd, error) { + tfClient, err := terraform.New(ctx, filepath.Join(upgradeWorkspace, upgradeID, constants.TerraformIAMUpgradeWorkingDir)) if err != nil { return nil, fmt.Errorf("setting up terraform client: %w", err) } return &IAMMigrateCmd{ - tf: tfClient, - upgradeID: upgradeID, - csp: csp, - logLevel: logLevel, + tf: tfClient, + upgradeID: upgradeID, + iamWorkspace: iamWorkspace, + upgradeWorkspace: upgradeWorkspace, + csp: csp, + logLevel: logLevel, }, nil } @@ -62,7 +57,7 @@ func (c *IAMMigrateCmd) UpgradeID() string { // CheckTerraformMigrations checks whether Terraform migrations are possible in the current workspace. func (c *IAMMigrateCmd) CheckTerraformMigrations(file file.Handler) error { - return checkTerraformMigrations(file, c.upgradeID, constants.TerraformIAMUpgradeBackupDir) + return checkTerraformMigrations(file, c.upgradeWorkspace, c.upgradeID, constants.TerraformIAMUpgradeBackupDir) } // Plan prepares the upgrade workspace and plans the Terraform migrations for the Constellation upgrade, writing the plan to the outWriter. @@ -70,20 +65,20 @@ func (c *IAMMigrateCmd) Plan(ctx context.Context, file file.Handler, outWriter i templateDir := filepath.Join("terraform", "iam", strings.ToLower(c.csp.String())) if err := terraform.PrepareIAMUpgradeWorkspace(file, templateDir, - constants.TerraformIAMWorkingDir, - filepath.Join(constants.UpgradeDir, c.upgradeID, constants.TerraformIAMUpgradeWorkingDir), - filepath.Join(constants.UpgradeDir, c.upgradeID, constants.TerraformIAMUpgradeBackupDir), + c.iamWorkspace, + filepath.Join(c.upgradeWorkspace, c.upgradeID, constants.TerraformIAMUpgradeWorkingDir), + filepath.Join(c.upgradeWorkspace, c.upgradeID, constants.TerraformIAMUpgradeBackupDir), ); err != nil { return false, fmt.Errorf("preparing terraform workspace: %w", err) } - hasDiff, err := c.tf.Plan(ctx, c.logLevel, constants.TerraformUpgradePlanFile) + hasDiff, err := c.tf.Plan(ctx, c.logLevel) if err != nil { return false, fmt.Errorf("terraform plan: %w", err) } if hasDiff { - if err := c.tf.ShowPlan(ctx, c.logLevel, constants.TerraformUpgradePlanFile, outWriter); err != nil { + if err := c.tf.ShowPlan(ctx, c.logLevel, outWriter); err != nil { return false, fmt.Errorf("terraform show plan: %w", err) } } @@ -97,14 +92,17 @@ func (c *IAMMigrateCmd) Apply(ctx context.Context, fileHandler file.Handler) err return fmt.Errorf("terraform apply: %w", err) } - if err := fileHandler.RemoveAll(constants.TerraformIAMWorkingDir); err != nil { + if err := fileHandler.RemoveAll(c.iamWorkspace); err != nil { return fmt.Errorf("removing old terraform directory: %w", err) } - if err := fileHandler.CopyDir(filepath.Join(constants.UpgradeDir, c.upgradeID, constants.TerraformIAMUpgradeWorkingDir), constants.TerraformIAMWorkingDir); err != nil { + if err := fileHandler.CopyDir( + filepath.Join(c.upgradeWorkspace, c.upgradeID, constants.TerraformIAMUpgradeWorkingDir), + c.iamWorkspace, + ); err != nil { return fmt.Errorf("replacing old terraform directory with new one: %w", err) } - if err := fileHandler.RemoveAll(filepath.Join(constants.UpgradeDir, c.upgradeID, constants.TerraformIAMUpgradeWorkingDir)); err != nil { + if err := fileHandler.RemoveAll(filepath.Join(c.upgradeWorkspace, c.upgradeID, constants.TerraformIAMUpgradeWorkingDir)); err != nil { return fmt.Errorf("removing terraform upgrade directory: %w", err) } diff --git a/cli/internal/upgrade/iammigrate_test.go b/cli/internal/upgrade/iammigrate_test.go index 8e158f9cf..70670e3d0 100644 --- a/cli/internal/upgrade/iammigrate_test.go +++ b/cli/internal/upgrade/iammigrate_test.go @@ -28,7 +28,14 @@ func TestIAMMigrate(t *testing.T) { fs, file := setupMemFSAndFileHandler(t, []string{"terraform.tfvars", "terraform.tfstate"}, []byte("OLD")) // act fakeTfClient := &tfClientStub{upgradeID, file} - sut := &IAMMigrateCmd{fakeTfClient, upgradeID, cloudprovider.AWS, terraform.LogLevelDebug} + sut := &IAMMigrateCmd{ + tf: fakeTfClient, + upgradeID: upgradeID, + csp: cloudprovider.AWS, + logLevel: terraform.LogLevelDebug, + iamWorkspace: constants.TerraformIAMWorkingDir, + upgradeWorkspace: constants.UpgradeDir, + } hasDiff, err := sut.Plan(context.Background(), file, bytes.NewBuffer(nil)) // assert assert.NoError(t, err) @@ -82,11 +89,11 @@ type tfClientStub struct { file file.Handler } -func (t *tfClientStub) Plan(_ context.Context, _ terraform.LogLevel, _ string) (bool, error) { +func (t *tfClientStub) Plan(_ context.Context, _ terraform.LogLevel) (bool, error) { return false, nil } -func (t *tfClientStub) ShowPlan(_ context.Context, _ terraform.LogLevel, _ string, _ io.Writer) error { +func (t *tfClientStub) ShowPlan(_ context.Context, _ terraform.LogLevel, _ io.Writer) error { return nil } diff --git a/cli/internal/upgrade/terraform.go b/cli/internal/upgrade/terraform.go index 18da89440..3d7aa9e48 100644 --- a/cli/internal/upgrade/terraform.go +++ b/cli/internal/upgrade/terraform.go @@ -47,14 +47,16 @@ type TerraformUpgradeOptions struct { // CSP is the cloud provider to perform the upgrade on. CSP cloudprovider.Provider // Vars are the Terraform variables used for the upgrade. - Vars terraform.Variables + Vars terraform.Variables + TFWorkspace string + UpgradeWorkspace string } // CheckTerraformMigrations checks whether Terraform migrations are possible in the current workspace. -func checkTerraformMigrations(file file.Handler, upgradeID, upgradeSubDir string) error { +func checkTerraformMigrations(file file.Handler, upgradeWorkspace, upgradeID, upgradeSubDir string) error { var existingFiles []string filesToCheck := []string{ - filepath.Join(constants.UpgradeDir, upgradeID, upgradeSubDir), + filepath.Join(upgradeWorkspace, upgradeID, upgradeSubDir), } for _, f := range filesToCheck { @@ -71,8 +73,8 @@ func checkTerraformMigrations(file file.Handler, upgradeID, upgradeSubDir string // CheckTerraformMigrations checks whether Terraform migrations are possible in the current workspace. // If the files that will be written during the upgrade already exist, it returns an error. -func (u *TerraformUpgrader) CheckTerraformMigrations(upgradeID, upgradeSubDir string) error { - return checkTerraformMigrations(u.fileHandler, upgradeID, upgradeSubDir) +func (u *TerraformUpgrader) CheckTerraformMigrations(upgradeWorkspace, upgradeID, upgradeSubDir string) error { + return checkTerraformMigrations(u.fileHandler, upgradeWorkspace, upgradeID, upgradeSubDir) } // checkFileExists checks whether a file exists and adds it to the existingFiles slice if it does. @@ -96,30 +98,22 @@ func (u *TerraformUpgrader) PlanTerraformMigrations(ctx context.Context, opts Te // Prepare the new Terraform workspace and backup the old one err := u.tf.PrepareUpgradeWorkspace( filepath.Join("terraform", strings.ToLower(opts.CSP.String())), - constants.TerraformWorkingDir, - filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformUpgradeWorkingDir), - filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformUpgradeBackupDir), + opts.TFWorkspace, + filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir), + filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeBackupDir), opts.Vars, ) if err != nil { return false, fmt.Errorf("preparing terraform workspace: %w", err) } - // Backup the old constellation-id.json file - if err := u.fileHandler.CopyFile( - constants.ClusterIDsFileName, - filepath.Join(constants.UpgradeDir, upgradeID, constants.ClusterIDsFileName+".old"), - ); err != nil { - return false, fmt.Errorf("backing up %s: %w", constants.ClusterIDsFileName, err) - } - - hasDiff, err := u.tf.Plan(ctx, opts.LogLevel, constants.TerraformUpgradePlanFile) + hasDiff, err := u.tf.Plan(ctx, opts.LogLevel) if err != nil { return false, fmt.Errorf("terraform plan: %w", err) } if hasDiff { - if err := u.tf.ShowPlan(ctx, opts.LogLevel, constants.TerraformUpgradePlanFile, u.outWriter); err != nil { + if err := u.tf.ShowPlan(ctx, opts.LogLevel, u.outWriter); err != nil { return false, fmt.Errorf("terraform show plan: %w", err) } } @@ -129,22 +123,16 @@ func (u *TerraformUpgrader) PlanTerraformMigrations(ctx context.Context, opts Te // CleanUpTerraformMigrations cleans up the Terraform migration workspace, for example when an upgrade is // aborted by the user. -func (u *TerraformUpgrader) CleanUpTerraformMigrations(upgradeID string) error { - return CleanUpTerraformMigrations(upgradeID, u.fileHandler) +func (u *TerraformUpgrader) CleanUpTerraformMigrations(upgradeWorkspace, upgradeID string) error { + return CleanUpTerraformMigrations(upgradeWorkspace, upgradeID, u.fileHandler) } // CleanUpTerraformMigrations cleans up the Terraform upgrade directory. -func CleanUpTerraformMigrations(upgradeID string, fileHandler file.Handler) error { - cleanupFiles := []string{ - filepath.Join(constants.UpgradeDir, upgradeID), +func CleanUpTerraformMigrations(upgradeWorkspace, upgradeID string, fileHandler file.Handler) error { + upgradeDir := filepath.Join(upgradeWorkspace, upgradeID) + if err := fileHandler.RemoveAll(upgradeDir); err != nil { + return fmt.Errorf("cleaning up file %s: %w", upgradeDir, err) } - - for _, f := range cleanupFiles { - if err := fileHandler.RemoveAll(f); err != nil { - return fmt.Errorf("cleaning up file %s: %w", f, err) - } - } - return nil } @@ -152,68 +140,54 @@ func CleanUpTerraformMigrations(upgradeID string, fileHandler file.Handler) erro // If PlanTerraformMigrations has not been executed before, it will return an error. // In case of a successful upgrade, the output will be written to the specified file and the old Terraform directory is replaced // By the new one. -func (u *TerraformUpgrader) ApplyTerraformMigrations(ctx context.Context, opts TerraformUpgradeOptions, upgradeID string) error { +func (u *TerraformUpgrader) ApplyTerraformMigrations(ctx context.Context, opts TerraformUpgradeOptions, upgradeID string) (clusterid.File, error) { tfOutput, err := u.tf.CreateCluster(ctx, opts.CSP, opts.LogLevel) if err != nil { - return fmt.Errorf("terraform apply: %w", err) + return clusterid.File{}, fmt.Errorf("terraform apply: %w", err) } - outputFileContents := clusterid.File{ + clusterID := clusterid.File{ CloudProvider: opts.CSP, InitSecret: []byte(tfOutput.Secret), IP: tfOutput.IP, APIServerCertSANs: tfOutput.APIServerCertSANs, UID: tfOutput.UID, } - // AttestationURL is only set for Azure. + + // Patch MAA policy if we applied an Azure upgrade. if tfOutput.Azure != nil { if err := u.policyPatcher.Patch(ctx, tfOutput.Azure.AttestationURL); err != nil { - return fmt.Errorf("patching policies: %w", err) + return clusterid.File{}, fmt.Errorf("patching policies: %w", err) } - outputFileContents.AttestationURL = tfOutput.Azure.AttestationURL + clusterID.AttestationURL = tfOutput.Azure.AttestationURL } - if err := u.fileHandler.RemoveAll(constants.TerraformWorkingDir); err != nil { - return fmt.Errorf("removing old terraform directory: %w", err) + if err := u.fileHandler.RemoveAll(opts.TFWorkspace); err != nil { + return clusterid.File{}, fmt.Errorf("removing old terraform directory: %w", err) } - if err := u.fileHandler.CopyDir(filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformUpgradeWorkingDir), constants.TerraformWorkingDir); err != nil { - return fmt.Errorf("replacing old terraform directory with new one: %w", err) + if err := u.fileHandler.CopyDir( + filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir), + opts.TFWorkspace, + ); err != nil { + return clusterid.File{}, fmt.Errorf("replacing old terraform directory with new one: %w", err) } - if err := u.fileHandler.RemoveAll(filepath.Join(constants.UpgradeDir, upgradeID, constants.TerraformUpgradeWorkingDir)); err != nil { - return fmt.Errorf("removing terraform upgrade directory: %w", err) + if err := u.fileHandler.RemoveAll(filepath.Join(opts.UpgradeWorkspace, upgradeID, constants.TerraformUpgradeWorkingDir)); err != nil { + return clusterid.File{}, fmt.Errorf("removing terraform upgrade directory: %w", err) } - if err := u.mergeClusterIDFile(outputFileContents); err != nil { - return fmt.Errorf("merging migration output into %s: %w", constants.ClusterIDsFileName, err) - } - - return nil -} - -// mergeClusterIDFile merges the output of the migration into the constellation-id.json file. -func (u *TerraformUpgrader) mergeClusterIDFile(migrationOutput clusterid.File) error { - idFile := &clusterid.File{} - if err := u.fileHandler.ReadJSON(constants.ClusterIDsFileName, idFile); err != nil { - return fmt.Errorf("reading %s: %w", constants.ClusterIDsFileName, err) - } - - if err := u.fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile.Merge(migrationOutput), file.OptOverwrite); err != nil { - return fmt.Errorf("writing %s: %w", constants.ClusterIDsFileName, err) - } - - return nil + return clusterID, nil } type tfClientCommon interface { - ShowPlan(ctx context.Context, logLevel terraform.LogLevel, planFilePath string, output io.Writer) error - Plan(ctx context.Context, logLevel terraform.LogLevel, planFile string) (bool, error) + ShowPlan(ctx context.Context, logLevel terraform.LogLevel, output io.Writer) error + Plan(ctx context.Context, logLevel terraform.LogLevel) (bool, error) } // tfResourceClient is a Terraform client for managing cluster resources. type tfResourceClient interface { - PrepareUpgradeWorkspace(path, oldWorkingDir, newWorkingDir, backupDir string, vars terraform.Variables) error + PrepareUpgradeWorkspace(embeddedPath, oldWorkingDir, newWorkingDir, backupDir string, vars terraform.Variables) error CreateCluster(ctx context.Context, provider cloudprovider.Provider, logLevel terraform.LogLevel) (terraform.ApplyOutput, error) tfClientCommon } diff --git a/cli/internal/upgrade/terraform_test.go b/cli/internal/upgrade/terraform_test.go index d15a99811..515e6cf89 100644 --- a/cli/internal/upgrade/terraform_test.go +++ b/cli/internal/upgrade/terraform_test.go @@ -58,7 +58,7 @@ func TestCheckTerraformMigrations(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { u := upgrader(tc.workspace) - err := u.CheckTerraformMigrations(tc.upgradeID, constants.TerraformUpgradeBackupDir) + err := u.CheckTerraformMigrations(constants.UpgradeDir, tc.upgradeID, constants.TerraformUpgradeBackupDir) if tc.wantErr { require.Error(t, err) return @@ -95,14 +95,14 @@ func TestPlanTerraformMigrations(t *testing.T) { "success no diff": { upgradeID: "1234", tf: &stubTerraformClient{}, - workspace: workspace([]string{constants.ClusterIDsFileName}), + workspace: workspace([]string{}), }, "success diff": { upgradeID: "1234", tf: &stubTerraformClient{ hasDiff: true, }, - workspace: workspace([]string{constants.ClusterIDsFileName}), + workspace: workspace([]string{}), want: true, }, "prepare workspace error": { @@ -110,26 +110,14 @@ func TestPlanTerraformMigrations(t *testing.T) { tf: &stubTerraformClient{ prepareWorkspaceErr: assert.AnError, }, - workspace: workspace([]string{constants.ClusterIDsFileName}), - wantErr: true, - }, - "constellation-id.json does not exist": { - upgradeID: "1234", - tf: &stubTerraformClient{}, - workspace: workspace(nil), - wantErr: true, - }, - "constellation-id backup already exists": { - upgradeID: "1234", - tf: &stubTerraformClient{}, - workspace: workspace([]string{filepath.Join(constants.UpgradeDir, "1234", constants.ClusterIDsFileName+".old")}), + workspace: workspace([]string{}), wantErr: true, }, "plan error": { tf: &stubTerraformClient{ planErr: assert.AnError, }, - workspace: workspace([]string{constants.ClusterIDsFileName}), + workspace: workspace([]string{}), wantErr: true, }, "show plan error no diff": { @@ -137,7 +125,7 @@ func TestPlanTerraformMigrations(t *testing.T) { tf: &stubTerraformClient{ showErr: assert.AnError, }, - workspace: workspace([]string{constants.ClusterIDsFileName}), + workspace: workspace([]string{}), }, "show plan error diff": { upgradeID: "1234", @@ -145,7 +133,7 @@ func TestPlanTerraformMigrations(t *testing.T) { showErr: assert.AnError, hasDiff: true, }, - workspace: workspace([]string{constants.ClusterIDsFileName}), + workspace: workspace([]string{}), wantErr: true, }, } @@ -196,12 +184,11 @@ func TestApplyTerraformMigrations(t *testing.T) { } testCases := map[string]struct { - upgradeID string - tf tfResourceClient - policyPatcher stubPolicyPatcher - fs file.Handler - skipIDFileCreation bool // if true, do not create the constellation-id.json file - wantErr bool + upgradeID string + tf tfResourceClient + policyPatcher stubPolicyPatcher + fs file.Handler + wantErr bool }{ "success": { upgradeID: "1234", @@ -218,14 +205,6 @@ func TestApplyTerraformMigrations(t *testing.T) { policyPatcher: stubPolicyPatcher{}, wantErr: true, }, - "constellation-id.json does not exist": { - upgradeID: "1234", - tf: &stubTerraformClient{}, - fs: fileHandler("1234"), - policyPatcher: stubPolicyPatcher{}, - skipIDFileCreation: true, - wantErr: true, - }, } for name, tc := range testCases { @@ -234,21 +213,15 @@ func TestApplyTerraformMigrations(t *testing.T) { u := upgrader(tc.tf, tc.fs) - if !tc.skipIDFileCreation { - require.NoError( - tc.fs.Write( - filepath.Join(constants.ClusterIDsFileName), - []byte("{}"), - )) - } - opts := TerraformUpgradeOptions{ - LogLevel: terraform.LogLevelDebug, - CSP: cloudprovider.Unknown, - Vars: &terraform.QEMUVariables{}, + LogLevel: terraform.LogLevelDebug, + CSP: cloudprovider.Unknown, + Vars: &terraform.QEMUVariables{}, + TFWorkspace: "test", + UpgradeWorkspace: constants.UpgradeDir, } - err := u.ApplyTerraformMigrations(context.Background(), opts, tc.upgradeID) + _, err := u.ApplyTerraformMigrations(context.Background(), opts, tc.upgradeID) if tc.wantErr { require.Error(err) } else { @@ -328,7 +301,7 @@ func TestCleanUpTerraformMigrations(t *testing.T) { workspace := workspace(tc.workspaceFiles) u := upgrader(workspace) - err := u.CleanUpTerraformMigrations(tc.upgradeID) + err := u.CleanUpTerraformMigrations(constants.UpgradeDir, tc.upgradeID) if tc.wantErr { require.Error(err) return @@ -359,19 +332,19 @@ type stubTerraformClient struct { CreateClusterErr error } -func (u *stubTerraformClient) PrepareUpgradeWorkspace(string, string, string, string, terraform.Variables) error { +func (u *stubTerraformClient) PrepareUpgradeWorkspace(_, _, _, _ string, _ terraform.Variables) error { return u.prepareWorkspaceErr } -func (u *stubTerraformClient) ShowPlan(context.Context, terraform.LogLevel, string, io.Writer) error { +func (u *stubTerraformClient) ShowPlan(_ context.Context, _ terraform.LogLevel, _ io.Writer) error { return u.showErr } -func (u *stubTerraformClient) Plan(context.Context, terraform.LogLevel, string) (bool, error) { +func (u *stubTerraformClient) Plan(_ context.Context, _ terraform.LogLevel) (bool, error) { return u.hasDiff, u.planErr } -func (u *stubTerraformClient) CreateCluster(context.Context, cloudprovider.Provider, terraform.LogLevel) (terraform.ApplyOutput, error) { +func (u *stubTerraformClient) CreateCluster(_ context.Context, _ cloudprovider.Provider, _ terraform.LogLevel) (terraform.ApplyOutput, error) { return terraform.ApplyOutput{}, u.CreateClusterErr } @@ -379,6 +352,6 @@ type stubPolicyPatcher struct { patchErr error } -func (p *stubPolicyPatcher) PatchPolicy(context.Context, string) error { +func (p *stubPolicyPatcher) PatchPolicy(_ context.Context, _ string) error { return p.patchErr } diff --git a/debugd/internal/cdbg/cmd/deploy.go b/debugd/internal/cdbg/cmd/deploy.go index 4969531d6..43068569e 100644 --- a/debugd/internal/cdbg/cmd/deploy.go +++ b/debugd/internal/cdbg/cmd/deploy.go @@ -113,7 +113,7 @@ func deploy(cmd *cobra.Command, fileHandler file.Handler, constellationConfig *c } if len(ips) == 0 { var idFile clusterIDsFile - if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { + if err := fileHandler.ReadJSON(constants.ClusterIDsFilename, &idFile); err != nil { return fmt.Errorf("reading cluster IDs file: %w", err) } ips = []string{idFile.IP} diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index 2a1fe54ef..95aaa292d 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -56,10 +56,10 @@ Work with the Constellation configuration file. ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation config generate @@ -78,7 +78,6 @@ constellation config generate {aws|azure|gcp|openstack|qemu|stackit} [flags] ``` -a, --attestation string attestation variant to use {aws-sev-snp|aws-nitro-tpm|azure-sev-snp|azure-trustedlaunch|gcp-sev-es|qemu-vtpm}. If not specified, the default for the cloud provider is used - -f, --file string path to output file, or '-' for stdout (default "constellation-conf.yaml") -h, --help help for generate -k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR (default "v1.26") ``` @@ -86,10 +85,10 @@ constellation config generate {aws|azure|gcp|openstack|qemu|stackit} [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation config fetch-measurements @@ -117,10 +116,10 @@ constellation config fetch-measurements [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation config instance-types @@ -144,10 +143,10 @@ constellation config instance-types [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation config kubernetes-versions @@ -171,10 +170,10 @@ constellation config kubernetes-versions [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation config migrate @@ -198,10 +197,10 @@ constellation config migrate [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation create @@ -226,10 +225,10 @@ constellation create [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation init @@ -249,20 +248,19 @@ constellation init [flags] ### Options ``` - --conformance enable conformance mode - -h, --help help for init - --master-secret string path to base64-encoded master secret - --merge-kubeconfig merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config - --skip-helm-wait install helm charts without waiting for deployments to be ready + --conformance enable conformance mode + -h, --help help for init + --merge-kubeconfig merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config + --skip-helm-wait install helm charts without waiting for deployments to be ready ``` ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation mini @@ -282,10 +280,10 @@ Manage MiniConstellation clusters. ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation mini up @@ -305,7 +303,6 @@ constellation mini up [flags] ### Options ``` - --config string path to the configuration file to use for the cluster -h, --help help for up --merge-kubeconfig merge Constellation kubeconfig file with default kubeconfig file in $HOME/.kube/config (default true) ``` @@ -313,9 +310,10 @@ constellation mini up [flags] ### Options inherited from parent commands ``` - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation mini down @@ -340,10 +338,10 @@ constellation mini down [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation status @@ -369,10 +367,10 @@ constellation status [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation verify @@ -400,10 +398,10 @@ constellation verify [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation upgrade @@ -423,10 +421,10 @@ Find and apply upgrades to your Constellation cluster. ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation upgrade check @@ -453,10 +451,10 @@ constellation upgrade check [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation upgrade apply @@ -483,10 +481,10 @@ constellation upgrade apply [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation recover @@ -506,18 +504,17 @@ constellation recover [flags] ### Options ``` - -e, --endpoint string endpoint of the instance, passed as HOST[:PORT] - -h, --help help for recover - --master-secret string path to master secret file (default "constellation-mastersecret.json") + -e, --endpoint string endpoint of the instance, passed as HOST[:PORT] + -h, --help help for recover ``` ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation terminate @@ -544,10 +541,10 @@ constellation terminate [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation iam @@ -567,10 +564,10 @@ Work with the IAM configuration on your cloud provider. ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation iam create @@ -592,10 +589,10 @@ Create IAM configuration on a cloud platform for your Constellation cluster. ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation iam create aws @@ -622,12 +619,12 @@ constellation iam create aws [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") - --update-config update the config file with the specific IAM information - -y, --yes create the IAM configuration without further confirmation + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + --update-config update the config file with the specific IAM information + -C, --workspace string path to the Constellation workspace + -y, --yes create the IAM configuration without further confirmation ``` ## constellation iam create azure @@ -654,12 +651,12 @@ constellation iam create azure [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") - --update-config update the config file with the specific IAM information - -y, --yes create the IAM configuration without further confirmation + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + --update-config update the config file with the specific IAM information + -C, --workspace string path to the Constellation workspace + -y, --yes create the IAM configuration without further confirmation ``` ## constellation iam create gcp @@ -689,12 +686,12 @@ constellation iam create gcp [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") - --update-config update the config file with the specific IAM information - -y, --yes create the IAM configuration without further confirmation + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + --update-config update the config file with the specific IAM information + -C, --workspace string path to the Constellation workspace + -y, --yes create the IAM configuration without further confirmation ``` ## constellation iam destroy @@ -719,10 +716,10 @@ constellation iam destroy [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation iam upgrade @@ -742,10 +739,10 @@ Find and apply upgrades to your IAM profile. ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation iam upgrade apply @@ -765,16 +762,15 @@ constellation iam upgrade apply [flags] ``` -h, --help help for apply -y, --yes run upgrades without further confirmation - ``` ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` ## constellation version @@ -798,9 +794,9 @@ constellation version [flags] ### Options inherited from parent commands ``` - --config string path to the configuration file (default "constellation-conf.yaml") - --debug enable debug logging - --force disable version compatibility checks - might result in corrupted clusters - --tf-log string Terraform log level (default "NONE") + --debug enable debug logging + --force disable version compatibility checks - might result in corrupted clusters + --tf-log string Terraform log level (default "NONE") + -C, --workspace string path to the Constellation workspace ``` diff --git a/e2e/internal/upgrade/upgrade_test.go b/e2e/internal/upgrade/upgrade_test.go index 9f65fbea8..409257d00 100644 --- a/e2e/internal/upgrade/upgrade_test.go +++ b/e2e/internal/upgrade/upgrade_test.go @@ -80,7 +80,7 @@ func TestUpgrade(t *testing.T) { // Migrate config if necessary. log.Println("Migrating config if needed.") - cmd := exec.CommandContext(context.Background(), cli, "config", "migrate", "--config", constants.ConfigFilename, "--debug") + cmd := exec.CommandContext(context.Background(), cli, "config", "migrate", "--debug") stdout, stderr, err := runCommandWithSeparateOutputs(cmd) require.NoError(err, "Stdout: %s\nStderr: %s", string(stdout), string(stderr)) log.Println(string(stdout)) diff --git a/internal/constants/constants.go b/internal/constants/constants.go index 11045d8ea..fcd97ff0f 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -74,8 +74,8 @@ const ( // Filenames. // - // ClusterIDsFileName filename that contains Constellation clusterID and IP. - ClusterIDsFileName = "constellation-id.json" + // ClusterIDsFilename filename that contains Constellation clusterID and IP. + ClusterIDsFilename = "constellation-id.json" // ConfigFilename filename of Constellation config file. ConfigFilename = "constellation-conf.yaml" // LicenseFilename filename of Constellation license file. @@ -88,8 +88,6 @@ const ( TerraformWorkingDir = "constellation-terraform" // TerraformIAMWorkingDir is the directory name for the Terraform IAM Client workspace. TerraformIAMWorkingDir = "constellation-iam-terraform" - // GCPServiceAccountKeyFile is the file name for the GCP service account key file. - GCPServiceAccountKeyFile = "gcpServiceAccountKey.json" // ErrorLog file which contains server errors during init. ErrorLog = "constellation-cluster.log" // ControlPlaneAdminConfFilename filepath to control plane kubernetes admin config. @@ -157,8 +155,6 @@ const ( MiniConstellationUID = "mini" // TerraformLogFile is the file name of the Terraform log file. TerraformLogFile = "terraform.log" - // TerraformUpgradePlanFile is the file name of the zipfile created by Terraform plan for Constellation upgrades. - TerraformUpgradePlanFile = "plan.zip" // TerraformUpgradeWorkingDir is the directory name for the Terraform workspace being used in an upgrade. TerraformUpgradeWorkingDir = "terraform" // TerraformIAMUpgradeWorkingDir is the directory name for the Terraform IAM workspace being used in an upgrade.