cli: add Terraform log support (#1620)

* add Terraform logging

* add TF logging to CLI

* fix path

* only create file if logging is enabled

* update bazel files

* register persistent flags manually

* clidocgen

* move logging code to separate file

* reword yes flag parsing error

* update bazel buildfile

* factor out log level setting
This commit is contained in:
Moritz Sanft 2023-04-14 14:15:07 +02:00 committed by GitHub
parent ca1400819d
commit 1d0ee796e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 688 additions and 238 deletions

1
.gitignore vendored
View file

@ -46,6 +46,7 @@ image/config.mk
.terraform .terraform
.terraform.tfstate.lock.info .terraform.tfstate.lock.info
*.tfvars *.tfvars
terraform.log
# macOS # macOS
.DS_Store .DS_Store

View file

@ -48,6 +48,7 @@ func NewRootCmd() *cobra.Command {
rootCmd.PersistentFlags().Bool("debug", false, "enable debug logging") rootCmd.PersistentFlags().Bool("debug", false, "enable debug logging")
rootCmd.PersistentFlags().Bool("force", false, "disable version compatibility checks - might result in corrupted clusters") rootCmd.PersistentFlags().Bool("force", false, "disable version compatibility checks - might result in corrupted clusters")
rootCmd.PersistentFlags().String("tf-log", "NONE", "sets the Terraform log level (default \"NONE\" - no logs)")
rootCmd.AddCommand(cmd.NewConfigCmd()) rootCmd.AddCommand(cmd.NewConfigCmd())
rootCmd.AddCommand(cmd.NewCreateCmd()) rootCmd.AddCommand(cmd.NewCreateCmd())

View file

@ -23,9 +23,9 @@ type imageFetcher interface {
type terraformClient interface { type terraformClient interface {
PrepareWorkspace(path string, input terraform.Variables) error PrepareWorkspace(path string, input terraform.Variables) error
CreateCluster(ctx context.Context) (terraform.CreateOutput, error) CreateCluster(ctx context.Context, logLevel terraform.LogLevel) (terraform.CreateOutput, error)
CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider) (terraform.IAMOutput, error) CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider, logLevel terraform.LogLevel) (terraform.IAMOutput, error)
Destroy(ctx context.Context) error Destroy(ctx context.Context, logLevel terraform.LogLevel) error
CleanUpWorkspace() error CleanUpWorkspace() error
RemoveInstaller() RemoveInstaller()
Show(ctx context.Context) (*tfjson.State, error) Show(ctx context.Context) (*tfjson.State, error)

View file

@ -45,7 +45,7 @@ type stubTerraformClient struct {
showErr error showErr error
} }
func (c *stubTerraformClient) CreateCluster(_ context.Context) (terraform.CreateOutput, error) { func (c *stubTerraformClient) CreateCluster(_ context.Context, _ terraform.LogLevel) (terraform.CreateOutput, error) {
return terraform.CreateOutput{ return terraform.CreateOutput{
IP: c.ip, IP: c.ip,
Secret: c.initSecret, Secret: c.initSecret,
@ -54,7 +54,7 @@ func (c *stubTerraformClient) CreateCluster(_ context.Context) (terraform.Create
}, c.createClusterErr }, c.createClusterErr
} }
func (c *stubTerraformClient) CreateIAMConfig(_ context.Context, _ cloudprovider.Provider) (terraform.IAMOutput, error) { func (c *stubTerraformClient) CreateIAMConfig(_ context.Context, _ cloudprovider.Provider, _ terraform.LogLevel) (terraform.IAMOutput, error) {
return c.iamOutput, c.iamOutputErr return c.iamOutput, c.iamOutputErr
} }
@ -62,7 +62,7 @@ func (c *stubTerraformClient) PrepareWorkspace(_ string, _ terraform.Variables)
return c.prepareWorkspaceErr return c.prepareWorkspaceErr
} }
func (c *stubTerraformClient) Destroy(_ context.Context) error { func (c *stubTerraformClient) Destroy(_ context.Context, _ terraform.LogLevel) error {
c.destroyCalled = true c.destroyCalled = true
return c.destroyErr return c.destroyErr
} }

View file

@ -63,43 +63,54 @@ func NewCreator(out io.Writer) *Creator {
} }
} }
// CreateOptions are the options for creating a Constellation cluster.
type CreateOptions struct {
Provider cloudprovider.Provider
Config *config.Config
InsType string
ControlPlaneCount int
WorkerCount int
image string
TFLogLevel terraform.LogLevel
}
// Create creates the handed amount of instances and all the needed resources. // Create creates the handed amount of instances and all the needed resources.
func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, config *config.Config, insType string, controlPlaneCount, workerCount int, func (c *Creator) Create(ctx context.Context, opts CreateOptions) (clusterid.File, error) {
) (clusterid.File, error) { image, err := c.image.FetchReference(ctx, opts.Config)
image, err := c.image.FetchReference(ctx, config)
if err != nil { if err != nil {
return clusterid.File{}, fmt.Errorf("fetching image reference: %w", err) return clusterid.File{}, fmt.Errorf("fetching image reference: %w", err)
} }
opts.image = image
switch provider { switch opts.Provider {
case cloudprovider.AWS: case cloudprovider.AWS:
cl, err := c.newTerraformClient(ctx) cl, err := c.newTerraformClient(ctx)
if err != nil { if err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createAWS(ctx, cl, config, insType, controlPlaneCount, workerCount, image) return c.createAWS(ctx, cl, opts)
case cloudprovider.GCP: case cloudprovider.GCP:
cl, err := c.newTerraformClient(ctx) cl, err := c.newTerraformClient(ctx)
if err != nil { if err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createGCP(ctx, cl, config, insType, controlPlaneCount, workerCount, image) return c.createGCP(ctx, cl, opts)
case cloudprovider.Azure: case cloudprovider.Azure:
cl, err := c.newTerraformClient(ctx) cl, err := c.newTerraformClient(ctx)
if err != nil { if err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createAzure(ctx, cl, config, insType, controlPlaneCount, workerCount, image) return c.createAzure(ctx, cl, opts)
case cloudprovider.OpenStack: case cloudprovider.OpenStack:
cl, err := c.newTerraformClient(ctx) cl, err := c.newTerraformClient(ctx)
if err != nil { if err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createOpenStack(ctx, cl, config, controlPlaneCount, workerCount, image) return c.createOpenStack(ctx, cl, opts)
case cloudprovider.QEMU: case cloudprovider.QEMU:
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" { if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" {
return clusterid.File{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH) return clusterid.File{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH)
@ -110,38 +121,40 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
lv := c.newLibvirtRunner() lv := c.newLibvirtRunner()
return c.createQEMU(ctx, cl, lv, config, controlPlaneCount, workerCount, image) qemuOpts := qemuCreateOptions{
source: image,
CreateOptions: opts,
}
return c.createQEMU(ctx, cl, lv, qemuOpts)
default: default:
return clusterid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider) return clusterid.File{}, fmt.Errorf("unsupported cloud provider: %s", opts.Provider)
} }
} }
func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *config.Config, func (c *Creator) createAWS(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) {
insType string, controlPlaneCount, workerCount int, image string,
) (idFile clusterid.File, retErr error) {
vars := terraform.AWSClusterVariables{ vars := terraform.AWSClusterVariables{
CommonVariables: terraform.CommonVariables{ CommonVariables: terraform.CommonVariables{
Name: config.Name, Name: opts.Config.Name,
CountControlPlanes: controlPlaneCount, CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: workerCount, CountWorkers: opts.WorkerCount,
StateDiskSizeGB: config.StateDiskSizeGB, StateDiskSizeGB: opts.Config.StateDiskSizeGB,
}, },
StateDiskType: config.Provider.AWS.StateDiskType, StateDiskType: opts.Config.Provider.AWS.StateDiskType,
Region: config.Provider.AWS.Region, Region: opts.Config.Provider.AWS.Region,
Zone: config.Provider.AWS.Zone, Zone: opts.Config.Provider.AWS.Zone,
InstanceType: insType, InstanceType: opts.InsType,
AMIImageID: image, AMIImageID: opts.image,
IAMProfileControlPlane: config.Provider.AWS.IAMProfileControlPlane, IAMProfileControlPlane: opts.Config.Provider.AWS.IAMProfileControlPlane,
IAMProfileWorkerNodes: config.Provider.AWS.IAMProfileWorkerNodes, IAMProfileWorkerNodes: opts.Config.Provider.AWS.IAMProfileWorkerNodes,
Debug: config.IsDebugCluster(), Debug: opts.Config.IsDebugCluster(),
} }
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}) defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
tfOutput, err := cl.CreateCluster(ctx) tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil { if err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
@ -154,32 +167,30 @@ func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *con
}, nil }, nil
} }
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config, func (c *Creator) createGCP(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) {
insType string, controlPlaneCount, workerCount int, image string,
) (idFile clusterid.File, retErr error) {
vars := terraform.GCPClusterVariables{ vars := terraform.GCPClusterVariables{
CommonVariables: terraform.CommonVariables{ CommonVariables: terraform.CommonVariables{
Name: config.Name, Name: opts.Config.Name,
CountControlPlanes: controlPlaneCount, CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: workerCount, CountWorkers: opts.WorkerCount,
StateDiskSizeGB: config.StateDiskSizeGB, StateDiskSizeGB: opts.Config.StateDiskSizeGB,
}, },
Project: config.Provider.GCP.Project, Project: opts.Config.Provider.GCP.Project,
Region: config.Provider.GCP.Region, Region: opts.Config.Provider.GCP.Region,
Zone: config.Provider.GCP.Zone, Zone: opts.Config.Provider.GCP.Zone,
CredentialsFile: config.Provider.GCP.ServiceAccountKeyPath, CredentialsFile: opts.Config.Provider.GCP.ServiceAccountKeyPath,
InstanceType: insType, InstanceType: opts.InsType,
StateDiskType: config.Provider.GCP.StateDiskType, StateDiskType: opts.Config.Provider.GCP.StateDiskType,
ImageID: image, ImageID: opts.image,
Debug: config.IsDebugCluster(), Debug: opts.Config.IsDebugCluster(),
} }
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}) defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
tfOutput, err := cl.CreateCluster(ctx) tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil { if err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
@ -192,27 +203,26 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
}, nil }, nil
} }
func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config, insType string, controlPlaneCount, workerCount int, image string, func (c *Creator) createAzure(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) {
) (idFile clusterid.File, retErr error) {
vars := terraform.AzureClusterVariables{ vars := terraform.AzureClusterVariables{
CommonVariables: terraform.CommonVariables{ CommonVariables: terraform.CommonVariables{
Name: config.Name, Name: opts.Config.Name,
CountControlPlanes: controlPlaneCount, CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: workerCount, CountWorkers: opts.WorkerCount,
StateDiskSizeGB: config.StateDiskSizeGB, StateDiskSizeGB: opts.Config.StateDiskSizeGB,
}, },
Location: config.Provider.Azure.Location, Location: opts.Config.Provider.Azure.Location,
ResourceGroup: config.Provider.Azure.ResourceGroup, ResourceGroup: opts.Config.Provider.Azure.ResourceGroup,
UserAssignedIdentity: config.Provider.Azure.UserAssignedIdentity, UserAssignedIdentity: opts.Config.Provider.Azure.UserAssignedIdentity,
InstanceType: insType, InstanceType: opts.InsType,
StateDiskType: config.Provider.Azure.StateDiskType, StateDiskType: opts.Config.Provider.Azure.StateDiskType,
ImageID: image, ImageID: opts.image,
SecureBoot: *config.Provider.Azure.SecureBoot, SecureBoot: *opts.Config.Provider.Azure.SecureBoot,
CreateMAA: config.Provider.Azure.EnforceIDKeyDigest == idkeydigest.MAAFallback, CreateMAA: opts.Config.Provider.Azure.EnforceIDKeyDigest == idkeydigest.MAAFallback,
Debug: config.IsDebugCluster(), Debug: opts.Config.IsDebugCluster(),
} }
attestVariant, err := variant.FromString(config.AttestationVariant) attestVariant, err := variant.FromString(opts.Config.AttestationVariant)
if err != nil { if err != nil {
return clusterid.File{}, fmt.Errorf("parsing attestation variant: %w", err) return clusterid.File{}, fmt.Errorf("parsing attestation variant: %w", err)
} }
@ -224,8 +234,8 @@ func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *c
return clusterid.File{}, err return clusterid.File{}, err
} }
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}) defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
tfOutput, err := cl.CreateCluster(ctx) tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil { if err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
@ -348,14 +358,12 @@ func normalizeAzureURIs(vars terraform.AzureClusterVariables) terraform.AzureClu
return vars return vars
} }
func (c *Creator) createOpenStack(ctx context.Context, cl terraformClient, config *config.Config, func (c *Creator) createOpenStack(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) {
controlPlaneCount, workerCount int, image string,
) (idFile clusterid.File, retErr error) {
// TODO: Remove this once OpenStack is supported. // TODO: Remove this once OpenStack is supported.
if os.Getenv("CONSTELLATION_OPENSTACK_DEV") != "1" { if os.Getenv("CONSTELLATION_OPENSTACK_DEV") != "1" {
return clusterid.File{}, errors.New("OpenStack isn't supported yet") return clusterid.File{}, errors.New("OpenStack isn't supported yet")
} }
if _, hasOSAuthURL := os.LookupEnv("OS_AUTH_URL"); !hasOSAuthURL && config.Provider.OpenStack.Cloud == "" { if _, hasOSAuthURL := os.LookupEnv("OS_AUTH_URL"); !hasOSAuthURL && opts.Config.Provider.OpenStack.Cloud == "" {
return clusterid.File{}, errors.New( return clusterid.File{}, errors.New(
"neither environment variable OS_AUTH_URL nor cloud name for \"clouds.yaml\" is set. OpenStack authentication requires a set of " + "neither environment variable OS_AUTH_URL nor cloud name for \"clouds.yaml\" is set. OpenStack authentication requires a set of " +
"OS_* environment variables that are typically sourced into the current shell with an openrc file " + "OS_* environment variables that are typically sourced into the current shell with an openrc file " +
@ -366,29 +374,29 @@ func (c *Creator) createOpenStack(ctx context.Context, cl terraformClient, confi
vars := terraform.OpenStackClusterVariables{ vars := terraform.OpenStackClusterVariables{
CommonVariables: terraform.CommonVariables{ CommonVariables: terraform.CommonVariables{
Name: config.Name, Name: opts.Config.Name,
CountControlPlanes: controlPlaneCount, CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: workerCount, CountWorkers: opts.WorkerCount,
StateDiskSizeGB: config.StateDiskSizeGB, StateDiskSizeGB: opts.Config.StateDiskSizeGB,
}, },
Cloud: config.Provider.OpenStack.Cloud, Cloud: opts.Config.Provider.OpenStack.Cloud,
AvailabilityZone: config.Provider.OpenStack.AvailabilityZone, AvailabilityZone: opts.Config.Provider.OpenStack.AvailabilityZone,
FloatingIPPoolID: config.Provider.OpenStack.FloatingIPPoolID, FloatingIPPoolID: opts.Config.Provider.OpenStack.FloatingIPPoolID,
FlavorID: config.Provider.OpenStack.FlavorID, FlavorID: opts.Config.Provider.OpenStack.FlavorID,
ImageURL: image, ImageURL: opts.image,
DirectDownload: *config.Provider.OpenStack.DirectDownload, DirectDownload: *opts.Config.Provider.OpenStack.DirectDownload,
OpenstackUserDomainName: config.Provider.OpenStack.UserDomainName, OpenstackUserDomainName: opts.Config.Provider.OpenStack.UserDomainName,
OpenstackUsername: config.Provider.OpenStack.Username, OpenstackUsername: opts.Config.Provider.OpenStack.Username,
OpenstackPassword: config.Provider.OpenStack.Password, OpenstackPassword: opts.Config.Provider.OpenStack.Password,
Debug: config.IsDebugCluster(), Debug: opts.Config.IsDebugCluster(),
} }
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.OpenStack.String())), &vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.OpenStack.String())), &vars); err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}) defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
tfOutput, err := cl.CreateCluster(ctx) tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil { if err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
@ -401,26 +409,29 @@ func (c *Creator) createOpenStack(ctx context.Context, cl terraformClient, confi
}, nil }, nil
} }
func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner, config *config.Config, type qemuCreateOptions struct {
controlPlaneCount, workerCount int, source string, source string
) (idFile clusterid.File, retErr error) { CreateOptions
}
func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner, opts qemuCreateOptions) (idFile clusterid.File, retErr error) {
qemuRollbacker := &rollbackerQEMU{client: cl, libvirt: lv, createdWorkspace: false} qemuRollbacker := &rollbackerQEMU{client: cl, libvirt: lv, createdWorkspace: false}
defer rollbackOnError(c.out, &retErr, qemuRollbacker) defer rollbackOnError(c.out, &retErr, qemuRollbacker, opts.TFLogLevel)
// TODO: render progress bar // TODO: render progress bar
downloader := c.newRawDownloader() downloader := c.newRawDownloader()
imagePath, err := downloader.Download(ctx, c.out, false, source, config.Image) imagePath, err := downloader.Download(ctx, c.out, false, opts.source, opts.Config.Image)
if err != nil { if err != nil {
return clusterid.File{}, fmt.Errorf("download raw image: %w", err) return clusterid.File{}, fmt.Errorf("download raw image: %w", err)
} }
libvirtURI := config.Provider.QEMU.LibvirtURI libvirtURI := opts.Config.Provider.QEMU.LibvirtURI
libvirtSocketPath := "." libvirtSocketPath := "."
switch { switch {
// if no libvirt URI is specified, start a libvirt container // if no libvirt URI is specified, start a libvirt container
case libvirtURI == "": case libvirtURI == "":
if err := lv.Start(ctx, config.Name, config.Provider.QEMU.LibvirtContainerImage); err != nil { if err := lv.Start(ctx, opts.Config.Name, opts.Config.Provider.QEMU.LibvirtContainerImage); err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }
libvirtURI = libvirt.LibvirtTCPConnectURI libvirtURI = libvirt.LibvirtTCPConnectURI
@ -452,21 +463,21 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
vars := terraform.QEMUVariables{ vars := terraform.QEMUVariables{
CommonVariables: terraform.CommonVariables{ CommonVariables: terraform.CommonVariables{
Name: config.Name, Name: opts.Config.Name,
CountControlPlanes: controlPlaneCount, CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: workerCount, CountWorkers: opts.WorkerCount,
StateDiskSizeGB: config.StateDiskSizeGB, StateDiskSizeGB: opts.Config.StateDiskSizeGB,
}, },
LibvirtURI: libvirtURI, LibvirtURI: libvirtURI,
LibvirtSocketPath: libvirtSocketPath, LibvirtSocketPath: libvirtSocketPath,
ImagePath: imagePath, ImagePath: imagePath,
ImageFormat: config.Provider.QEMU.ImageFormat, ImageFormat: opts.Config.Provider.QEMU.ImageFormat,
CPUCount: config.Provider.QEMU.VCPUs, CPUCount: opts.Config.Provider.QEMU.VCPUs,
MemorySizeMiB: config.Provider.QEMU.Memory, MemorySizeMiB: opts.Config.Provider.QEMU.Memory,
MetadataAPIImage: config.Provider.QEMU.MetadataAPIImage, MetadataAPIImage: opts.Config.Provider.QEMU.MetadataAPIImage,
MetadataLibvirtURI: metadataLibvirtURI, MetadataLibvirtURI: metadataLibvirtURI,
NVRAM: config.Provider.QEMU.NVRAM, NVRAM: opts.Config.Provider.QEMU.NVRAM,
Firmware: config.Provider.QEMU.Firmware, Firmware: opts.Config.Provider.QEMU.Firmware,
} }
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.QEMU.String())), &vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.QEMU.String())), &vars); err != nil {
@ -476,7 +487,7 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
// Allow rollback of QEMU Terraform workspace from this point on // Allow rollback of QEMU Terraform workspace from this point on
qemuRollbacker.createdWorkspace = true qemuRollbacker.createdWorkspace = true
tfOutput, err := cl.CreateCluster(ctx) tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil { if err != nil {
return clusterid.File{}, err return clusterid.File{}, err
} }

View file

@ -215,7 +215,15 @@ func TestCreator(t *testing.T) {
policyPatcher: tc.policyPatcher, policyPatcher: tc.policyPatcher,
} }
idFile, err := creator.Create(context.Background(), tc.provider, tc.config, "type", 2, 3) opts := CreateOptions{
Provider: tc.provider,
Config: tc.config,
InsType: "type",
ControlPlaneCount: 2,
WorkerCount: 3,
TFLogLevel: terraform.LogLevelNone,
}
idFile, err := creator.Create(context.Background(), opts)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)

