cli: add --workspace flag to set base directory for Constellation workspace (#2148)

* 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 <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-08-04 13:53:51 +02:00 committed by GitHub
parent ec33530c38
commit d1ace13713
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 966 additions and 1145 deletions

View File

@ -7,7 +7,6 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//cli/internal/cmd",
"//internal/constants",
"@com_github_spf13_cobra//:cobra",
],
)

View File

@ -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) {

View File

@ -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",

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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",

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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())

View File

@ -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

View File

@ -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())

View File

@ -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
}

View File

@ -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:

View File

@ -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)

View File

@ -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.

View File

@ -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))

View File

@ -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
}

View File

@ -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)
})
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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) {

View File

@ -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)}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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)
})
}
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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}

View File

@ -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
```

View File

@ -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))

View File

@ -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.