From 1556e239cad8cfb2cc20a9c0c58c6074ae878469 Mon Sep 17 00:00:00 2001 From: katexochen <49727155+katexochen@users.noreply.github.com> Date: Tue, 11 Oct 2022 12:24:33 +0200 Subject: [PATCH] Remove state file --- CHANGELOG.md | 1 + cli/internal/cloudcmd/clients.go | 4 +- cli/internal/cloudcmd/clients_test.go | 11 +- cli/internal/cloudcmd/create.go | 56 ++++--- cli/internal/cloudcmd/create_test.go | 39 ++--- cli/internal/cloudcmd/terminate.go | 32 ++-- cli/internal/cloudcmd/terminate_test.go | 24 ++- cli/internal/clusterid/id.go | 25 +++ cli/internal/cmd/cloud.go | 6 +- cli/internal/cmd/cloud_test.go | 11 +- cli/internal/cmd/create.go | 21 +-- cli/internal/cmd/create_test.go | 41 ++--- cli/internal/cmd/id.go | 13 -- cli/internal/cmd/init.go | 40 ++--- cli/internal/cmd/init_test.go | 80 ++++------ cli/internal/cmd/minidown.go | 10 +- cli/internal/cmd/miniup.go | 9 +- cli/internal/cmd/recover_test.go | 3 +- cli/internal/cmd/terminate.go | 14 +- cli/internal/cmd/terminate_test.go | 36 +++-- cli/internal/cmd/verify.go | 3 +- cli/internal/cmd/verify_test.go | 9 +- cli/internal/terraform/terraform.go | 30 ++-- cli/internal/terraform/terraform_test.go | 3 +- internal/cloud/cloudprovider/cloudprovider.go | 20 ++- .../cloud/cloudprovider/cloudprovider_test.go | 143 ++++++++++++++++++ internal/constants/constants.go | 1 - internal/state/state.go | 15 -- 28 files changed, 381 insertions(+), 319 deletions(-) create mode 100644 cli/internal/clusterid/id.go delete mode 100644 cli/internal/cmd/id.go create mode 100644 internal/cloud/cloudprovider/cloudprovider_test.go delete mode 100644 internal/state/state.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 8add0aa8f..d9bf6bf58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `endpoint` flag of `constellation init`. IP is now always taken from the `constellation-id.json` file. +- `constellation-state.json` file won't be created anymore. Resources are now managed through Terraform. ### Fixed diff --git a/cli/internal/cloudcmd/clients.go b/cli/internal/cloudcmd/clients.go index e6b9956f1..4959bbc3d 100644 --- a/cli/internal/cloudcmd/clients.go +++ b/cli/internal/cloudcmd/clients.go @@ -10,12 +10,10 @@ import ( "context" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" - "github.com/edgelesssys/constellation/v2/internal/state" ) type terraformClient interface { - GetState() state.ConstellationState - CreateCluster(ctx context.Context, name string, input terraform.Variables) error + CreateCluster(ctx context.Context, name string, input terraform.Variables) (string, error) DestroyCluster(ctx context.Context) error CleanUpWorkspace() error RemoveInstaller() diff --git a/cli/internal/cloudcmd/clients_test.go b/cli/internal/cloudcmd/clients_test.go index 6750cd45b..6a88a2121 100644 --- a/cli/internal/cloudcmd/clients_test.go +++ b/cli/internal/cloudcmd/clients_test.go @@ -11,7 +11,6 @@ import ( "testing" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" - "github.com/edgelesssys/constellation/v2/internal/state" "go.uber.org/goleak" ) @@ -23,7 +22,7 @@ func TestMain(m *testing.M) { } type stubTerraformClient struct { - state state.ConstellationState + ip string cleanUpWorkspaceCalled bool removeInstallerCalled bool destroyClusterCalled bool @@ -32,12 +31,8 @@ type stubTerraformClient struct { cleanUpWorkspaceErr error } -func (c *stubTerraformClient) GetState() state.ConstellationState { - return c.state -} - -func (c *stubTerraformClient) CreateCluster(ctx context.Context, name string, input terraform.Variables) error { - return c.createClusterErr +func (c *stubTerraformClient) CreateCluster(ctx context.Context, name string, input terraform.Variables) (string, error) { + return c.ip, c.createClusterErr } func (c *stubTerraformClient) DestroyCluster(ctx context.Context) error { diff --git a/cli/internal/cloudcmd/create.go b/cli/internal/cloudcmd/create.go index 052520bb5..9d6d5a3ff 100644 --- a/cli/internal/cloudcmd/create.go +++ b/cli/internal/cloudcmd/create.go @@ -15,11 +15,11 @@ import ( "runtime" "strings" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "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/state" ) // Creator creates cloud resources. @@ -44,41 +44,41 @@ func NewCreator(out io.Writer) *Creator { // Create creates the handed amount of instances and all the needed resources. func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, config *config.Config, name, insType string, controlPlaneCount, workerCount int, -) (state.ConstellationState, error) { +) (clusterid.File, error) { switch provider { case cloudprovider.GCP: cl, err := c.newTerraformClient(ctx, provider) if err != nil { - return state.ConstellationState{}, err + return clusterid.File{}, err } defer cl.RemoveInstaller() return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount) case cloudprovider.Azure: cl, err := c.newTerraformClient(ctx, provider) if err != nil { - return state.ConstellationState{}, err + return clusterid.File{}, err } defer cl.RemoveInstaller() return c.createAzure(ctx, cl, config, name, insType, controlPlaneCount, workerCount) case cloudprovider.QEMU: if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" { - return state.ConstellationState{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH) + return clusterid.File{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH) } cl, err := c.newTerraformClient(ctx, provider) if err != nil { - return state.ConstellationState{}, err + return clusterid.File{}, err } defer cl.RemoveInstaller() lv := c.newLibvirtRunner() return c.createQEMU(ctx, cl, lv, name, config, controlPlaneCount, workerCount) default: - return state.ConstellationState{}, fmt.Errorf("unsupported cloud provider: %s", provider) + return clusterid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider) } } func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config, name, insType string, controlPlaneCount, workerCount int, -) (stat state.ConstellationState, retErr error) { +) (idFile clusterid.File, retErr error) { defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl}) vars := &terraform.GCPVariables{ @@ -98,16 +98,20 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con Debug: config.IsDebugCluster(), } - if err := cl.CreateCluster(ctx, name, vars); err != nil { - return state.ConstellationState{}, err + ip, err := cl.CreateCluster(ctx, name, vars) + if err != nil { + return clusterid.File{}, err } - return cl.GetState(), nil + return clusterid.File{ + CloudProvider: cloudprovider.GCP, + IP: ip, + }, nil } func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config, name, insType string, controlPlaneCount, workerCount int, -) (stat state.ConstellationState, retErr error) { +) (idFile clusterid.File, retErr error) { defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl}) vars := &terraform.AzureVariables{ @@ -127,16 +131,20 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c Debug: config.IsDebugCluster(), } - if err := cl.CreateCluster(ctx, name, vars); err != nil { - return state.ConstellationState{}, err + ip, err := cl.CreateCluster(ctx, name, vars) + if err != nil { + return clusterid.File{}, err } - return cl.GetState(), nil + return clusterid.File{ + CloudProvider: cloudprovider.Azure, + IP: ip, + }, nil } func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner, name string, config *config.Config, controlPlaneCount, workerCount int, -) (stat state.ConstellationState, retErr error) { +) (idFile clusterid.File, retErr error) { defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerQEMU{client: cl, libvirt: lv}) libvirtURI := config.Provider.QEMU.LibvirtURI @@ -146,7 +154,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt // if no libvirt URI is specified, start a libvirt container case libvirtURI == "": if err := lv.Start(ctx, name, config.Provider.QEMU.LibvirtContainerImage); err != nil { - return state.ConstellationState{}, err + return clusterid.File{}, err } libvirtURI = libvirt.LibvirtTCPConnectURI @@ -162,11 +170,11 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt case strings.HasPrefix(libvirtURI, "qemu+unix://"): unixURI, err := url.Parse(strings.TrimPrefix(libvirtURI, "qemu+unix://")) if err != nil { - return state.ConstellationState{}, err + return clusterid.File{}, err } libvirtSocketPath = unixURI.Query().Get("socket") if libvirtSocketPath == "" { - return state.ConstellationState{}, fmt.Errorf("socket path not specified in qemu+unix URI: %s", libvirtURI) + return clusterid.File{}, fmt.Errorf("socket path not specified in qemu+unix URI: %s", libvirtURI) } } @@ -192,9 +200,13 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt MetadataLibvirtURI: metadataLibvirtURI, } - if err := cl.CreateCluster(ctx, name, vars); err != nil { - return state.ConstellationState{}, err + ip, err := cl.CreateCluster(ctx, name, vars) + if err != nil { + return clusterid.File{}, err } - return cl.GetState(), nil + return clusterid.File{ + CloudProvider: cloudprovider.QEMU, + IP: ip, + }, nil } diff --git a/cli/internal/cloudcmd/create_test.go b/cli/internal/cloudcmd/create_test.go index 074c58142..d27b73454 100644 --- a/cli/internal/cloudcmd/create_test.go +++ b/cli/internal/cloudcmd/create_test.go @@ -15,23 +15,12 @@ import ( "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" - "github.com/edgelesssys/constellation/v2/internal/state" "github.com/stretchr/testify/assert" ) func TestCreator(t *testing.T) { failOnNonAMD64 := (runtime.GOARCH != "amd64") || (runtime.GOOS != "linux") - - wantGCPState := state.ConstellationState{ - CloudProvider: cloudprovider.GCP.String(), - LoadBalancerIP: "192.0.2.1", - } - - wantQEMUState := state.ConstellationState{ - CloudProvider: cloudprovider.QEMU.String(), - LoadBalancerIP: "192.0.2.1", - } - + ip := "192.0.2.1" someErr := errors.New("failed") testCases := map[string]struct { @@ -40,15 +29,13 @@ func TestCreator(t *testing.T) { libvirt *stubLibvirtRunner provider cloudprovider.Provider config *config.Config - wantState state.ConstellationState wantErr bool wantRollback bool // Use only together with stubClients. }{ "gcp": { - tfClient: &stubTerraformClient{state: wantGCPState}, - provider: cloudprovider.GCP, - config: config.Default(), - wantState: wantGCPState, + tfClient: &stubTerraformClient{ip: ip}, + provider: cloudprovider.GCP, + config: config.Default(), }, "gcp newTerraformClient error": { newTfClientErr: someErr, @@ -64,12 +51,11 @@ func TestCreator(t *testing.T) { wantRollback: true, }, "qemu": { - tfClient: &stubTerraformClient{state: wantQEMUState}, - libvirt: &stubLibvirtRunner{}, - provider: cloudprovider.QEMU, - config: config.Default(), - wantState: wantQEMUState, - wantErr: failOnNonAMD64, + tfClient: &stubTerraformClient{ip: ip}, + libvirt: &stubLibvirtRunner{}, + provider: cloudprovider.QEMU, + config: config.Default(), + wantErr: failOnNonAMD64, }, "qemu newTerraformClient error": { newTfClientErr: someErr, @@ -87,7 +73,7 @@ func TestCreator(t *testing.T) { wantRollback: !failOnNonAMD64, // if we run on non-AMD64/linux, we don't get to a point where rollback is needed }, "qemu start libvirt error": { - tfClient: &stubTerraformClient{state: wantQEMUState}, + tfClient: &stubTerraformClient{ip: ip}, libvirt: &stubLibvirtRunner{startErr: someErr}, provider: cloudprovider.QEMU, config: config.Default(), @@ -115,7 +101,7 @@ func TestCreator(t *testing.T) { }, } - state, err := creator.Create(context.Background(), tc.provider, tc.config, "name", "type", 2, 3) + idFile, err := creator.Create(context.Background(), tc.provider, tc.config, "name", "type", 2, 3) if tc.wantErr { assert.Error(err) @@ -129,7 +115,8 @@ func TestCreator(t *testing.T) { } } else { assert.NoError(err) - assert.Equal(tc.wantState, state) + assert.Equal(tc.provider, idFile.CloudProvider) + assert.Equal(ip, idFile.IP) } }) } diff --git a/cli/internal/cloudcmd/terminate.go b/cli/internal/cloudcmd/terminate.go index 33219d5b7..d908c1b58 100644 --- a/cli/internal/cloudcmd/terminate.go +++ b/cli/internal/cloudcmd/terminate.go @@ -8,12 +8,11 @@ package cloudcmd import ( "context" - "fmt" + "errors" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/state" ) // Terminator deletes cloud provider resources. @@ -35,10 +34,18 @@ func NewTerminator() *Terminator { } // Terminate deletes the could provider resources defined in the constellation state. -func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationState) error { - provider := cloudprovider.FromString(state.CloudProvider) +func (t *Terminator) Terminate(ctx context.Context, provider cloudprovider.Provider) (retErr error) { if provider == cloudprovider.Unknown { - return fmt.Errorf("unknown cloud provider %s", state.CloudProvider) + return errors.New("unknown cloud provider") + } + + if provider == cloudprovider.QEMU { + libvirt := t.newLibvirtRunner() + defer func() { + if retErr == nil { + retErr = libvirt.Stop(ctx) + } + }() } cl, err := t.newTerraformClient(ctx, provider) @@ -47,11 +54,6 @@ func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationSta } defer cl.RemoveInstaller() - if provider == cloudprovider.QEMU { - libvirt := t.newLibvirtRunner() - return t.terminateQEMU(ctx, cl, libvirt) - } - return t.terminateTerraform(ctx, cl) } @@ -61,13 +63,3 @@ func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient) } return cl.CleanUpWorkspace() } - -func (t *Terminator) terminateQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner) error { - if err := cl.DestroyCluster(ctx); err != nil { - return err - } - if err := lv.Stop(ctx); err != nil { - return err - } - return cl.CleanUpWorkspace() -} diff --git a/cli/internal/cloudcmd/terminate_test.go b/cli/internal/cloudcmd/terminate_test.go index 83fbb3a90..f3e7b99f4 100644 --- a/cli/internal/cloudcmd/terminate_test.go +++ b/cli/internal/cloudcmd/terminate_test.go @@ -12,7 +12,6 @@ import ( "testing" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/state" "github.com/stretchr/testify/assert" ) @@ -23,53 +22,52 @@ func TestTerminator(t *testing.T) { tfClient terraformClient newTfClientErr error libvirt *stubLibvirtRunner - state state.ConstellationState + provider cloudprovider.Provider wantErr bool }{ "gcp": { tfClient: &stubTerraformClient{}, - state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, + provider: cloudprovider.GCP, }, "gcp newTfClientErr": { newTfClientErr: someErr, - state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, + provider: cloudprovider.GCP, wantErr: true, }, "gcp destroy cluster error": { tfClient: &stubTerraformClient{destroyClusterErr: someErr}, - state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, + provider: cloudprovider.GCP, wantErr: true, }, "gcp clean up workspace error": { tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr}, - state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, + provider: cloudprovider.GCP, wantErr: true, }, "qemu": { tfClient: &stubTerraformClient{}, libvirt: &stubLibvirtRunner{}, - state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, + provider: cloudprovider.QEMU, }, "qemu destroy cluster error": { tfClient: &stubTerraformClient{destroyClusterErr: someErr}, libvirt: &stubLibvirtRunner{}, - state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, + provider: cloudprovider.QEMU, wantErr: true, }, "qemu clean up workspace error": { tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr}, libvirt: &stubLibvirtRunner{}, - state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, + provider: cloudprovider.QEMU, wantErr: true, }, "qemu stop libvirt error": { tfClient: &stubTerraformClient{}, libvirt: &stubLibvirtRunner{stopErr: someErr}, - state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, + provider: cloudprovider.QEMU, wantErr: true, }, "unknown cloud provider": { - state: state.ConstellationState{}, wantErr: true, }, } @@ -87,7 +85,7 @@ func TestTerminator(t *testing.T) { }, } - err := terminator.Terminate(context.Background(), tc.state) + err := terminator.Terminate(context.Background(), tc.provider) if tc.wantErr { assert.Error(err) @@ -96,7 +94,7 @@ func TestTerminator(t *testing.T) { cl := tc.tfClient.(*stubTerraformClient) assert.True(cl.destroyClusterCalled) assert.True(cl.removeInstallerCalled) - if cloudprovider.FromString(tc.state.CloudProvider) == cloudprovider.QEMU { + if tc.provider == cloudprovider.QEMU { assert.True(tc.libvirt.stopCalled) } } diff --git a/cli/internal/clusterid/id.go b/cli/internal/clusterid/id.go new file mode 100644 index 000000000..ad85c72ac --- /dev/null +++ b/cli/internal/clusterid/id.go @@ -0,0 +1,25 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package clusterid + +import ( + "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" +) + +// File contains identifying information about a cluster. +type File struct { + // ClusterID is the unique identifier of the cluster. + ClusterID string `json:"clusterID,omitempty"` + // OwnerID is the unique identifier of the owner of the cluster. + OwnerID string `json:"ownerID,omitempty"` + // UID is the unique identifier of the cluster, used for infrastructure management. + UID string `json:"uid,omitempty"` + // CloudProvider is the cloud provider of the cluster. + CloudProvider cloudprovider.Provider `json:"cloudprovider,omitempty"` + // IP is the IP address the cluster can be reached at (often the load balancer). + IP string `json:"ip,omitempty"` +} diff --git a/cli/internal/cmd/cloud.go b/cli/internal/cmd/cloud.go index f2bda090f..aad1be6ea 100644 --- a/cli/internal/cmd/cloud.go +++ b/cli/internal/cmd/cloud.go @@ -9,9 +9,9 @@ package cmd import ( "context" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" - "github.com/edgelesssys/constellation/v2/internal/state" ) type cloudCreator interface { @@ -21,9 +21,9 @@ type cloudCreator interface { config *config.Config, name, insType string, coordCount, nodeCount int, - ) (state.ConstellationState, error) + ) (clusterid.File, error) } type cloudTerminator interface { - Terminate(context.Context, state.ConstellationState) error + Terminate(context.Context, cloudprovider.Provider) error } diff --git a/cli/internal/cmd/cloud_test.go b/cli/internal/cmd/cloud_test.go index de80e4010..4269f8883 100644 --- a/cli/internal/cmd/cloud_test.go +++ b/cli/internal/cmd/cloud_test.go @@ -10,9 +10,9 @@ import ( "context" "testing" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" - "github.com/edgelesssys/constellation/v2/internal/state" "go.uber.org/goleak" ) @@ -25,7 +25,7 @@ func TestMain(m *testing.M) { type stubCloudCreator struct { createCalled bool - state state.ConstellationState + id clusterid.File createErr error } @@ -35,9 +35,10 @@ func (c *stubCloudCreator) Create( config *config.Config, name, insType string, coordCount, nodeCount int, -) (state.ConstellationState, error) { +) (clusterid.File, error) { c.createCalled = true - return c.state, c.createErr + c.id.CloudProvider = provider + return c.id, c.createErr } type stubCloudTerminator struct { @@ -45,7 +46,7 @@ type stubCloudTerminator struct { terminateErr error } -func (c *stubCloudTerminator) Terminate(context.Context, state.ConstellationState) error { +func (c *stubCloudTerminator) Terminate(context.Context, cloudprovider.Provider) error { c.called = true return c.terminateErr } diff --git a/cli/internal/cmd/create.go b/cli/internal/cmd/create.go index ac590cb10..1e7136019 100644 --- a/cli/internal/cmd/create.go +++ b/cli/internal/cmd/create.go @@ -15,7 +15,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" - "github.com/edgelesssys/constellation/v2/internal/state" "github.com/spf13/afero" "github.com/spf13/cobra" ) @@ -118,17 +117,13 @@ func create(cmd *cobra.Command, creator cloudCreator, fileHandler file.Handler, } spinner.Start("Creating", false) - state, err := creator.Create(cmd.Context(), provider, config, flags.name, instanceType, flags.controllerCount, flags.workerCount) + idFile, err := creator.Create(cmd.Context(), provider, config, flags.name, instanceType, flags.controllerCount, flags.workerCount) spinner.Stop() if err != nil { return err } - if err := fileHandler.WriteJSON(constants.StateFilename, state, file.OptNone); err != nil { - return err - } - - if err := writeIPtoIDFile(fileHandler, state); err != nil { + if err := fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone); err != nil { return err } @@ -195,9 +190,6 @@ type createFlags struct { // checkDirClean checks if files of a previous Constellation are left in the current working dir. func checkDirClean(fileHandler file.Handler) error { - if _, err := fileHandler.Stat(constants.StateFilename); !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf("file '%s' already exists in working directory, run 'constellation terminate' before creating a new one", constants.StateFilename) - } 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) } @@ -211,15 +203,6 @@ func checkDirClean(fileHandler file.Handler) error { return nil } -func writeIPtoIDFile(fileHandler file.Handler, state state.ConstellationState) error { - ip := state.LoadBalancerIP - if ip == "" { - return fmt.Errorf("bootstrapper ip not found") - } - idFile := clusterIDsFile{IP: ip} - return fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone) -} - func must(err error) { if err != nil { panic(err) diff --git a/cli/internal/cmd/create_test.go b/cli/internal/cmd/create_test.go index 150daa3e2..ef2d4c476 100644 --- a/cli/internal/cmd/create_test.go +++ b/cli/internal/cmd/create_test.go @@ -13,11 +13,11 @@ import ( "strings" "testing" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "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/edgelesssys/constellation/v2/internal/state" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,7 +30,7 @@ func TestCreate(t *testing.T) { require.NoError(file.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), provider))) return fs } - testState := state.ConstellationState{Name: "test", LoadBalancerIP: "192.0.2.1"} + idFile := clusterid.File{IP: "192.0.2.1"} someErr := errors.New("failed") testCases := map[string]struct { @@ -48,7 +48,7 @@ func TestCreate(t *testing.T) { }{ "create": { setupFs: fsWithDefaultConfig, - creator: &stubCloudCreator{state: testState}, + creator: &stubCloudCreator{id: idFile}, provider: cloudprovider.GCP, controllerCountFlag: intPtr(1), workerCountFlag: intPtr(2), @@ -56,7 +56,7 @@ func TestCreate(t *testing.T) { }, "interactive": { setupFs: fsWithDefaultConfig, - creator: &stubCloudCreator{state: testState}, + creator: &stubCloudCreator{id: idFile}, provider: cloudprovider.Azure, controllerCountFlag: intPtr(2), workerCountFlag: intPtr(1), @@ -119,21 +119,6 @@ func TestCreate(t *testing.T) { controllerCountFlag: intPtr(3), wantErr: true, }, - "old state in directory": { - setupFs: func(require *require.Assertions, csp cloudprovider.Provider) afero.Fs { - fs := afero.NewMemMapFs() - fileHandler := file.NewHandler(fs) - require.NoError(fileHandler.Write(constants.StateFilename, []byte{1}, file.OptNone)) - require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), csp))) - return fs - }, - creator: &stubCloudCreator{}, - provider: cloudprovider.GCP, - controllerCountFlag: intPtr(1), - workerCountFlag: intPtr(1), - yesFlag: true, - wantErr: true, - }, "old adminConf in directory": { setupFs: func(require *require.Assertions, csp cloudprovider.Provider) afero.Fs { fs := afero.NewMemMapFs() @@ -237,11 +222,12 @@ func TestCreate(t *testing.T) { assert.False(tc.creator.createCalled) } else { assert.True(tc.creator.createCalled) - var state state.ConstellationState - require.NoError(fileHandler.ReadJSON(constants.StateFilename, &state)) - var idFile clusterIDsFile - require.NoError(fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile)) - assert.Equal(state, testState) + var gotIDFile clusterid.File + require.NoError(fileHandler.ReadJSON(constants.ClusterIDsFileName, &gotIDFile)) + assert.Equal(gotIDFile, clusterid.File{ + IP: idFile.IP, + CloudProvider: tc.provider, + }) } } }) @@ -267,14 +253,9 @@ func TestCheckDirClean(t *testing.T) { existingFiles: []string{constants.MasterSecretFilename}, wantErr: true, }, - "state file exists": { - fileHandler: file.NewHandler(afero.NewMemMapFs()), - existingFiles: []string{constants.StateFilename}, - wantErr: true, - }, "multiple exist": { fileHandler: file.NewHandler(afero.NewMemMapFs()), - existingFiles: []string{constants.AdminConfFilename, constants.MasterSecretFilename, constants.StateFilename}, + existingFiles: []string{constants.AdminConfFilename, constants.MasterSecretFilename}, wantErr: true, }, } diff --git a/cli/internal/cmd/id.go b/cli/internal/cmd/id.go deleted file mode 100644 index d0abfa8c5..000000000 --- a/cli/internal/cmd/id.go +++ /dev/null @@ -1,13 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package cmd - -type clusterIDsFile struct { - ClusterID string `json:"clusterID,omitempty"` - OwnerID string `json:"ownerID,omitempty"` - IP string `json:"ip,omitempty"` -} diff --git a/cli/internal/cmd/init.go b/cli/internal/cmd/init.go index 3e21c3091..1935304bf 100644 --- a/cli/internal/cmd/init.go +++ b/cli/internal/cmd/init.go @@ -18,6 +18,7 @@ import ( "github.com/edgelesssys/constellation/v2/bootstrapper/initproto" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/helm" "github.com/edgelesssys/constellation/v2/internal/azureshared" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" @@ -48,7 +49,6 @@ func NewInitCmd() *cobra.Command { RunE: runInitialize, } cmd.Flags().String("master-secret", "", "path to base64-encoded master secret") - cmd.Flags().String("endpoint", "", "endpoint of the bootstrapper, passed as HOST[:PORT]") cmd.Flags().Bool("conformance", false, "enable conformance mode") return cmd } @@ -74,7 +74,7 @@ func runInitialize(cmd *cobra.Command, args []string) error { func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator) *dialer.Dialer, fileHandler file.Handler, helmLoader helmLoader, quotaChecker license.QuotaChecker, spinner spinnerInterf, ) error { - flags, err := evalFlagArgs(cmd, fileHandler) + flags, err := evalFlagArgs(cmd) if err != nil { return err } @@ -84,6 +84,11 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator return fmt.Errorf("reading and validating config: %w", err) } + var idFile clusterid.File + if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { + return fmt.Errorf("reading cluster ID file: %w", err) + } + k8sVersion, err := versions.NewValidK8sVersion(config.KubernetesVersion) if err != nil { return fmt.Errorf("validating kubernetes version: %w", err) @@ -142,13 +147,14 @@ func initialize(cmd *cobra.Command, newDialer func(validator *cloudcmd.Validator EnforceIdkeydigest: getEnforceIDKeyDigest(provider, config), ConformanceMode: flags.conformance, } - resp, err := initCall(cmd.Context(), newDialer(validator), flags.endpoint, req) + resp, err := initCall(cmd.Context(), newDialer(validator), idFile.IP, req) spinner.Stop() if err != nil { return err } - if err := writeOutput(resp, flags.endpoint, cmd.OutOrStdout(), fileHandler); err != nil { + idFile.CloudProvider = provider + if err := writeOutput(idFile, resp, cmd.OutOrStdout(), fileHandler); err != nil { return err } @@ -190,7 +196,7 @@ func (d *initDoer) Do(ctx context.Context) error { return nil } -func writeOutput(resp *initproto.InitResponse, ip string, wr io.Writer, fileHandler file.Handler) error { +func writeOutput(idFile clusterid.File, resp *initproto.InitResponse, wr io.Writer, fileHandler file.Handler) error { fmt.Fprint(wr, "Your Constellation cluster was successfully initialized.\n\n") ownerID := base64.StdEncoding.EncodeToString(resp.OwnerId) @@ -207,11 +213,9 @@ func writeOutput(resp *initproto.InitResponse, ip string, wr io.Writer, fileHand return fmt.Errorf("writing kubeconfig: %w", err) } - idFile := clusterIDsFile{ - ClusterID: clusterID, - OwnerID: ownerID, - IP: ip, - } + idFile.OwnerID = ownerID + idFile.ClusterID = clusterID + if err := fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptOverwrite); err != nil { return fmt.Errorf("writing Constellation id file: %w", err) } @@ -249,21 +253,11 @@ func getEnforceIDKeyDigest(provider cloudprovider.Provider, config *config.Confi // 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 evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, error) { +func 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) } - endpoint, err := cmd.Flags().GetString("endpoint") - if err != nil { - return initFlags{}, fmt.Errorf("parsing endpoint flag: %w", err) - } - if endpoint == "" { - endpoint, err = readIPFromIDFile(fileHandler) - if err != nil { - return initFlags{}, fmt.Errorf("getting bootstrapper endpoint: %w", err) - } - } conformance, err := cmd.Flags().GetBool("conformance") if err != nil { return initFlags{}, fmt.Errorf("parsing autoscale flag: %w", err) @@ -275,7 +269,6 @@ func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, erro return initFlags{ configPath: configPath, - endpoint: endpoint, conformance: conformance, masterSecretPath: masterSecretPath, }, nil @@ -285,7 +278,6 @@ func evalFlagArgs(cmd *cobra.Command, fileHandler file.Handler) (initFlags, erro type initFlags struct { configPath string masterSecretPath string - endpoint string conformance bool } @@ -334,7 +326,7 @@ func readOrGenerateMasterSecret(writer io.Writer, fileHandler file.Handler, file } func readIPFromIDFile(fileHandler file.Handler) (string, error) { - var idFile clusterIDsFile + var idFile clusterid.File if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { return "", err } diff --git a/cli/internal/cmd/init_test.go b/cli/internal/cmd/init_test.go index 893399dac..9341ff79b 100644 --- a/cli/internal/cmd/init_test.go +++ b/cli/internal/cmd/init_test.go @@ -20,8 +20,8 @@ import ( "github.com/edgelesssys/constellation/v2/bootstrapper/initproto" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" - "github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes" "github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" @@ -31,7 +31,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/grpc/testdialer" "github.com/edgelesssys/constellation/v2/internal/license" "github.com/edgelesssys/constellation/v2/internal/oid" - "github.com/edgelesssys/constellation/v2/internal/state" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -48,18 +47,9 @@ func TestInitArgumentValidation(t *testing.T) { } func TestInitialize(t *testing.T) { - testGcpState := &state.ConstellationState{ - CloudProvider: "GCP", - } gcpServiceAccKey := &gcpshared.ServiceAccountKey{ Type: "service_account", } - testAzureState := &state.ConstellationState{ - CloudProvider: "Azure", - } - testQemuState := &state.ConstellationState{ - CloudProvider: "QEMU", - } testInitResp := &initproto.InitResponse{ Kubeconfig: []byte("kubeconfig"), OwnerId: []byte("ownerID"), @@ -69,59 +59,51 @@ func TestInitialize(t *testing.T) { someErr := errors.New("failed") testCases := map[string]struct { - state *state.ConstellationState - idFile *clusterIDsFile + provider cloudprovider.Provider + idFile *clusterid.File configMutator func(*config.Config) serviceAccKey *gcpshared.ServiceAccountKey helmLoader stubHelmLoader initServerAPI *stubInitServer - endpointFlag string masterSecretShouldExist bool wantErr bool }{ "initialize some gcp instances": { - state: testGcpState, - idFile: &clusterIDsFile{IP: "192.0.2.1"}, + provider: cloudprovider.GCP, + idFile: &clusterid.File{IP: "192.0.2.1"}, configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath }, serviceAccKey: gcpServiceAccKey, initServerAPI: &stubInitServer{initResp: testInitResp}, }, "initialize some azure instances": { - state: testAzureState, - idFile: &clusterIDsFile{IP: "192.0.2.1"}, + provider: cloudprovider.Azure, + idFile: &clusterid.File{IP: "192.0.2.1"}, initServerAPI: &stubInitServer{initResp: testInitResp}, }, "initialize some qemu instances": { - state: testQemuState, - idFile: &clusterIDsFile{IP: "192.0.2.1"}, + provider: cloudprovider.QEMU, + idFile: &clusterid.File{IP: "192.0.2.1"}, initServerAPI: &stubInitServer{initResp: testInitResp}, }, - "initialize with endpoint flag": { - state: testGcpState, - configMutator: func(c *config.Config) { c.Provider.GCP.ServiceAccountKeyPath = serviceAccPath }, - serviceAccKey: gcpServiceAccKey, - initServerAPI: &stubInitServer{initResp: testInitResp}, - endpointFlag: "192.0.2.1", - }, - "empty state": { - state: &state.ConstellationState{}, - idFile: &clusterIDsFile{IP: "192.0.2.1"}, + "empty id file": { + provider: cloudprovider.GCP, + idFile: &clusterid.File{}, initServerAPI: &stubInitServer{}, wantErr: true, }, - "neither endpoint flag nor id file": { - state: &state.ConstellationState{}, - wantErr: true, + "no id file": { + provider: cloudprovider.GCP, + wantErr: true, }, "init call fails": { - state: testGcpState, - idFile: &clusterIDsFile{IP: "192.0.2.1"}, + provider: cloudprovider.GCP, + idFile: &clusterid.File{IP: "192.0.2.1"}, initServerAPI: &stubInitServer{initErr: someErr}, wantErr: true, }, "fail missing enforced PCR": { - state: testGcpState, - idFile: &clusterIDsFile{IP: "192.0.2.1"}, + provider: cloudprovider.GCP, + idFile: &clusterid.File{IP: "192.0.2.1"}, configMutator: func(c *config.Config) { c.Provider.GCP.EnforcedMeasurements = append(c.Provider.GCP.EnforcedMeasurements, 10) }, @@ -158,22 +140,17 @@ func TestInitialize(t *testing.T) { // Flags cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually - if tc.endpointFlag != "" { - require.NoError(cmd.Flags().Set("endpoint", tc.endpointFlag)) - } // File system preparation fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) - config := defaultConfigWithExpectedMeasurements(t, config.Default(), cloudprovider.FromString(tc.state.CloudProvider)) + config := defaultConfigWithExpectedMeasurements(t, config.Default(), tc.provider) if tc.configMutator != nil { tc.configMutator(config) } require.NoError(fileHandler.WriteYAML(constants.ConfigFilename, config, file.OptNone)) - if tc.state != nil { - require.NoError(fileHandler.WriteJSON(constants.StateFilename, tc.state, file.OptNone)) - } if tc.idFile != nil { + tc.idFile.CloudProvider = tc.provider require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, tc.idFile, file.OptNone)) } if tc.serviceAccKey != nil { @@ -218,17 +195,22 @@ func TestWriteOutput(t *testing.T) { ownerID := base64.StdEncoding.EncodeToString(resp.OwnerId) clusterID := base64.StdEncoding.EncodeToString(resp.ClusterId) - expectedIDFile := clusterIDsFile{ + expectedIDFile := clusterid.File{ ClusterID: clusterID, OwnerID: ownerID, IP: "cluster-ip", + UID: "test-uid", } var out bytes.Buffer testFs := afero.NewMemMapFs() fileHandler := file.NewHandler(testFs) - err := writeOutput(resp, "cluster-ip", &out, fileHandler) + idFile := clusterid.File{ + UID: "test-uid", + IP: "cluster-ip", + } + err := writeOutput(idFile, resp, &out, fileHandler) assert.NoError(err) // assert.Contains(out.String(), ownerID) assert.Contains(out.String(), clusterID) @@ -241,7 +223,7 @@ func TestWriteOutput(t *testing.T) { idsFile, err := afs.ReadFile(constants.ClusterIDsFileName) assert.NoError(err) - var testIDFile clusterIDsFile + var testIDFile clusterid.File err = json.Unmarshal(idsFile, &testIDFile) assert.NoError(err) assert.Equal(expectedIDFile, testIDFile) @@ -366,8 +348,7 @@ func TestAttestation(t *testing.T) { OwnerId: []byte("ownerID"), ClusterId: []byte("clusterID"), }} - existingState := state.ConstellationState{CloudProvider: "QEMU"} - existingIDFile := &clusterIDsFile{IP: "192.0.2.4"} + existingIDFile := &clusterid.File{IP: "192.0.2.4", CloudProvider: cloudprovider.QEMU} netDialer := testdialer.NewBufconnDialer() newDialer := func(v *cloudcmd.Validator) *dialer.Dialer { @@ -404,7 +385,6 @@ func TestAttestation(t *testing.T) { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) - require.NoError(fileHandler.WriteJSON(constants.StateFilename, existingState, file.OptNone)) require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, existingIDFile, file.OptNone)) cfg := config.Default() diff --git a/cli/internal/cmd/minidown.go b/cli/internal/cmd/minidown.go index 303c3fe34..fefe52fdc 100644 --- a/cli/internal/cmd/minidown.go +++ b/cli/internal/cmd/minidown.go @@ -11,10 +11,10 @@ import ( "fmt" "os" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" - "github.com/edgelesssys/constellation/v2/internal/state" "github.com/spf13/afero" "github.com/spf13/cobra" "go.uber.org/multierr" @@ -45,14 +45,14 @@ func runDown(cmd *cobra.Command, args []string) error { } func checkForMiniCluster(fileHandler file.Handler) error { - var state state.ConstellationState - if err := fileHandler.ReadJSON(constants.StateFilename, &state); err != nil { + var idFile clusterid.File + if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { return err } - if cloudprovider.FromString(state.CloudProvider) != cloudprovider.QEMU { + if idFile.CloudProvider != cloudprovider.QEMU { return errors.New("cluster is not a QEMU based Constellation") } - if state.Name != "mini" { + if idFile.UID != "mini" { return errors.New("cluster is not a mini Constellation cluster") } diff --git a/cli/internal/cmd/miniup.go b/cli/internal/cmd/miniup.go index b14ffdf8f..6a74f2d58 100644 --- a/cli/internal/cmd/miniup.go +++ b/cli/internal/cmd/miniup.go @@ -199,15 +199,14 @@ func prepareConfig(cmd *cobra.Command, fileHandler file.Handler) (*config.Config // createMiniCluster creates a new cluster using the given config. func createMiniCluster(ctx context.Context, fileHandler file.Handler, creator cloudCreator, config *config.Config) error { - state, err := creator.Create(ctx, cloudprovider.QEMU, config, "mini", "", 1, 1) + idFile, err := creator.Create(ctx, cloudprovider.QEMU, config, "mini", "", 1, 1) if err != nil { return err } - if err := fileHandler.WriteJSON(constants.StateFilename, state); err != nil { - return err - } - return writeIPtoIDFile(fileHandler, state) + idFile.UID = "mini" // use UID "mini" to identify mini constellation clusters. + + return fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone) } // initializeMiniCluster initializes a QEMU cluster. diff --git a/cli/internal/cmd/recover_test.go b/cli/internal/cmd/recover_test.go index e668c29fa..5f6c13387 100644 --- a/cli/internal/cmd/recover_test.go +++ b/cli/internal/cmd/recover_test.go @@ -16,6 +16,7 @@ import ( "time" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/disk-mapper/recoverproto" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" @@ -226,7 +227,7 @@ func TestParseRecoverFlags(t *testing.T) { fileHandler := file.NewHandler(afero.NewMemMapFs()) if tc.writeIDFile { - require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, &clusterIDsFile{IP: "192.0.2.42"})) + require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, &clusterid.File{IP: "192.0.2.42"})) } flags, err := parseRecoverFlags(cmd, fileHandler) diff --git a/cli/internal/cmd/terminate.go b/cli/internal/cmd/terminate.go index a5fd08334..dc2cb3d3f 100644 --- a/cli/internal/cmd/terminate.go +++ b/cli/internal/cmd/terminate.go @@ -16,9 +16,9 @@ import ( "go.uber.org/multierr" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" - "github.com/edgelesssys/constellation/v2/internal/state" ) // NewTerminateCmd returns a new cobra.Command for the terminate command. @@ -45,13 +45,13 @@ func runTerminate(cmd *cobra.Command, args []string) error { func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.Handler, spinner spinnerInterf, ) error { - var stat state.ConstellationState - if err := fileHandler.ReadJSON(constants.StateFilename, &stat); err != nil { - return fmt.Errorf("reading Constellation state: %w", err) + var idFile clusterid.File + if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err != nil { + return err } spinner.Start("Terminating", false) - err := terminator.Terminate(cmd.Context(), stat) + err := terminator.Terminate(cmd.Context(), idFile.CloudProvider) spinner.Stop() if err != nil { return fmt.Errorf("terminating Constellation cluster: %w", err) @@ -60,10 +60,6 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file. cmd.Println("Your Constellation cluster was terminated successfully.") var retErr error - if err := fileHandler.Remove(constants.StateFilename); err != nil { - retErr = multierr.Append(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.StateFilename)) - } - if err := fileHandler.Remove(constants.AdminConfFilename); err != nil && !errors.Is(err, fs.ErrNotExist) { retErr = multierr.Append(err, fmt.Errorf("failed to remove file: '%s', please remove it manually", constants.AdminConfFilename)) } diff --git a/cli/internal/cmd/terminate_test.go b/cli/internal/cmd/terminate_test.go index 0cca98423..b46894350 100644 --- a/cli/internal/cmd/terminate_test.go +++ b/cli/internal/cmd/terminate_test.go @@ -11,9 +11,10 @@ import ( "errors" "testing" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" + "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/file" - "github.com/edgelesssys/constellation/v2/internal/state" "github.com/spf13/afero" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -46,47 +47,46 @@ func TestTerminateCmdArgumentValidation(t *testing.T) { } func TestTerminate(t *testing.T) { - setupFs := func(require *require.Assertions, state state.ConstellationState) afero.Fs { + setupFs := func(require *require.Assertions, idFile clusterid.File) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone)) require.NoError(fileHandler.Write(constants.WGQuickConfigFilename, []byte{1, 2}, file.OptNone)) - require.NoError(fileHandler.Write(constants.ClusterIDsFileName, []byte{1, 2}, file.OptNone)) - require.NoError(fileHandler.WriteJSON(constants.StateFilename, state, file.OptNone)) + require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone)) return fs } someErr := errors.New("failed") testCases := map[string]struct { - state state.ConstellationState - setupFs func(*require.Assertions, state.ConstellationState) afero.Fs + idFile clusterid.File + setupFs func(*require.Assertions, clusterid.File) afero.Fs terminator spyCloudTerminator wantErr bool }{ "success": { - state: state.ConstellationState{CloudProvider: "gcp"}, + idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, setupFs: setupFs, terminator: &stubCloudTerminator{}, }, "files to remove do not exist": { - state: state.ConstellationState{CloudProvider: "gcp"}, - setupFs: func(require *require.Assertions, state state.ConstellationState) afero.Fs { + idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, + setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) - require.NoError(fileHandler.WriteJSON(constants.StateFilename, state, file.OptNone)) + require.NoError(fileHandler.WriteJSON(constants.ClusterIDsFileName, idFile, file.OptNone)) return fs }, terminator: &stubCloudTerminator{}, }, "terminate error": { - state: state.ConstellationState{CloudProvider: "gcp"}, + idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, setupFs: setupFs, terminator: &stubCloudTerminator{terminateErr: someErr}, wantErr: true, }, "missing state file": { - state: state.ConstellationState{CloudProvider: "gcp"}, - setupFs: func(require *require.Assertions, state state.ConstellationState) afero.Fs { + idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, + setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs { fs := afero.NewMemMapFs() fileHandler := file.NewHandler(fs) require.NoError(fileHandler.Write(constants.AdminConfFilename, []byte{1, 2}, file.OptNone)) @@ -97,9 +97,9 @@ func TestTerminate(t *testing.T) { wantErr: true, }, "remove file fails": { - state: state.ConstellationState{CloudProvider: "gcp"}, - setupFs: func(require *require.Assertions, state state.ConstellationState) afero.Fs { - fs := setupFs(require, state) + idFile: clusterid.File{CloudProvider: cloudprovider.GCP}, + setupFs: func(require *require.Assertions, idFile clusterid.File) afero.Fs { + fs := setupFs(require, idFile) return afero.NewReadOnlyFs(fs) }, terminator: &stubCloudTerminator{}, @@ -117,7 +117,7 @@ func TestTerminate(t *testing.T) { cmd.SetErr(&bytes.Buffer{}) require.NotNil(tc.setupFs) - fileHandler := file.NewHandler(tc.setupFs(require, tc.state)) + fileHandler := file.NewHandler(tc.setupFs(require, tc.idFile)) err := terminate(cmd, tc.terminator, fileHandler, nopSpinner{}) @@ -126,8 +126,6 @@ func TestTerminate(t *testing.T) { } else { assert.NoError(err) assert.True(tc.terminator.Called()) - _, err := fileHandler.Stat(constants.StateFilename) - assert.Error(err) _, err = fileHandler.Stat(constants.AdminConfFilename) assert.Error(err) _, err = fileHandler.Stat(constants.WGQuickConfigFilename) diff --git a/cli/internal/cmd/verify.go b/cli/internal/cmd/verify.go index a3e170c80..792ef624a 100644 --- a/cli/internal/cmd/verify.go +++ b/cli/internal/cmd/verify.go @@ -17,6 +17,7 @@ import ( "strings" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/crypto" @@ -117,7 +118,7 @@ func parseVerifyFlags(cmd *cobra.Command, fileHandler file.Handler) (verifyFlags emptyEndpoint := endpoint == "" emptyIDs := ownerID == "" && clusterID == "" if emptyEndpoint || emptyIDs { - var idFile clusterIDsFile + var idFile clusterid.File if err := fileHandler.ReadJSON(constants.ClusterIDsFileName, &idFile); err == nil { if emptyEndpoint { cmd.Printf("Using endpoint from %q. Specify --node-endpoint to override this.\n", constants.ClusterIDsFileName) diff --git a/cli/internal/cmd/verify_test.go b/cli/internal/cmd/verify_test.go index 6df8bc041..90a2bc7bb 100644 --- a/cli/internal/cmd/verify_test.go +++ b/cli/internal/cmd/verify_test.go @@ -16,6 +16,7 @@ import ( "strconv" "testing" + "github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/internal/atls" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" @@ -44,7 +45,7 @@ func TestVerify(t *testing.T) { configFlag string ownerIDFlag string clusterIDFlag string - idFile *clusterIDsFile + idFile *clusterid.File wantEndpoint string wantErr bool }{ @@ -79,7 +80,7 @@ func TestVerify(t *testing.T) { provider: cloudprovider.GCP, clusterIDFlag: zeroBase64, protoClient: &stubVerifyClient{}, - idFile: &clusterIDsFile{IP: "192.0.2.1"}, + idFile: &clusterid.File{IP: "192.0.2.1"}, wantEndpoint: "192.0.2.1:" + strconv.Itoa(constants.VerifyServiceNodePortGRPC), }, "override endpoint from details file": { @@ -87,7 +88,7 @@ func TestVerify(t *testing.T) { nodeEndpointFlag: "192.0.2.2:1234", clusterIDFlag: zeroBase64, protoClient: &stubVerifyClient{}, - idFile: &clusterIDsFile{IP: "192.0.2.1"}, + idFile: &clusterid.File{IP: "192.0.2.1"}, wantEndpoint: "192.0.2.2:1234", }, "invalid endpoint": { @@ -106,7 +107,7 @@ func TestVerify(t *testing.T) { provider: cloudprovider.GCP, nodeEndpointFlag: "192.0.2.1:1234", protoClient: &stubVerifyClient{}, - idFile: &clusterIDsFile{OwnerID: zeroBase64}, + idFile: &clusterid.File{OwnerID: zeroBase64}, wantEndpoint: "192.0.2.1:1234", }, "config file not existing": { diff --git a/cli/internal/terraform/terraform.go b/cli/internal/terraform/terraform.go index af1be1cca..72eecea6a 100644 --- a/cli/internal/terraform/terraform.go +++ b/cli/internal/terraform/terraform.go @@ -12,7 +12,6 @@ import ( "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/file" - "github.com/edgelesssys/constellation/v2/internal/state" "github.com/hashicorp/go-version" install "github.com/hashicorp/hc-install" "github.com/hashicorp/hc-install/fs" @@ -36,7 +35,6 @@ type Client struct { provider cloudprovider.Provider file file.Handler - state state.ConstellationState remove func() } @@ -58,43 +56,38 @@ func New(ctx context.Context, provider cloudprovider.Provider) (*Client, error) } // CreateCluster creates a Constellation cluster using Terraform. -func (c *Client) CreateCluster(ctx context.Context, name string, vars Variables) error { +func (c *Client) CreateCluster(ctx context.Context, name string, vars Variables) (string, error) { if err := prepareWorkspace(c.file, c.provider); err != nil { - return err + return "", err } if err := c.tf.Init(ctx); err != nil { - return err + return "", err } if err := c.file.Write(terraformVarsFile, []byte(vars.String())); err != nil { - return err + return "", err } if err := c.tf.Apply(ctx); err != nil { - return err + return "", err } tfState, err := c.tf.Show(ctx) if err != nil { - return err + return "", err } ipOutput, ok := tfState.Values.Outputs["ip"] if !ok { - return errors.New("no IP output found") + return "", errors.New("no IP output found") } ip, ok := ipOutput.Value.(string) if !ok { - return errors.New("invalid type in IP output: not a string") - } - c.state = state.ConstellationState{ - Name: name, - CloudProvider: c.provider.String(), - LoadBalancerIP: ip, + return "", errors.New("invalid type in IP output: not a string") } - return nil + return ip, nil } // DestroyInstances destroys a Constellation cluster using Terraform. @@ -132,11 +125,6 @@ func (c *Client) CleanUpWorkspace() error { return nil } -// GetState returns the state of the cluster. -func (c *Client) GetState() state.ConstellationState { - return c.state -} - // GetExecutable returns a Terraform executable either from the local filesystem, // or downloads the latest version fulfilling the version constraint. func GetExecutable(ctx context.Context, workingDir string) (terraform *tfexec.Terraform, remove func(), err error) { diff --git a/cli/internal/terraform/terraform_test.go b/cli/internal/terraform/terraform_test.go index fca377cf2..0193ac30a 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -127,13 +127,14 @@ func TestCreateCluster(t *testing.T) { file: file.NewHandler(tc.fs), } - err := c.CreateCluster(context.Background(), "test", tc.vars) + ip, err := c.CreateCluster(context.Background(), "test", tc.vars) if tc.wantErr { assert.Error(err) return } assert.NoError(err) + assert.Equal("192.0.2.100", ip) }) } } diff --git a/internal/cloud/cloudprovider/cloudprovider.go b/internal/cloud/cloudprovider/cloudprovider.go index dea05e296..0365ff546 100644 --- a/internal/cloud/cloudprovider/cloudprovider.go +++ b/internal/cloud/cloudprovider/cloudprovider.go @@ -6,7 +6,10 @@ SPDX-License-Identifier: AGPL-3.0-only package cloudprovider -import "strings" +import ( + "encoding/json" + "strings" +) //go:generate stringer -type=Provider @@ -21,6 +24,21 @@ const ( QEMU ) +// MarshalJSON marshals the Provider to JSON string. +func (p Provider) MarshalJSON() ([]byte, error) { + return json.Marshal(p.String()) +} + +// UnmarshalJSON unmarshals the Provider from JSON string. +func (p *Provider) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + *p = FromString(s) + return nil +} + // FromString returns cloud provider from string. func FromString(s string) Provider { s = strings.ToLower(s) diff --git a/internal/cloud/cloudprovider/cloudprovider_test.go b/internal/cloud/cloudprovider/cloudprovider_test.go new file mode 100644 index 000000000..76f918f89 --- /dev/null +++ b/internal/cloud/cloudprovider/cloudprovider_test.go @@ -0,0 +1,143 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package cloudprovider + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMarshalJSON(t *testing.T) { + testCases := map[string]struct { + input Provider + want []byte + }{ + "unknown": { + input: Unknown, + want: []byte("\"Unknown\""), + }, + "aws": { + input: AWS, + want: []byte("\"AWS\""), + }, + "azure": { + input: Azure, + want: []byte("\"Azure\""), + }, + "gcp": { + input: GCP, + want: []byte("\"GCP\""), + }, + "qemu": { + input: QEMU, + want: []byte("\"QEMU\""), + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + b, err := tc.input.MarshalJSON() + + assert.NoError(err) + assert.Equal(tc.want, b) + }) + } +} + +func TestUnmarshalJSON(t *testing.T) { + testCases := map[string]struct { + input []byte + want Provider + wantErr bool + }{ + "empty": { + input: []byte{}, + wantErr: true, + }, + "unknown": { + input: []byte("\"unknown\""), + want: Unknown, + }, + "aws": { + input: []byte("\"aws\""), + want: AWS, + }, + "azure": { + input: []byte("\"azure\""), + want: Azure, + }, + "gcp": { + input: []byte("\"gcp\""), + want: GCP, + }, + "qemu": { + input: []byte("\"qemu\""), + want: QEMU, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + var p Provider + err := p.UnmarshalJSON(tc.input) + + if tc.wantErr { + assert.Error(err) + } else { + assert.NoError(err) + assert.Equal(tc.want, p) + } + }) + } +} + +func TestFromString(t *testing.T) { + testCases := map[string]struct { + input string + want Provider + }{ + "empty": { + input: "", + want: Unknown, + }, + "unknown": { + input: "unknown", + want: Unknown, + }, + "aws": { + input: "aws", + want: AWS, + }, + "azure": { + input: "azure", + want: Azure, + }, + "gcp": { + input: "gcp", + want: GCP, + }, + "qemu": { + input: "qemu", + want: QEMU, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + + p := FromString(tc.input) + + assert.Equal(tc.want, p) + }) + } +} diff --git a/internal/constants/constants.go b/internal/constants/constants.go index dd33af5ed..a720c1148 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -58,7 +58,6 @@ const ( // // Filenames. // - StateFilename = "constellation-state.json" ClusterIDsFileName = "constellation-id.json" ConfigFilename = "constellation-conf.yaml" LicenseFilename = "constellation.license" diff --git a/internal/state/state.go b/internal/state/state.go deleted file mode 100644 index 0da95f562..000000000 --- a/internal/state/state.go +++ /dev/null @@ -1,15 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package state - -// ConstellationState is the state of a Constellation. -type ConstellationState struct { - Name string `json:"name,omitempty"` - UID string `json:"uid,omitempty"` - CloudProvider string `json:"cloudprovider,omitempty"` - LoadBalancerIP string `json:"bootstrapperhost,omitempty"` -}