View file

@ -70,8 +70,8 @@ func (d *IAMDestroyer) GetTfstateServiceAccountKey(ctx context.Context) (gcpshar
} }
// DestroyIAMConfiguration destroys the previously created IAM configuration and deletes the local IAM terraform files. // DestroyIAMConfiguration destroys the previously created IAM configuration and deletes the local IAM terraform files.
func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context) error { func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context, logLevel terraform.LogLevel) error {
if err := d.client.Destroy(ctx); err != nil { if err := d.client.Destroy(ctx, logLevel); err != nil {
return err return err
} }
return d.client.CleanUpWorkspace() return d.client.CleanUpWorkspace()
@ -83,11 +83,12 @@ type IAMCreator struct {
newTerraformClient func(ctx context.Context) (terraformClient, error) newTerraformClient func(ctx context.Context) (terraformClient, error)
} }
// IAMConfig holds the necessary values for IAM configuration. // IAMConfigOptions holds the necessary values for IAM configuration.
type IAMConfig struct { type IAMConfigOptions struct {
GCP GCPIAMConfig GCP GCPIAMConfig
Azure AzureIAMConfig Azure AzureIAMConfig
AWS AWSIAMConfig AWS AWSIAMConfig
TFLogLevel terraform.LogLevel
} }
// GCPIAMConfig holds the necessary values for GCP IAM configuration. // GCPIAMConfig holds the necessary values for GCP IAM configuration.
@ -122,7 +123,7 @@ func NewIAMCreator(out io.Writer) *IAMCreator {
} }
// Create prepares and hands over the corresponding providers IAM creator. // Create prepares and hands over the corresponding providers IAM creator.
func (c *IAMCreator) Create(ctx context.Context, provider cloudprovider.Provider, iamConfig *IAMConfig) (iamid.File, error) { func (c *IAMCreator) Create(ctx context.Context, provider cloudprovider.Provider, opts *IAMConfigOptions) (iamid.File, error) {
switch provider { switch provider {
case cloudprovider.GCP: case cloudprovider.GCP:
cl, err := c.newTerraformClient(ctx) cl, err := c.newTerraformClient(ctx)
@ -130,42 +131,42 @@ func (c *IAMCreator) Create(ctx context.Context, provider cloudprovider.Provider
return iamid.File{}, err return iamid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createGCP(ctx, cl, iamConfig) return c.createGCP(ctx, cl, opts)
case cloudprovider.Azure: case cloudprovider.Azure:
cl, err := c.newTerraformClient(ctx) cl, err := c.newTerraformClient(ctx)
if err != nil { if err != nil {
return iamid.File{}, err return iamid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createAzure(ctx, cl, iamConfig) return c.createAzure(ctx, cl, opts)
case cloudprovider.AWS: case cloudprovider.AWS:
cl, err := c.newTerraformClient(ctx) cl, err := c.newTerraformClient(ctx)
if err != nil { if err != nil {
return iamid.File{}, err return iamid.File{}, err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createAWS(ctx, cl, iamConfig) return c.createAWS(ctx, cl, opts)
default: default:
return iamid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider) return iamid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider)
} }
} }
// createGCP creates the IAM configuration on GCP. // createGCP creates the IAM configuration on GCP.
func (c *IAMCreator) createGCP(ctx context.Context, cl terraformClient, iamConfig *IAMConfig) (retFile iamid.File, retErr error) { func (c *IAMCreator) createGCP(ctx context.Context, cl terraformClient, opts *IAMConfigOptions) (retFile iamid.File, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}) defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
vars := terraform.GCPIAMVariables{ vars := terraform.GCPIAMVariables{
ServiceAccountID: iamConfig.GCP.ServiceAccountID, ServiceAccountID: opts.GCP.ServiceAccountID,
Project: iamConfig.GCP.ProjectID, Project: opts.GCP.ProjectID,
Region: iamConfig.GCP.Region, Region: opts.GCP.Region,
Zone: iamConfig.GCP.Zone, Zone: opts.GCP.Zone,
} }
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil {
return iamid.File{}, err return iamid.File{}, err
} }
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.GCP) iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.GCP, opts.TFLogLevel)
if err != nil { if err != nil {
return iamid.File{}, err return iamid.File{}, err
} }
@ -179,20 +180,20 @@ func (c *IAMCreator) createGCP(ctx context.Context, cl terraformClient, iamConfi
} }
// createAzure creates the IAM configuration on Azure. // createAzure creates the IAM configuration on Azure.
func (c *IAMCreator) createAzure(ctx context.Context, cl terraformClient, iamConfig *IAMConfig) (retFile iamid.File, retErr error) { func (c *IAMCreator) createAzure(ctx context.Context, cl terraformClient, opts *IAMConfigOptions) (retFile iamid.File, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}) defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
vars := terraform.AzureIAMVariables{ vars := terraform.AzureIAMVariables{
Region: iamConfig.Azure.Region, Region: opts.Azure.Region,
ResourceGroup: iamConfig.Azure.ResourceGroup, ResourceGroup: opts.Azure.ResourceGroup,
ServicePrincipal: iamConfig.Azure.ServicePrincipal, ServicePrincipal: opts.Azure.ServicePrincipal,
} }
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil {
return iamid.File{}, err return iamid.File{}, err
} }
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.Azure) iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.Azure, opts.TFLogLevel)
if err != nil { if err != nil {
return iamid.File{}, err return iamid.File{}, err
} }
@ -210,19 +211,19 @@ func (c *IAMCreator) createAzure(ctx context.Context, cl terraformClient, iamCon
} }
// createAWS creates the IAM configuration on AWS. // createAWS creates the IAM configuration on AWS.
func (c *IAMCreator) createAWS(ctx context.Context, cl terraformClient, iamConfig *IAMConfig) (retFile iamid.File, retErr error) { func (c *IAMCreator) createAWS(ctx context.Context, cl terraformClient, opts *IAMConfigOptions) (retFile iamid.File, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}) defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
vars := terraform.AWSIAMVariables{ vars := terraform.AWSIAMVariables{
Region: iamConfig.AWS.Region, Region: opts.AWS.Region,
Prefix: iamConfig.AWS.Prefix, Prefix: opts.AWS.Prefix,
} }
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil { if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil {
return iamid.File{}, err return iamid.File{}, err
} }
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.AWS) iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.AWS, opts.TFLogLevel)
if err != nil { if err != nil {
return iamid.File{}, err return iamid.File{}, err
} }

