diff --git a/cli/internal/cmd/configgenerate.go b/cli/internal/cmd/configgenerate.go index 7a8f05ec71..36c7448917 100644 --- a/cli/internal/cmd/configgenerate.go +++ b/cli/internal/cmd/configgenerate.go @@ -10,6 +10,7 @@ import ( "fmt" "strings" + "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" @@ -25,8 +26,8 @@ import ( func newConfigGenerateCmd() *cobra.Command { cmd := &cobra.Command{ Use: "generate {aws|azure|gcp|openstack|qemu|stackit}", - Short: "Generate a default configuration file", - Long: "Generate a default configuration file for your selected cloud provider.", + Short: "Generate a default configuration and state file", + Long: "Generate a default configuration and state file for your selected cloud provider.", Args: cobra.MatchAll( cobra.ExactArgs(1), isCloudProvider(0), @@ -92,6 +93,8 @@ func runConfigGenerate(cmd *cobra.Command, args []string) error { func (cg *configGenerateCmd) configGenerate(cmd *cobra.Command, fileHandler file.Handler, provider cloudprovider.Provider, rawProvider string) error { cg.log.Debugf("Using cloud provider %s", provider.String()) + + // Config creation conf, err := createConfigWithAttestationVariant(provider, rawProvider, cg.flags.attestationVariant) if err != nil { return fmt.Errorf("creating config: %w", err) @@ -99,11 +102,25 @@ func (cg *configGenerateCmd) configGenerate(cmd *cobra.Command, fileHandler file conf.KubernetesVersion = cg.flags.k8sVersion cg.log.Debugf("Writing YAML data to configuration file") if err := fileHandler.WriteYAML(constants.ConfigFilename, conf, file.OptMkdirAll); err != nil { - return err + return fmt.Errorf("writing config file: %w", err) } cmd.Println("Config file written to", cg.flags.pathPrefixer.PrefixPrintablePath(constants.ConfigFilename)) cmd.Println("Please fill in your CSP-specific configuration before proceeding.") + + // State-file creation + stateFile := state.New() + switch provider { + case cloudprovider.GCP: + stateFile.SetInfrastructure(state.Infrastructure{GCP: &state.GCP{}}) + case cloudprovider.Azure: + stateFile.SetInfrastructure(state.Infrastructure{Azure: &state.Azure{}}) + } + if err = stateFile.WriteToFile(fileHandler, constants.StateFilename); err != nil { + return fmt.Errorf("writing state file: %w", err) + } + cmd.Println("State file written to", cg.flags.pathPrefixer.PrefixPrintablePath(constants.StateFilename)) + cmd.Println("For more information refer to the documentation:") cmd.Println("\thttps://docs.edgeless.systems/constellation/getting-started/first-steps") diff --git a/cli/internal/cmd/configgenerate_test.go b/cli/internal/cmd/configgenerate_test.go index c0be7160fc..6c84179889 100644 --- a/cli/internal/cmd/configgenerate_test.go +++ b/cli/internal/cmd/configgenerate_test.go @@ -11,6 +11,7 @@ import ( "strings" "testing" + "github.com/edgelesssys/constellation/v2/cli/internal/state" "github.com/edgelesssys/constellation/v2/internal/attestation/variant" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/config" @@ -103,6 +104,9 @@ func TestConfigGenerateDefault(t *testing.T) { err := fileHandler.ReadYAML(constants.ConfigFilename, &readConfig) assert.NoError(err) assert.Equal(*config.Default(), readConfig) + + _, err = state.ReadFromFile(fileHandler, constants.StateFilename) + assert.NoError(err) } func TestConfigGenerateDefaultProviderSpecific(t *testing.T) { @@ -152,6 +156,15 @@ func TestConfigGenerateDefaultProviderSpecific(t *testing.T) { err := fileHandler.ReadYAML(constants.ConfigFilename, &readConfig) assert.NoError(err) assert.Equal(*wantConf, readConfig) + + stateFile, err := state.ReadFromFile(fileHandler, constants.StateFilename) + assert.NoError(err) + switch tc.provider { + case cloudprovider.GCP: + assert.NotNil(stateFile.Infrastructure.GCP) + case cloudprovider.Azure: + assert.NotNil(stateFile.Infrastructure.Azure) + } }) } } diff --git a/cli/internal/cmd/create.go b/cli/internal/cmd/create.go index 4c3140227a..c9be99d3c9 100644 --- a/cli/internal/cmd/create.go +++ b/cli/internal/cmd/create.go @@ -191,8 +191,12 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler } c.log.Debugf("Successfully created the cloud resources for the cluster") - state := state.New().SetInfrastructure(infraState) - if err := state.WriteToFile(fileHandler, constants.StateFilename); err != nil { + stateFile, err := state.ReadFromFile(fileHandler, constants.StateFilename) + if err != nil { + return fmt.Errorf("reading state file: %w", err) + } + stateFile = stateFile.SetInfrastructure(infraState) + if err := stateFile.WriteToFile(fileHandler, constants.StateFilename); err != nil { return fmt.Errorf("writing state file: %w", err) } @@ -216,13 +220,6 @@ func (c *createCmd) checkDirClean(fileHandler file.Handler) error { c.flags.pathPrefixer.PrefixPrintablePath(constants.MasterSecretFilename), ) } - c.log.Debugf("Checking state file") - if _, err := fileHandler.Stat(constants.StateFilename); !errors.Is(err, fs.ErrNotExist) { - return fmt.Errorf( - "file '%s' already exists in working directory. Constellation won't overwrite previous cluster state. Move it somewhere or delete it before creating a new cluster", - c.flags.pathPrefixer.PrefixPrintablePath(constants.StateFilename), - ) - } return nil } diff --git a/cli/internal/cmd/create_test.go b/cli/internal/cmd/create_test.go index 8767662137..5df2f7fa71 100644 --- a/cli/internal/cmd/create_test.go +++ b/cli/internal/cmd/create_test.go @@ -24,7 +24,21 @@ import ( ) func TestCreate(t *testing.T) { - fsWithDefaultConfig := func(require *require.Assertions, provider cloudprovider.Provider) afero.Fs { + fsWithDefaultConfigAndState := func(require *require.Assertions, provider cloudprovider.Provider) afero.Fs { + fs := afero.NewMemMapFs() + file := file.NewHandler(fs) + require.NoError(file.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), provider))) + stateFile := state.New() + switch provider { + case cloudprovider.GCP: + stateFile.SetInfrastructure(state.Infrastructure{GCP: &state.GCP{}}) + case cloudprovider.Azure: + stateFile.SetInfrastructure(state.Infrastructure{Azure: &state.Azure{}}) + } + require.NoError(stateFile.WriteToFile(file, constants.StateFilename)) + return fs + } + fsWithoutState := func(require *require.Assertions, provider cloudprovider.Provider) afero.Fs { fs := afero.NewMemMapFs() file := file.NewHandler(fs) require.NoError(file.WriteYAML(constants.ConfigFilename, defaultConfigWithExpectedMeasurements(t, config.Default(), provider))) @@ -45,26 +59,26 @@ func TestCreate(t *testing.T) { wantAbort bool }{ "create": { - setupFs: fsWithDefaultConfig, + setupFs: fsWithDefaultConfigAndState, creator: &stubCloudCreator{state: infraState}, provider: cloudprovider.GCP, yesFlag: true, }, "interactive": { - setupFs: fsWithDefaultConfig, + setupFs: fsWithDefaultConfigAndState, creator: &stubCloudCreator{state: infraState}, provider: cloudprovider.Azure, stdin: "yes\n", }, "interactive abort": { - setupFs: fsWithDefaultConfig, + setupFs: fsWithDefaultConfigAndState, creator: &stubCloudCreator{}, provider: cloudprovider.GCP, stdin: "no\n", wantAbort: true, }, "interactive error": { - setupFs: fsWithDefaultConfig, + setupFs: fsWithDefaultConfigAndState, creator: &stubCloudCreator{}, provider: cloudprovider.GCP, stdin: "foo\nfoo\nfoo\n", @@ -103,8 +117,15 @@ func TestCreate(t *testing.T) { yesFlag: true, wantErr: true, }, + "state file does not exist": { + setupFs: fsWithoutState, + creator: &stubCloudCreator{}, + provider: cloudprovider.GCP, + yesFlag: true, + wantErr: true, + }, "create error": { - setupFs: fsWithDefaultConfig, + setupFs: fsWithDefaultConfigAndState, creator: &stubCloudCreator{createErr: someErr}, provider: cloudprovider.GCP, yesFlag: true, diff --git a/docs/docs/getting-started/first-steps.md b/docs/docs/getting-started/first-steps.md index 07b7f84106..6435271b88 100644 --- a/docs/docs/getting-started/first-steps.md +++ b/docs/docs/getting-started/first-steps.md @@ -13,7 +13,7 @@ If you encounter any problem with the following steps, make sure to use the [lat ## Create a cluster -1. Create the [configuration file](../workflows/config.md) for your cloud provider. +1. Create the [configuration file](../workflows/config.md) and state file for your cloud provider. diff --git a/docs/docs/reference/cli.md b/docs/docs/reference/cli.md index b5230e279a..a8556fc28b 100644 --- a/docs/docs/reference/cli.md +++ b/docs/docs/reference/cli.md @@ -12,7 +12,7 @@ constellation [command] Commands: * [config](#constellation-config): Work with the Constellation configuration file - * [generate](#constellation-config-generate): Generate a default configuration file + * [generate](#constellation-config-generate): Generate a default configuration and state file * [fetch-measurements](#constellation-config-fetch-measurements): Fetch measurements for configured cloud provider and image * [instance-types](#constellation-config-instance-types): Print the supported instance types for all cloud providers * [kubernetes-versions](#constellation-config-kubernetes-versions): Print the Kubernetes versions supported by this CLI @@ -64,11 +64,11 @@ Work with the Constellation configuration file. ## constellation config generate -Generate a default configuration file +Generate a default configuration and state file ### Synopsis -Generate a default configuration file for your selected cloud provider. +Generate a default configuration and state file for your selected cloud provider. ``` constellation config generate {aws|azure|gcp|openstack|qemu|stackit} [flags]