Skip to content

Commit

Permalink
cli: generate state file during constellation config generate (#2455)
Browse files Browse the repository at this point in the history
* create state file during config generate

* use written file in `constellation create`

* document creation of state file

* remove accidentally added test

* check error when writing state file
  • Loading branch information
msanft authored Oct 16, 2023
1 parent e5513f1 commit 25b2368
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 22 deletions.
23 changes: 20 additions & 3 deletions cli/internal/cmd/configgenerate.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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),
Expand Down Expand Up @@ -92,18 +93,34 @@ 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)
}
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")

Expand Down
13 changes: 13 additions & 0 deletions cli/internal/cmd/configgenerate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
}
})
}
}
Expand Down
15 changes: 6 additions & 9 deletions cli/internal/cmd/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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
}
Expand Down
33 changes: 27 additions & 6 deletions cli/internal/cmd/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
Expand All @@ -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",
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/getting-started/first-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<tabs groupId="csp">

Expand Down
6 changes: 3 additions & 3 deletions docs/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand Down

0 comments on commit 25b2368

Please sign in to comment.