View file

@ -89,7 +89,7 @@ func TestIAMCreator(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
tfClient terraformClient tfClient terraformClient
newTfClientErr error newTfClientErr error
config *IAMConfig config *IAMConfigOptions
provider cloudprovider.Provider provider cloudprovider.Provider
wantIAMIDFile iamid.File wantIAMIDFile iamid.File
wantErr bool wantErr bool
@ -107,19 +107,19 @@ func TestIAMCreator(t *testing.T) {
tfClient: &stubTerraformClient{iamOutput: validGCPIAMOutput}, tfClient: &stubTerraformClient{iamOutput: validGCPIAMOutput},
wantIAMIDFile: validGCPIAMIDFile, wantIAMIDFile: validGCPIAMIDFile,
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
config: &IAMConfig{GCP: validGCPIAMConfig}, config: &IAMConfigOptions{GCP: validGCPIAMConfig},
}, },
"azure": { "azure": {
tfClient: &stubTerraformClient{iamOutput: validAzureIAMOutput}, tfClient: &stubTerraformClient{iamOutput: validAzureIAMOutput},
wantIAMIDFile: validAzureIAMIDFile, wantIAMIDFile: validAzureIAMIDFile,
provider: cloudprovider.Azure, provider: cloudprovider.Azure,
config: &IAMConfig{Azure: validAzureIAMConfig}, config: &IAMConfigOptions{Azure: validAzureIAMConfig},
}, },
"aws": { "aws": {
tfClient: &stubTerraformClient{iamOutput: validAWSIAMOutput}, tfClient: &stubTerraformClient{iamOutput: validAWSIAMOutput},
wantIAMIDFile: validAWSIAMIDFile, wantIAMIDFile: validAWSIAMIDFile,
provider: cloudprovider.AWS, provider: cloudprovider.AWS,
config: &IAMConfig{AWS: validAWSIAMConfig}, config: &IAMConfigOptions{AWS: validAWSIAMConfig},
}, },
} }
@ -188,7 +188,7 @@ func TestDestroyIAMConfiguration(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
destroyer := &IAMDestroyer{client: tc.tfClient} destroyer := &IAMDestroyer{client: tc.tfClient}
err := destroyer.DestroyIAMConfiguration(context.Background()) err := destroyer.DestroyIAMConfiguration(context.Background(), terraform.LogLevelNone)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)

View file

@ -11,22 +11,24 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
) )
// rollbacker does a rollback. // rollbacker does a rollback.
type rollbacker interface { type rollbacker interface {
rollback(ctx context.Context) error rollback(ctx context.Context, logLevel terraform.LogLevel) error
} }
// rollbackOnError calls rollback on the rollbacker if the handed error is not nil, // rollbackOnError calls rollback on the rollbacker if the handed error is not nil,
// and writes logs to the writer w. // and writes logs to the writer w.
func rollbackOnError(w io.Writer, onErr *error, roll rollbacker) { func rollbackOnError(w io.Writer, onErr *error, roll rollbacker, logLevel terraform.LogLevel) {
if *onErr == nil { if *onErr == nil {
return return
} }
fmt.Fprintf(w, "An error occurred: %s\n", *onErr) fmt.Fprintf(w, "An error occurred: %s\n", *onErr)
fmt.Fprintln(w, "Attempting to roll back.") fmt.Fprintln(w, "Attempting to roll back.")
if err := roll.rollback(context.Background()); err != nil { if err := roll.rollback(context.Background(), logLevel); err != nil {
*onErr = errors.Join(*onErr, fmt.Errorf("on rollback: %w", err)) // TODO: print the error, or return it? *onErr = errors.Join(*onErr, fmt.Errorf("on rollback: %w", err)) // TODO: print the error, or return it?
return return
} }
@ -37,8 +39,8 @@ type rollbackerTerraform struct {
client terraformClient client terraformClient
} }
func (r *rollbackerTerraform) rollback(ctx context.Context) error { func (r *rollbackerTerraform) rollback(ctx context.Context, logLevel terraform.LogLevel) error {
if err := r.client.Destroy(ctx); err != nil { if err := r.client.Destroy(ctx, logLevel); err != nil {
return err return err
} }
return r.client.CleanUpWorkspace() return r.client.CleanUpWorkspace()
@ -50,9 +52,9 @@ type rollbackerQEMU struct {
createdWorkspace bool createdWorkspace bool
} }
func (r *rollbackerQEMU) rollback(ctx context.Context) (retErr error) { func (r *rollbackerQEMU) rollback(ctx context.Context, logLevel terraform.LogLevel) (retErr error) {
if r.createdWorkspace { if r.createdWorkspace {
retErr = r.client.Destroy(ctx) retErr = r.client.Destroy(ctx, logLevel)
} }
if retErr := errors.Join(retErr, r.libvirt.Stop(ctx)); retErr != nil { if retErr := errors.Join(retErr, r.libvirt.Stop(ctx)); retErr != nil {
return retErr return retErr

View file

@ -11,6 +11,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -42,7 +43,7 @@ func TestRollbackTerraform(t *testing.T) {
client: tc.tfClient, client: tc.tfClient,
} }
err := rollbacker.rollback(context.Background()) err := rollbacker.rollback(context.Background(), terraform.LogLevelNone)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
if tc.tfClient.cleanUpWorkspaceErr == nil { if tc.tfClient.cleanUpWorkspaceErr == nil {
@ -98,7 +99,7 @@ func TestRollbackQEMU(t *testing.T) {
createdWorkspace: tc.createdWorkspace, createdWorkspace: tc.createdWorkspace,
} }
err := rollbacker.rollback(context.Background()) err := rollbacker.rollback(context.Background(), terraform.LogLevelNone)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
if tc.tfClient.cleanUpWorkspaceErr == nil { if tc.tfClient.cleanUpWorkspaceErr == nil {

View file

@ -33,7 +33,7 @@ func NewTerminator() *Terminator {
} }
// Terminate deletes the could provider resources. // Terminate deletes the could provider resources.
func (t *Terminator) Terminate(ctx context.Context) (retErr error) { func (t *Terminator) Terminate(ctx context.Context, logLevel terraform.LogLevel) (retErr error) {
defer func() { defer func() {
if retErr == nil { if retErr == nil {
retErr = t.newLibvirtRunner().Stop(ctx) retErr = t.newLibvirtRunner().Stop(ctx)
@ -46,11 +46,11 @@ func (t *Terminator) Terminate(ctx context.Context) (retErr error) {
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return t.terminateTerraform(ctx, cl) return t.terminateTerraform(ctx, cl, logLevel)
} }
func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient) error { func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient, logLevel terraform.LogLevel) error {
if err := cl.Destroy(ctx); err != nil { if err := cl.Destroy(ctx, logLevel); err != nil {
return err return err
} }
return cl.CleanUpWorkspace() return cl.CleanUpWorkspace()

View file

@ -11,6 +11,7 @@ import (
"errors" "errors"
"testing" "testing"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -62,7 +63,7 @@ func TestTerminator(t *testing.T) {
}, },
} }
err := terminator.Terminate(context.Background()) err := terminator.Terminate(context.Background(), terraform.LogLevelNone)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)

View file

@ -120,6 +120,7 @@ go_test(
"//cli/internal/helm", "//cli/internal/helm",
"//cli/internal/iamid", "//cli/internal/iamid",
"//cli/internal/kubernetes", "//cli/internal/kubernetes",
"//cli/internal/terraform",
"//disk-mapper/recoverproto", "//disk-mapper/recoverproto",
"//internal/atls", "//internal/atls",
"//internal/attestation/measurements", "//internal/attestation/measurements",

View file

@ -12,18 +12,15 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/iamid" "github.com/edgelesssys/constellation/v2/cli/internal/iamid"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared" "github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/config"
) )
type cloudCreator interface { type cloudCreator interface {
Create( Create(
ctx context.Context, ctx context.Context,
provider cloudprovider.Provider, opts cloudcmd.CreateOptions,
config *config.Config,
insType string,
coordCount, nodeCount int,
) (clusterid.File, error) ) (clusterid.File, error)
} }
@ -31,15 +28,15 @@ type cloudIAMCreator interface {
Create( Create(
ctx context.Context, ctx context.Context,
provider cloudprovider.Provider, provider cloudprovider.Provider,
iamConfig *cloudcmd.IAMConfig, opts *cloudcmd.IAMConfigOptions,
) (iamid.File, error) ) (iamid.File, error)
} }
type iamDestroyer interface { type iamDestroyer interface {
DestroyIAMConfiguration(ctx context.Context) error DestroyIAMConfiguration(ctx context.Context, logLevel terraform.LogLevel) error
GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error) GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error)
} }
type cloudTerminator interface { type cloudTerminator interface {
Terminate(context.Context) error Terminate(ctx context.Context, logLevel terraform.LogLevel) error
} }

View file

@ -13,9 +13,9 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/clusterid" "github.com/edgelesssys/constellation/v2/cli/internal/clusterid"
"github.com/edgelesssys/constellation/v2/cli/internal/iamid" "github.com/edgelesssys/constellation/v2/cli/internal/iamid"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared" "github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/config"
"go.uber.org/goleak" "go.uber.org/goleak"
) )
@ -34,13 +34,10 @@ type stubCloudCreator struct {
func (c *stubCloudCreator) Create( func (c *stubCloudCreator) Create(
_ context.Context, _ context.Context,
provider cloudprovider.Provider, opts cloudcmd.CreateOptions,
_ *config.Config,
_ string,
_, _ int,
) (clusterid.File, error) { ) (clusterid.File, error) {
c.createCalled = true c.createCalled = true
c.id.CloudProvider = provider c.id.CloudProvider = opts.Provider
return c.id, c.createErr return c.id, c.createErr
} }
@ -49,7 +46,7 @@ type stubCloudTerminator struct {
terminateErr error terminateErr error
} }
func (c *stubCloudTerminator) Terminate(context.Context) error { func (c *stubCloudTerminator) Terminate(_ context.Context, _ terraform.LogLevel) error {
c.called = true c.called = true
return c.terminateErr return c.terminateErr
} }
@ -67,7 +64,7 @@ type stubIAMCreator struct {
func (c *stubIAMCreator) Create( func (c *stubIAMCreator) Create(
_ context.Context, _ context.Context,
provider cloudprovider.Provider, provider cloudprovider.Provider,
_ *cloudcmd.IAMConfig, _ *cloudcmd.IAMConfigOptions,
) (iamid.File, error) { ) (iamid.File, error) {
c.createCalled = true c.createCalled = true
c.id.CloudProvider = provider c.id.CloudProvider = provider
@ -82,7 +79,7 @@ type stubIAMDestroyer struct {
getTfstateKeyErr error getTfstateKeyErr error
} }
func (d *stubIAMDestroyer) DestroyIAMConfiguration(_ context.Context) error { func (d *stubIAMDestroyer) DestroyIAMConfiguration(_ context.Context, _ terraform.LogLevel) error {
d.destroyCalled = true d.destroyCalled = true
return d.destroyErr return d.destroyErr
} }

View file

@ -153,7 +153,15 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler
} }
spinner.Start("Creating", false) spinner.Start("Creating", false)
idFile, err := creator.Create(cmd.Context(), provider, conf, instanceType, flags.controllerCount, flags.workerCount) opts := cloudcmd.CreateOptions{
Provider: provider,
Config: conf,
InsType: instanceType,
ControlPlaneCount: flags.controllerCount,
WorkerCount: flags.workerCount,
TFLogLevel: flags.tfLogLevel,
}
idFile, err := creator.Create(cmd.Context(), opts)
spinner.Stop() spinner.Stop()
if err != nil { if err != nil {
return translateCreateErrors(cmd, err) return translateCreateErrors(cmd, err)
@ -190,7 +198,7 @@ func (c *createCmd) parseCreateFlags(cmd *cobra.Command) (createFlags, error) {
yes, err := cmd.Flags().GetBool("yes") yes, err := cmd.Flags().GetBool("yes")
if err != nil { if err != nil {
return createFlags{}, fmt.Errorf("%w; Set '-yes' without a value to automatically confirm", err) return createFlags{}, fmt.Errorf("parsing yes bool: %w", err)
} }
c.log.Debugf("Yes flag is %t", yes) c.log.Debugf("Yes flag is %t", yes)
@ -206,10 +214,21 @@ func (c *createCmd) parseCreateFlags(cmd *cobra.Command) (createFlags, error) {
} }
c.log.Debugf("force flag is %t", force) c.log.Debugf("force flag is %t", force)
logLevelString, err := cmd.Flags().GetString("tf-log")
if err != nil {
return createFlags{}, fmt.Errorf("parsing tf-log string: %w", err)
}
logLevel, err := terraform.ParseLogLevel(logLevelString)
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())
return createFlags{ return createFlags{
controllerCount: controllerCount, controllerCount: controllerCount,
workerCount: workerCount, workerCount: workerCount,
configPath: configPath, configPath: configPath,
tfLogLevel: logLevel,
force: force, force: force,
yes: yes, yes: yes,
}, nil }, nil
@ -220,6 +239,7 @@ type createFlags struct {
controllerCount int controllerCount int
workerCount int workerCount int
configPath string configPath string
tfLogLevel terraform.LogLevel
force bool force bool
yes bool yes bool
} }

View file

@ -185,6 +185,7 @@ func TestCreate(t *testing.T) {
cmd.SetIn(bytes.NewBufferString(tc.stdin)) cmd.SetIn(bytes.NewBufferString(tc.stdin))
cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually
cmd.Flags().Bool("force", true, "") // 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 { if tc.yesFlag {
require.NoError(cmd.Flags().Set("yes", "true")) require.NoError(cmd.Flags().Set("yes", "true"))

View file

@ -15,6 +15,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/iamid" "github.com/edgelesssys/constellation/v2/cli/internal/iamid"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
@ -147,8 +148,18 @@ func createRunIAMFunc(provider cloudprovider.Provider) func(cmd *cobra.Command,
return fmt.Errorf("unknown provider %s", provider) return fmt.Errorf("unknown provider %s", provider)
} }
} }
return func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error {
iamCreator, err := newIAMCreator(cmd) logLevelString, err := cmd.Flags().GetString("tf-log")
if err != nil {
return fmt.Errorf("parsing tf-log string: %w", err)
}
logLevel, err := terraform.ParseLogLevel(logLevelString)
if err != nil {
return fmt.Errorf("parsing Terraform log level %s: %w", logLevelString, err)
}
iamCreator, err := newIAMCreator(cmd, logLevel)
if err != nil { if err != nil {
return fmt.Errorf("creating iamCreator: %w", err) return fmt.Errorf("creating iamCreator: %w", err)
} }
@ -161,7 +172,7 @@ func createRunIAMFunc(provider cloudprovider.Provider) func(cmd *cobra.Command,
} }
// newIAMCreator creates a new iamiamCreator. // newIAMCreator creates a new iamiamCreator.
func newIAMCreator(cmd *cobra.Command) (*iamCreator, error) { func newIAMCreator(cmd *cobra.Command, logLevel terraform.LogLevel) (*iamCreator, error) {
spinner, err := newSpinnerOrStderr(cmd) spinner, err := newSpinnerOrStderr(cmd)
if err != nil { if err != nil {
return nil, fmt.Errorf("creating spinner: %w", err) return nil, fmt.Errorf("creating spinner: %w", err)
@ -170,13 +181,17 @@ func newIAMCreator(cmd *cobra.Command) (*iamCreator, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("creating logger: %w", err) return nil, fmt.Errorf("creating logger: %w", err)
} }
log.Debugf("Terraform logs will be written into %s at level %s", constants.TerraformLogFile, logLevel.String())
return &iamCreator{ return &iamCreator{
cmd: cmd, cmd: cmd,
spinner: spinner, spinner: spinner,
log: log, log: log,
creator: cloudcmd.NewIAMCreator(spinner), creator: cloudcmd.NewIAMCreator(spinner),
fileHandler: file.NewHandler(afero.NewOsFs()), fileHandler: file.NewHandler(afero.NewOsFs()),
iamConfig: &cloudcmd.IAMConfig{}, iamConfig: &cloudcmd.IAMConfigOptions{
TFLogLevel: logLevel,
},
}, nil }, nil
} }
@ -188,7 +203,7 @@ type iamCreator struct {
fileHandler file.Handler fileHandler file.Handler
provider cloudprovider.Provider provider cloudprovider.Provider
providerCreator providerIAMCreator providerCreator providerIAMCreator
iamConfig *cloudcmd.IAMConfig iamConfig *cloudcmd.IAMConfigOptions
log debugLog log debugLog
} }
@ -361,7 +376,7 @@ type providerIAMCreator interface {
// writeOutputValuesToConfig writes the output values of the IAM creation to the constellation config file. // writeOutputValuesToConfig writes the output values of the IAM creation to the constellation config file.
writeOutputValuesToConfig(conf *config.Config, flags iamFlags, iamFile iamid.File) writeOutputValuesToConfig(conf *config.Config, flags iamFlags, iamFile iamid.File)
// parseFlagsAndSetupConfig parses the provider-specific flags and fills the values into the IAM config (output values of the command). // parseFlagsAndSetupConfig parses the provider-specific flags and fills the values into the IAM config (output values of the command).
parseFlagsAndSetupConfig(cmd *cobra.Command, flags iamFlags, iamConfig *cloudcmd.IAMConfig) (iamFlags, error) parseFlagsAndSetupConfig(cmd *cobra.Command, flags iamFlags, iamConfig *cloudcmd.IAMConfigOptions) (iamFlags, error)
// parseAndWriteIDFile parses the GCP service account key and writes it to a keyfile. It is only implemented for GCP. // parseAndWriteIDFile parses the GCP service account key and writes it to a keyfile. It is only implemented for GCP.
parseAndWriteIDFile(iamFile iamid.File, fileHandler file.Handler) error parseAndWriteIDFile(iamFile iamid.File, fileHandler file.Handler) error
} }
@ -369,7 +384,7 @@ type providerIAMCreator interface {
// awsIAMCreator implements the providerIAMCreator interface for AWS. // awsIAMCreator implements the providerIAMCreator interface for AWS.
type awsIAMCreator struct{} type awsIAMCreator struct{}
func (c *awsIAMCreator) parseFlagsAndSetupConfig(cmd *cobra.Command, flags iamFlags, iamConfig *cloudcmd.IAMConfig) (iamFlags, error) { func (c *awsIAMCreator) parseFlagsAndSetupConfig(cmd *cobra.Command, flags iamFlags, iamConfig *cloudcmd.IAMConfigOptions) (iamFlags, error) {
prefix, err := cmd.Flags().GetString("prefix") prefix, err := cmd.Flags().GetString("prefix")
if err != nil { if err != nil {
return iamFlags{}, fmt.Errorf("parsing prefix string: %w", err) return iamFlags{}, fmt.Errorf("parsing prefix string: %w", err)
@ -429,7 +444,7 @@ func (c *awsIAMCreator) parseAndWriteIDFile(_ iamid.File, _ file.Handler) error
// azureIAMCreator implements the providerIAMCreator interface for Azure. // azureIAMCreator implements the providerIAMCreator interface for Azure.
type azureIAMCreator struct{} type azureIAMCreator struct{}
func (c *azureIAMCreator) parseFlagsAndSetupConfig(cmd *cobra.Command, flags iamFlags, iamConfig *cloudcmd.IAMConfig) (iamFlags, error) { func (c *azureIAMCreator) parseFlagsAndSetupConfig(cmd *cobra.Command, flags iamFlags, iamConfig *cloudcmd.IAMConfigOptions) (iamFlags, error) {
region, err := cmd.Flags().GetString("region") region, err := cmd.Flags().GetString("region")
if err != nil { if err != nil {
return iamFlags{}, fmt.Errorf("parsing region string: %w", err) return iamFlags{}, fmt.Errorf("parsing region string: %w", err)
@ -494,7 +509,7 @@ func (c *azureIAMCreator) parseAndWriteIDFile(_ iamid.File, _ file.Handler) erro
// gcpIAMCreator implements the providerIAMCreator interface for GCP. // gcpIAMCreator implements the providerIAMCreator interface for GCP.
type gcpIAMCreator struct{} type gcpIAMCreator struct{}
func (c *gcpIAMCreator) parseFlagsAndSetupConfig(cmd *cobra.Command, flags iamFlags, iamConfig *cloudcmd.IAMConfig) (iamFlags, error) { func (c *gcpIAMCreator) parseFlagsAndSetupConfig(cmd *cobra.Command, flags iamFlags, iamConfig *cloudcmd.IAMConfigOptions) (iamFlags, error) {
zone, err := cmd.Flags().GetString("zone") zone, err := cmd.Flags().GetString("zone")
if err != nil { if err != nil {
return iamFlags{}, fmt.Errorf("parsing zone string: %w", err) return iamFlags{}, fmt.Errorf("parsing zone string: %w", err)

View file

@ -278,6 +278,7 @@ func TestIAMCreateAWS(t *testing.T) {
cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "") cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "")
cmd.Flags().Bool("yes", false, "") cmd.Flags().Bool("yes", false, "")
cmd.Flags().String("name", "constell", "") cmd.Flags().String("name", "constell", "")
cmd.Flags().String("tf-log", "NONE", "")
if tc.zoneFlag != "" { if tc.zoneFlag != "" {
require.NoError(cmd.Flags().Set("zone", tc.zoneFlag)) require.NoError(cmd.Flags().Set("zone", tc.zoneFlag))
@ -306,7 +307,7 @@ func TestIAMCreateAWS(t *testing.T) {
spinner: &nopSpinner{}, spinner: &nopSpinner{},
creator: tc.creator, creator: tc.creator,
fileHandler: fileHandler, fileHandler: fileHandler,
iamConfig: &cloudcmd.IAMConfig{}, iamConfig: &cloudcmd.IAMConfigOptions{},
provider: tc.provider, provider: tc.provider,
providerCreator: &awsIAMCreator{}, providerCreator: &awsIAMCreator{},
} }
@ -550,12 +551,13 @@ func TestIAMCreateAzure(t *testing.T) {
cmd.SetErr(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{})
cmd.SetIn(bytes.NewBufferString(tc.stdin)) cmd.SetIn(bytes.NewBufferString(tc.stdin))
// register persistent flag manually // register persistent flags manually
cmd.Flags().String("config", constants.ConfigFilename, "") cmd.Flags().String("config", constants.ConfigFilename, "")
cmd.Flags().Bool("generate-config", false, "") cmd.Flags().Bool("generate-config", false, "")
cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "") cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "")
cmd.Flags().Bool("yes", false, "") cmd.Flags().Bool("yes", false, "")
cmd.Flags().String("name", "constell", "") cmd.Flags().String("name", "constell", "")
cmd.Flags().String("tf-log", "NONE", "")
if tc.regionFlag != "" { if tc.regionFlag != "" {
require.NoError(cmd.Flags().Set("region", tc.regionFlag)) require.NoError(cmd.Flags().Set("region", tc.regionFlag))
@ -587,7 +589,7 @@ func TestIAMCreateAzure(t *testing.T) {
spinner: &nopSpinner{}, spinner: &nopSpinner{},
creator: tc.creator, creator: tc.creator,
fileHandler: fileHandler, fileHandler: fileHandler,
iamConfig: &cloudcmd.IAMConfig{}, iamConfig: &cloudcmd.IAMConfigOptions{},
provider: tc.provider, provider: tc.provider,
providerCreator: &azureIAMCreator{}, providerCreator: &azureIAMCreator{},
} }
@ -862,6 +864,7 @@ func TestIAMCreateGCP(t *testing.T) {
cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "") cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "")
cmd.Flags().Bool("yes", false, "") cmd.Flags().Bool("yes", false, "")
cmd.Flags().String("name", "constell", "") cmd.Flags().String("name", "constell", "")
cmd.Flags().String("tf-log", "NONE", "")
if tc.zoneFlag != "" { if tc.zoneFlag != "" {
require.NoError(cmd.Flags().Set("zone", tc.zoneFlag)) require.NoError(cmd.Flags().Set("zone", tc.zoneFlag))
@ -893,7 +896,7 @@ func TestIAMCreateGCP(t *testing.T) {
spinner: &nopSpinner{}, spinner: &nopSpinner{},
creator: tc.creator, creator: tc.creator,
fileHandler: fileHandler, fileHandler: fileHandler,
iamConfig: &cloudcmd.IAMConfig{}, iamConfig: &cloudcmd.IAMConfigOptions{},
provider: tc.provider, provider: tc.provider,
providerCreator: &gcpIAMCreator{}, providerCreator: &gcpIAMCreator{},
} }

View file

@ -11,6 +11,7 @@ import (
"os" "os"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared" "github.com/edgelesssys/constellation/v2/internal/cloud/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
@ -56,9 +57,14 @@ type destroyCmd struct {
} }
func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destroyer iamDestroyer, fsHandler file.Handler) error { func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destroyer iamDestroyer, fsHandler file.Handler) error {
flags, err := c.parseDestroyFlags(cmd)
if err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
// check if there is a possibility that the cluster is still running by looking out for specific files // 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", constants.AdminConfFilename)
_, err := fsHandler.Stat(constants.AdminConfFilename) _, err = fsHandler.Stat(constants.AdminConfFilename)
if !errors.Is(err, os.ErrNotExist) { 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", constants.AdminConfFilename)
} }
@ -68,12 +74,6 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr
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", constants.ClusterIDsFileName)
} }
yes, err := cmd.Flags().GetBool("yes")
if err != nil {
return err
}
c.log.Debugf("\"yes\" flag is set to %t", yes)
gcpFileExists := false gcpFileExists := false
c.log.Debugf("Checking if %q exists", constants.GCPServiceAccountKeyFile) c.log.Debugf("Checking if %q exists", constants.GCPServiceAccountKeyFile)
@ -87,7 +87,7 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr
gcpFileExists = true gcpFileExists = true
} }
if !yes { if !flags.yes {
// Confirmation // Confirmation
confirmString := "Do you really want to destroy your IAM configuration?" confirmString := "Do you really want to destroy your IAM configuration?"
if gcpFileExists { if gcpFileExists {
@ -119,7 +119,7 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr
spinner.Start("Destroying IAM configuration", false) spinner.Start("Destroying IAM configuration", false)
defer spinner.Stop() defer spinner.Stop()
if err := destroyer.DestroyIAMConfiguration(cmd.Context()); err != nil { if err := destroyer.DestroyIAMConfiguration(cmd.Context(), flags.tfLogLevel); err != nil {
return fmt.Errorf("destroying IAM configuration: %w", err) return fmt.Errorf("destroying IAM configuration: %w", err)
} }
@ -155,3 +155,32 @@ func (c *destroyCmd) deleteGCPServiceAccountKeyFile(cmd *cobra.Command, destroye
c.log.Debugf("Successfully deleted %q", constants.GCPServiceAccountKeyFile) c.log.Debugf("Successfully deleted %q", constants.GCPServiceAccountKeyFile)
return true, nil return true, nil
} }
type destroyFlags struct {
yes bool
tfLogLevel terraform.LogLevel
}
// parseDestroyFlags parses the flags of the create command.
func (c *destroyCmd) parseDestroyFlags(cmd *cobra.Command) (destroyFlags, error) {
yes, err := cmd.Flags().GetBool("yes")
if err != nil {
return destroyFlags{}, fmt.Errorf("parsing yes bool: %w", err)
}
c.log.Debugf("Yes flag is %t", yes)
logLevelString, err := cmd.Flags().GetString("tf-log")
if err != nil {
return destroyFlags{}, fmt.Errorf("parsing tf-log string: %w", err)
}
logLevel, err := terraform.ParseLogLevel(logLevelString)
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())
return destroyFlags{
tfLogLevel: logLevel,
yes: yes,
}, nil
}

View file

@ -105,6 +105,10 @@ func TestIAMDestroy(t *testing.T) {
cmd.SetOut(&bytes.Buffer{}) cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{})
cmd.SetIn(bytes.NewBufferString(tc.stdin)) cmd.SetIn(bytes.NewBufferString(tc.stdin))
// register persistent flags manually
cmd.Flags().String("tf-log", "NONE", "")
assert.NoError(cmd.Flags().Set("yes", tc.yesFlag)) assert.NoError(cmd.Flags().Set("yes", tc.yesFlag))
c := &destroyCmd{log: logger.NewTest(t)} c := &destroyCmd{log: logger.NewTest(t)}

View file

@ -19,6 +19,7 @@ import (
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
@ -73,17 +74,22 @@ func (m *miniUpCmd) up(cmd *cobra.Command, creator cloudCreator, spinner spinner
return fmt.Errorf("system requirements not met: %w", err) return fmt.Errorf("system requirements not met: %w", err)
} }
flags, err := m.parseUpFlags(cmd)
if err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
fileHandler := file.NewHandler(afero.NewOsFs()) fileHandler := file.NewHandler(afero.NewOsFs())
// create config if not passed as flag and set default values // create config if not passed as flag and set default values
config, err := m.prepareConfig(cmd, fileHandler) config, err := m.prepareConfig(cmd, fileHandler, flags)
if err != nil { if err != nil {
return fmt.Errorf("preparing config: %w", err) return fmt.Errorf("preparing config: %w", err)
} }
// create cluster // create cluster
spinner.Start("Creating cluster in QEMU ", false) spinner.Start("Creating cluster in QEMU ", false)
err = m.createMiniCluster(cmd.Context(), fileHandler, creator, config) err = m.createMiniCluster(cmd.Context(), fileHandler, creator, config, flags.tfLogLevel)
spinner.Stop() spinner.Stop()
if err != nil { if err != nil {
return fmt.Errorf("creating cluster: %w", err) return fmt.Errorf("creating cluster: %w", err)
@ -173,20 +179,10 @@ func (m *miniUpCmd) checkSystemRequirements(out io.Writer) error {
} }
// prepareConfig reads a given config, or creates a new minimal QEMU config. // prepareConfig reads a given config, or creates a new minimal QEMU config.
func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler) (*config.Config, error) { func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler, flags upFlags) (*config.Config, error) {
m.log.Debugf("Preparing configuration")
configPath, err := cmd.Flags().GetString("config")
if err != nil {
return nil, err
}
force, err := cmd.Flags().GetBool("force")
if err != nil {
return nil, fmt.Errorf("parsing force argument: %w", err)
}
// check for existing config // check for existing config
if configPath != "" { if flags.configPath != "" {
conf, err := config.New(fileHandler, configPath, force) conf, err := config.New(fileHandler, flags.configPath, flags.force)
var configValidationErr *config.ValidationError var configValidationErr *config.ValidationError
if errors.As(err, &configValidationErr) { if errors.As(err, &configValidationErr) {
cmd.PrintErrln(configValidationErr.LongMessage()) cmd.PrintErrln(configValidationErr.LongMessage())
@ -199,11 +195,11 @@ func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler)
} }
return conf, nil return conf, nil
} }
m.log.Debugf("Configuration path is %q", configPath) m.log.Debugf("Configuration path is %q", flags.configPath)
if err := cmd.Flags().Set("config", constants.ConfigFilename); err != nil { if err := cmd.Flags().Set("config", constants.ConfigFilename); err != nil {
return nil, err return nil, err
} }
_, err = fileHandler.Stat(constants.ConfigFilename) _, err := fileHandler.Stat(constants.ConfigFilename)
if err == nil { if err == nil {
// config already exists, prompt user to overwrite // 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.") cmd.PrintErrln("A config file already exists in the current workspace. Use --config to use an existing config file.")
@ -227,9 +223,17 @@ func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler)
} }
// createMiniCluster creates a new cluster using the given config. // createMiniCluster creates a new cluster using the given config.
func (m *miniUpCmd) createMiniCluster(ctx context.Context, fileHandler file.Handler, creator cloudCreator, config *config.Config) error { func (m *miniUpCmd) createMiniCluster(ctx context.Context, fileHandler file.Handler, creator cloudCreator, config *config.Config, tfLogLevel terraform.LogLevel) error {
m.log.Debugf("Creating mini cluster") m.log.Debugf("Creating mini cluster")
idFile, err := creator.Create(ctx, cloudprovider.QEMU, config, "", 1, 1) opts := cloudcmd.CreateOptions{
Provider: cloudprovider.QEMU,
Config: config,
InsType: "",
ControlPlaneCount: 1,
WorkerCount: 1,
TFLogLevel: tfLogLevel,
}
idFile, err := creator.Create(ctx, opts)
if err != nil { if err != nil {
return err return err
} }
@ -271,3 +275,39 @@ func (m *miniUpCmd) initializeMiniCluster(cmd *cobra.Command, fileHandler file.H
m.log.Debugf("Initialized mini cluster") m.log.Debugf("Initialized mini cluster")
return nil return nil
} }
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")
if err != nil {
return upFlags{}, fmt.Errorf("parsing config string: %w", err)
}
m.log.Debugf("Configuration path is %q", configPath)
force, err := cmd.Flags().GetBool("force")
if err != nil {
return upFlags{}, fmt.Errorf("parsing force bool: %w", err)
}
m.log.Debugf("force flag is %q", configPath)
logLevelString, err := cmd.Flags().GetString("tf-log")
if err != nil {
return upFlags{}, fmt.Errorf("parsing tf-log string: %w", err)
}
logLevel, err := terraform.ParseLogLevel(logLevelString)
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())
return upFlags{
configPath: configPath,
force: force,
tfLogLevel: logLevel,
}, nil
}

View file

@ -15,6 +15,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd" "github.com/edgelesssys/constellation/v2/cli/internal/cloudcmd"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/constants" "github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
) )
@ -48,12 +49,12 @@ func runTerminate(cmd *cobra.Command, _ []string) error {
func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.Handler, spinner spinnerInterf, func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.Handler, spinner spinnerInterf,
) error { ) error {
yesFlag, err := cmd.Flags().GetBool("yes") flags, err := parseTerminateFlags(cmd)
if err != nil { if err != nil {
return err return fmt.Errorf("parsing flags: %w", err)
} }
if !yesFlag { if !flags.yes {
cmd.Println("You are about to terminate a Constellation cluster.") cmd.Println("You are about to terminate a Constellation cluster.")
cmd.Println("All of its associated resources will be DESTROYED.") cmd.Println("All of its associated resources will be DESTROYED.")
cmd.Println("This action is irreversible and ALL DATA WILL BE LOST.") cmd.Println("This action is irreversible and ALL DATA WILL BE LOST.")
@ -68,7 +69,7 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.
} }
spinner.Start("Terminating", false) spinner.Start("Terminating", false)
err = terminator.Terminate(cmd.Context()) err = terminator.Terminate(cmd.Context(), flags.logLevel)
spinner.Stop() spinner.Stop()
if err != nil { if err != nil {
return fmt.Errorf("terminating Constellation cluster: %w", err) return fmt.Errorf("terminating Constellation cluster: %w", err)
@ -87,3 +88,28 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.
return removeErr return removeErr
} }
type terminateFlags struct {
yes bool
logLevel terraform.LogLevel
}
func parseTerminateFlags(cmd *cobra.Command) (terminateFlags, error) {
yes, err := cmd.Flags().GetBool("yes")
if err != nil {
return terminateFlags{}, fmt.Errorf("parsing yes bool: %w", err)
}
logLevelString, err := cmd.Flags().GetString("tf-log")
if err != nil {
return terminateFlags{}, fmt.Errorf("parsing tf-log string: %w", err)
}
logLevel, err := terraform.ParseLogLevel(logLevelString)
if err != nil {
return terminateFlags{}, fmt.Errorf("parsing Terraform log level %s: %w", logLevelString, err)
}
return terminateFlags{
yes: yes,
logLevel: logLevel,
}, nil
}

View file

@ -135,6 +135,9 @@ func TestTerminate(t *testing.T) {
cmd.SetErr(&bytes.Buffer{}) cmd.SetErr(&bytes.Buffer{})
cmd.SetIn(bytes.NewBufferString(tc.stdin)) cmd.SetIn(bytes.NewBufferString(tc.stdin))
// register persistent flags manually
cmd.Flags().String("tf-log", "NONE", "")
require.NotNil(tc.setupFs) require.NotNil(tc.setupFs)
fileHandler := file.NewHandler(tc.setupFs(require, tc.idFile)) fileHandler := file.NewHandler(tc.setupFs(require, tc.idFile))

View file

@ -5,6 +5,7 @@ go_library(
name = "terraform", name = "terraform",
srcs = [ srcs = [
"loader.go", "loader.go",
"logging.go",
"terraform.go", "terraform.go",
"variables.go", "variables.go",
], ],
@ -73,6 +74,7 @@ go_library(
visibility = ["//cli:__subpackages__"], visibility = ["//cli:__subpackages__"],
deps = [ deps = [
"//internal/cloud/cloudprovider", "//internal/cloud/cloudprovider",
"//internal/constants",
"//internal/file", "//internal/file",
"@com_github_hashicorp_go_version//:go-version", "@com_github_hashicorp_go_version//:go-version",
"@com_github_hashicorp_hc_install//:hc-install", "@com_github_hashicorp_hc_install//:hc-install",

View file

@ -0,0 +1,75 @@
/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
package terraform
import (
"fmt"
"strings"
)
const (
// LogLevelNone represents a log level that does not produce any output.
LogLevelNone LogLevel = iota
// LogLevelError enables log output at ERROR level.
LogLevelError
// LogLevelWarn enables log output at WARN level.
LogLevelWarn
// LogLevelInfo enables log output at INFO level.
LogLevelInfo
// LogLevelDebug enables log output at DEBUG level.
LogLevelDebug
// LogLevelTrace enables log output at TRACE level.
LogLevelTrace
// LogLevelJSON enables log output at TRACE level in JSON format.
LogLevelJSON
)
// LogLevel is a Terraform log level.
// As per https://developer.hashicorp.com/terraform/internals/debugging
type LogLevel int
// ParseLogLevel parses a log level string into a Terraform log level.
func ParseLogLevel(level string) (LogLevel, error) {
switch strings.ToUpper(level) {
case "NONE":
return LogLevelNone, nil
case "ERROR":
return LogLevelError, nil
case "WARN":
return LogLevelWarn, nil
case "INFO":
return LogLevelInfo, nil
case "DEBUG":
return LogLevelDebug, nil
case "TRACE":
return LogLevelTrace, nil
case "JSON":
return LogLevelJSON, nil
default:
return LogLevelNone, fmt.Errorf("invalid log level %s", level)
}
}
// String returns the string representation of a Terraform log level.
func (l LogLevel) String() string {
switch l {
case LogLevelError:
return "ERROR"
case LogLevelWarn:
return "WARN"
case LogLevelInfo:
return "INFO"
case LogLevelDebug:
return "DEBUG"
case LogLevelTrace:
return "TRACE"
case LogLevelJSON:
return "JSON"
default:
return ""
}
}

View file

@ -17,9 +17,11 @@ package terraform
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"path/filepath" "path/filepath"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file" "github.com/edgelesssys/constellation/v2/internal/file"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
install "github.com/hashicorp/hc-install" install "github.com/hashicorp/hc-install"
@ -83,18 +85,22 @@ func (c *Client) PrepareWorkspace(path string, vars Variables) error {
} }
// CreateCluster creates a Constellation cluster using Terraform. // CreateCluster creates a Constellation cluster using Terraform.
func (c *Client) CreateCluster(ctx context.Context) (CreateOutput, error) { func (c *Client) CreateCluster(ctx context.Context, logLevel LogLevel) (CreateOutput, error) {
if err := c.setLogLevel(logLevel); err != nil {
return CreateOutput{}, fmt.Errorf("set terraform log level %s: %w", logLevel.String(), err)
}
if err := c.tf.Init(ctx); err != nil { if err := c.tf.Init(ctx); err != nil {
return CreateOutput{}, err return CreateOutput{}, fmt.Errorf("terraform init: %w", err)
} }
if err := c.tf.Apply(ctx); err != nil { if err := c.tf.Apply(ctx); err != nil {
return CreateOutput{}, err return CreateOutput{}, fmt.Errorf("terraform apply: %w", err)
} }
tfState, err := c.tf.Show(ctx) tfState, err := c.tf.Show(ctx)
if err != nil { if err != nil {
return CreateOutput{}, err return CreateOutput{}, fmt.Errorf("terraform show: %w", err)
} }
ipOutput, ok := tfState.Values.Outputs["ip"] ipOutput, ok := tfState.Values.Outputs["ip"]
@ -177,7 +183,11 @@ type AWSIAMOutput struct {
} }
// CreateIAMConfig creates an IAM configuration using Terraform. // CreateIAMConfig creates an IAM configuration using Terraform.
func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider) (IAMOutput, error) { func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Provider, logLevel LogLevel) (IAMOutput, error) {
if err := c.setLogLevel(logLevel); err != nil {
return IAMOutput{}, fmt.Errorf("set terraform log level %s: %w", logLevel.String(), err)
}
if err := c.tf.Init(ctx); err != nil { if err := c.tf.Init(ctx); err != nil {
return IAMOutput{}, err return IAMOutput{}, err
} }
@ -285,9 +295,13 @@ func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Pro
} }
// Destroy destroys Terraform-created cloud resources. // Destroy destroys Terraform-created cloud resources.
func (c *Client) Destroy(ctx context.Context) error { func (c *Client) Destroy(ctx context.Context, logLevel LogLevel) error {
if err := c.setLogLevel(logLevel); err != nil {
return fmt.Errorf("set terraform log level %s: %w", logLevel.String(), err)
}
if err := c.tf.Init(ctx); err != nil { if err := c.tf.Init(ctx); err != nil {
return err return fmt.Errorf("terraform init: %w", err)
} }
return c.tf.Destroy(ctx) return c.tf.Destroy(ctx)
} }
@ -354,9 +368,24 @@ func (c *Client) writeVars(vars Variables) error {
return nil return nil
} }
// setLogLevel sets the log level for Terraform.
func (c *Client) setLogLevel(logLevel LogLevel) error {
if logLevel.String() != "" {
if err := c.tf.SetLog(logLevel.String()); err != nil {
return fmt.Errorf("set log level %s: %w", logLevel.String(), err)
}
if err := c.tf.SetLogPath(filepath.Join("..", constants.TerraformLogFile)); err != nil {
return fmt.Errorf("set log path: %w", err)
}
}
return nil
}
type tfInterface interface { type tfInterface interface {
Apply(context.Context, ...tfexec.ApplyOption) error Apply(context.Context, ...tfexec.ApplyOption) error
Destroy(context.Context, ...tfexec.DestroyOption) error Destroy(context.Context, ...tfexec.DestroyOption) error
Init(context.Context, ...tfexec.InitOption) error Init(context.Context, ...tfexec.InitOption) error
Show(context.Context, ...tfexec.ShowOption) (*tfjson.State, error) Show(context.Context, ...tfexec.ShowOption) (*tfjson.State, error)
SetLog(level string) error
SetLogPath(path string) error
} }

View file

@ -299,6 +299,22 @@ func TestCreateCluster(t *testing.T) {
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
wantErr: true, wantErr: true,
}, },
"set log fails": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
tf: &stubTerraform{setLogErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"set log path fails": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
vars: qemuVars,
tf: &stubTerraform{setLogPathErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"no ip": { "no ip": {
pathBase: "terraform", pathBase: "terraform",
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
@ -406,7 +422,7 @@ func TestCreateCluster(t *testing.T) {
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String())) path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
require.NoError(c.PrepareWorkspace(path, tc.vars)) require.NoError(c.PrepareWorkspace(path, tc.vars))
tfOutput, err := c.CreateCluster(context.Background()) tfOutput, err := c.CreateCluster(context.Background(), LogLevelDebug)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
@ -481,6 +497,22 @@ func TestCreateIAM(t *testing.T) {
wantErr bool wantErr bool
want IAMOutput want IAMOutput
}{ }{
"set log fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
tf: &stubTerraform{setLogErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"set log path fails": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
vars: gcpVars,
tf: &stubTerraform{setLogPathErr: someErr},
fs: afero.NewMemMapFs(),
wantErr: true,
},
"gcp works": { "gcp works": {
pathBase: path.Join("terraform", "iam"), pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
@ -685,7 +717,7 @@ func TestCreateIAM(t *testing.T) {
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String())) path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
require.NoError(c.PrepareWorkspace(path, tc.vars)) require.NoError(c.PrepareWorkspace(path, tc.vars))
IAMoutput, err := c.CreateIAMConfig(context.Background(), tc.provider) IAMoutput, err := c.CreateIAMConfig(context.Background(), tc.provider, LogLevelDebug)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
@ -698,6 +730,7 @@ func TestCreateIAM(t *testing.T) {
} }
func TestDestroyInstances(t *testing.T) { func TestDestroyInstances(t *testing.T) {
someErr := errors.New("some error")
testCases := map[string]struct { testCases := map[string]struct {
tf *stubTerraform tf *stubTerraform
wantErr bool wantErr bool
@ -706,9 +739,15 @@ func TestDestroyInstances(t *testing.T) {
tf: &stubTerraform{}, tf: &stubTerraform{},
}, },
"destroy fails": { "destroy fails": {
tf: &stubTerraform{ tf: &stubTerraform{destroyErr: someErr},
destroyErr: errors.New("error"), wantErr: true,
}, },
"setLog fails": {
tf: &stubTerraform{setLogErr: someErr},
wantErr: true,
},
"setLogPath fails": {
tf: &stubTerraform{setLogPathErr: someErr},
wantErr: true, wantErr: true,
}, },
} }
@ -721,7 +760,7 @@ func TestDestroyInstances(t *testing.T) {
tf: tc.tf, tf: tc.tf,
} }
err := c.Destroy(context.Background()) err := c.Destroy(context.Background(), LogLevelDebug)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
@ -788,12 +827,121 @@ func TestCleanupWorkspace(t *testing.T) {
} }
} }
func TestParseLogLevel(t *testing.T) {
testCases := map[string]struct {
level string
want LogLevel
wantErr bool
}{
"json": {
level: "json",
want: LogLevelJSON,
},
"trace": {
level: "trace",
want: LogLevelTrace,
},
"debug": {
level: "debug",
want: LogLevelDebug,
},
"info": {
level: "info",
want: LogLevelInfo,
},
"warn": {
level: "warn",
want: LogLevelWarn,
},
"error": {
level: "error",
want: LogLevelError,
},
"none": {
level: "none",
want: LogLevelNone,
},
"unknown": {
level: "unknown",
wantErr: true,
},
"empty": {
level: "",
wantErr: true,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
level, err := ParseLogLevel(tc.level)
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)
assert.Equal(tc.want, level)
})
}
}
func TestLogLevelString(t *testing.T) {
testCases := map[string]struct {
level LogLevel
want string
}{
"json": {
level: LogLevelJSON,
want: "JSON",
},
"trace": {
level: LogLevelTrace,
want: "TRACE",
},
"debug": {
level: LogLevelDebug,
want: "DEBUG",
},
"info": {
level: LogLevelInfo,
want: "INFO",
},
"warn": {
level: LogLevelWarn,
want: "WARN",
},
"error": {
level: LogLevelError,
want: "ERROR",
},
"none": {
level: LogLevelNone,
want: "",
},
"invalid int": {
level: LogLevel(-1),
want: "",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
assert.Equal(tc.want, tc.level.String())
})
}
}
type stubTerraform struct { type stubTerraform struct {
applyErr error applyErr error
destroyErr error destroyErr error
initErr error initErr error
showErr error showErr error
showState *tfjson.State setLogErr error
setLogPathErr error
showState *tfjson.State
} }
func (s *stubTerraform) Apply(context.Context, ...tfexec.ApplyOption) error { func (s *stubTerraform) Apply(context.Context, ...tfexec.ApplyOption) error {
@ -811,3 +959,11 @@ func (s *stubTerraform) Init(context.Context, ...tfexec.InitOption) error {
func (s *stubTerraform) Show(context.Context, ...tfexec.ShowOption) (*tfjson.State, error) { func (s *stubTerraform) Show(context.Context, ...tfexec.ShowOption) (*tfjson.State, error) {
return s.showState, s.showErr return s.showState, s.showErr
} }
func (s *stubTerraform) SetLog(_ string) error {
return s.setLogErr
}
func (s *stubTerraform) SetLogPath(_ string) error {
return s.setLogPathErr
}

View file

@ -56,6 +56,7 @@ Work with the Constellation configuration file.
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation config generate ## constellation config generate
@ -84,6 +85,7 @@ constellation config generate {aws|azure|gcp|openstack|qemu} [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation config fetch-measurements ## constellation config fetch-measurements
@ -114,6 +116,7 @@ constellation config fetch-measurements [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation config instance-types ## constellation config instance-types
@ -140,6 +143,7 @@ constellation config instance-types [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation config kubernetes-versions ## constellation config kubernetes-versions
@ -166,6 +170,7 @@ constellation config kubernetes-versions [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation create ## constellation create
@ -195,6 +200,7 @@ constellation create [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation init ## constellation init
@ -226,6 +232,7 @@ constellation init [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation mini ## constellation mini
@ -248,6 +255,7 @@ Manage MiniConstellation clusters.
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation mini up ## constellation mini up
@ -275,8 +283,9 @@ constellation mini up [flags]
### Options inherited from parent commands ### Options inherited from parent commands
``` ```
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation mini down ## constellation mini down
@ -304,6 +313,7 @@ constellation mini down [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation verify ## constellation verify
@ -333,6 +343,7 @@ constellation verify [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation upgrade ## constellation upgrade
@ -355,6 +366,7 @@ Find and apply upgrades to your Constellation cluster.
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation upgrade check ## constellation upgrade check
@ -384,6 +396,7 @@ constellation upgrade check [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation upgrade apply ## constellation upgrade apply
@ -413,6 +426,7 @@ constellation upgrade apply [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation recover ## constellation recover
@ -443,6 +457,7 @@ constellation recover [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation terminate ## constellation terminate
@ -472,6 +487,7 @@ constellation terminate [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation version ## constellation version
@ -498,6 +514,7 @@ constellation version [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation iam ## constellation iam
@ -520,6 +537,7 @@ Work with the IAM configuration on your cloud provider.
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation iam create ## constellation iam create
@ -545,6 +563,7 @@ Create IAM configuration on a cloud platform for your Constellation cluster.
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation iam create aws ## constellation iam create aws
@ -576,6 +595,7 @@ constellation iam create aws [flags]
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--generate-config automatically generate a configuration file and fill in the required fields --generate-config automatically generate a configuration file and fill in the required fields
-k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR - only usable in combination with --generate-config (default "v1.25") -k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR - only usable in combination with --generate-config (default "v1.25")
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
-y, --yes create the IAM configuration without further confirmation -y, --yes create the IAM configuration without further confirmation
``` ```
@ -608,6 +628,7 @@ constellation iam create azure [flags]
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--generate-config automatically generate a configuration file and fill in the required fields --generate-config automatically generate a configuration file and fill in the required fields
-k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR - only usable in combination with --generate-config (default "v1.25") -k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR - only usable in combination with --generate-config (default "v1.25")
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
-y, --yes create the IAM configuration without further confirmation -y, --yes create the IAM configuration without further confirmation
``` ```
@ -643,6 +664,7 @@ constellation iam create gcp [flags]
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--generate-config automatically generate a configuration file and fill in the required fields --generate-config automatically generate a configuration file and fill in the required fields
-k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR - only usable in combination with --generate-config (default "v1.25") -k, --kubernetes string Kubernetes version to use in format MAJOR.MINOR - only usable in combination with --generate-config (default "v1.25")
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
-y, --yes create the IAM configuration without further confirmation -y, --yes create the IAM configuration without further confirmation
``` ```
@ -671,6 +693,7 @@ constellation iam destroy [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```
## constellation status ## constellation status
@ -699,5 +722,6 @@ constellation status [flags]
--config string path to the configuration file (default "constellation-conf.yaml") --config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging --debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters --force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
``` ```

View file

@ -152,6 +152,8 @@ const (
EnvVarNoSpinner = EnvVarPrefix + "NO_SPINNER" EnvVarNoSpinner = EnvVarPrefix + "NO_SPINNER"
// MiniConstellationUID is a sentinel value for the UID of a mini constellation. // MiniConstellationUID is a sentinel value for the UID of a mini constellation.
MiniConstellationUID = "mini" MiniConstellationUID = "mini"
// TerraformLogFile is the file name of the Terraform log file.
TerraformLogFile = "terraform.log"
// //
// Kubernetes. // Kubernetes.