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.tfstate.lock.info
*.tfvars
terraform.log
# macOS
.DS_Store

View File

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

View File

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

View File

@ -45,7 +45,7 @@ type stubTerraformClient struct {
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{
IP: c.ip,
Secret: c.initSecret,
@ -54,7 +54,7 @@ func (c *stubTerraformClient) CreateCluster(_ context.Context) (terraform.Create
}, 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
}
@ -62,7 +62,7 @@ func (c *stubTerraformClient) PrepareWorkspace(_ string, _ terraform.Variables)
return c.prepareWorkspaceErr
}
func (c *stubTerraformClient) Destroy(_ context.Context) error {
func (c *stubTerraformClient) Destroy(_ context.Context, _ terraform.LogLevel) error {
c.destroyCalled = true
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.
func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, config *config.Config, insType string, controlPlaneCount, workerCount int,
) (clusterid.File, error) {
image, err := c.image.FetchReference(ctx, config)
func (c *Creator) Create(ctx context.Context, opts CreateOptions) (clusterid.File, error) {
image, err := c.image.FetchReference(ctx, opts.Config)
if err != nil {
return clusterid.File{}, fmt.Errorf("fetching image reference: %w", err)
}
opts.image = image
switch provider {
switch opts.Provider {
case cloudprovider.AWS:
cl, err := c.newTerraformClient(ctx)
if err != nil {
return clusterid.File{}, err
}
defer cl.RemoveInstaller()
return c.createAWS(ctx, cl, config, insType, controlPlaneCount, workerCount, image)
return c.createAWS(ctx, cl, opts)
case cloudprovider.GCP:
cl, err := c.newTerraformClient(ctx)
if err != nil {
return clusterid.File{}, err
}
defer cl.RemoveInstaller()
return c.createGCP(ctx, cl, config, insType, controlPlaneCount, workerCount, image)
return c.createGCP(ctx, cl, opts)
case cloudprovider.Azure:
cl, err := c.newTerraformClient(ctx)
if err != nil {
return clusterid.File{}, err
}
defer cl.RemoveInstaller()
return c.createAzure(ctx, cl, config, insType, controlPlaneCount, workerCount, image)
return c.createAzure(ctx, cl, opts)
case cloudprovider.OpenStack:
cl, err := c.newTerraformClient(ctx)
if err != nil {
return clusterid.File{}, err
}
defer cl.RemoveInstaller()
return c.createOpenStack(ctx, cl, config, controlPlaneCount, workerCount, image)
return c.createOpenStack(ctx, cl, opts)
case cloudprovider.QEMU:
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)
@ -110,38 +121,40 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c
}
defer cl.RemoveInstaller()
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:
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,
insType string, controlPlaneCount, workerCount int, image string,
) (idFile clusterid.File, retErr error) {
func (c *Creator) createAWS(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) {
vars := terraform.AWSClusterVariables{
CommonVariables: terraform.CommonVariables{
Name: config.Name,
CountControlPlanes: controlPlaneCount,
CountWorkers: workerCount,
StateDiskSizeGB: config.StateDiskSizeGB,
Name: opts.Config.Name,
CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: opts.WorkerCount,
StateDiskSizeGB: opts.Config.StateDiskSizeGB,
},
StateDiskType: config.Provider.AWS.StateDiskType,
Region: config.Provider.AWS.Region,
Zone: config.Provider.AWS.Zone,
InstanceType: insType,
AMIImageID: image,
IAMProfileControlPlane: config.Provider.AWS.IAMProfileControlPlane,
IAMProfileWorkerNodes: config.Provider.AWS.IAMProfileWorkerNodes,
Debug: config.IsDebugCluster(),
StateDiskType: opts.Config.Provider.AWS.StateDiskType,
Region: opts.Config.Provider.AWS.Region,
Zone: opts.Config.Provider.AWS.Zone,
InstanceType: opts.InsType,
AMIImageID: opts.image,
IAMProfileControlPlane: opts.Config.Provider.AWS.IAMProfileControlPlane,
IAMProfileWorkerNodes: opts.Config.Provider.AWS.IAMProfileWorkerNodes,
Debug: opts.Config.IsDebugCluster(),
}
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil {
return clusterid.File{}, err
}
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl})
tfOutput, err := cl.CreateCluster(ctx)
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil {
return clusterid.File{}, err
}
@ -154,32 +167,30 @@ func (c *Creator) createAWS(ctx context.Context, cl terraformClient, config *con
}, nil
}
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config,
insType string, controlPlaneCount, workerCount int, image string,
) (idFile clusterid.File, retErr error) {
func (c *Creator) createGCP(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) {
vars := terraform.GCPClusterVariables{
CommonVariables: terraform.CommonVariables{
Name: config.Name,
CountControlPlanes: controlPlaneCount,
CountWorkers: workerCount,
StateDiskSizeGB: config.StateDiskSizeGB,
Name: opts.Config.Name,
CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: opts.WorkerCount,
StateDiskSizeGB: opts.Config.StateDiskSizeGB,
},
Project: config.Provider.GCP.Project,
Region: config.Provider.GCP.Region,
Zone: config.Provider.GCP.Zone,
CredentialsFile: config.Provider.GCP.ServiceAccountKeyPath,
InstanceType: insType,
StateDiskType: config.Provider.GCP.StateDiskType,
ImageID: image,
Debug: config.IsDebugCluster(),
Project: opts.Config.Provider.GCP.Project,
Region: opts.Config.Provider.GCP.Region,
Zone: opts.Config.Provider.GCP.Zone,
CredentialsFile: opts.Config.Provider.GCP.ServiceAccountKeyPath,
InstanceType: opts.InsType,
StateDiskType: opts.Config.Provider.GCP.StateDiskType,
ImageID: opts.image,
Debug: opts.Config.IsDebugCluster(),
}
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil {
return clusterid.File{}, err
}
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl})
tfOutput, err := cl.CreateCluster(ctx)
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil {
return clusterid.File{}, err
}
@ -192,27 +203,26 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
}, nil
}
func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config, insType string, controlPlaneCount, workerCount int, image string,
) (idFile clusterid.File, retErr error) {
func (c *Creator) createAzure(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) {
vars := terraform.AzureClusterVariables{
CommonVariables: terraform.CommonVariables{
Name: config.Name,
CountControlPlanes: controlPlaneCount,
CountWorkers: workerCount,
StateDiskSizeGB: config.StateDiskSizeGB,
Name: opts.Config.Name,
CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: opts.WorkerCount,
StateDiskSizeGB: opts.Config.StateDiskSizeGB,
},
Location: config.Provider.Azure.Location,
ResourceGroup: config.Provider.Azure.ResourceGroup,
UserAssignedIdentity: config.Provider.Azure.UserAssignedIdentity,
InstanceType: insType,
StateDiskType: config.Provider.Azure.StateDiskType,
ImageID: image,
SecureBoot: *config.Provider.Azure.SecureBoot,
CreateMAA: config.Provider.Azure.EnforceIDKeyDigest == idkeydigest.MAAFallback,
Debug: config.IsDebugCluster(),
Location: opts.Config.Provider.Azure.Location,
ResourceGroup: opts.Config.Provider.Azure.ResourceGroup,
UserAssignedIdentity: opts.Config.Provider.Azure.UserAssignedIdentity,
InstanceType: opts.InsType,
StateDiskType: opts.Config.Provider.Azure.StateDiskType,
ImageID: opts.image,
SecureBoot: *opts.Config.Provider.Azure.SecureBoot,
CreateMAA: opts.Config.Provider.Azure.EnforceIDKeyDigest == idkeydigest.MAAFallback,
Debug: opts.Config.IsDebugCluster(),
}
attestVariant, err := variant.FromString(config.AttestationVariant)
attestVariant, err := variant.FromString(opts.Config.AttestationVariant)
if err != nil {
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
}
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl})
tfOutput, err := cl.CreateCluster(ctx)
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil {
return clusterid.File{}, err
}
@ -348,14 +358,12 @@ func normalizeAzureURIs(vars terraform.AzureClusterVariables) terraform.AzureClu
return vars
}
func (c *Creator) createOpenStack(ctx context.Context, cl terraformClient, config *config.Config,
controlPlaneCount, workerCount int, image string,
) (idFile clusterid.File, retErr error) {
func (c *Creator) createOpenStack(ctx context.Context, cl terraformClient, opts CreateOptions) (idFile clusterid.File, retErr error) {
// TODO: Remove this once OpenStack is supported.
if os.Getenv("CONSTELLATION_OPENSTACK_DEV") != "1" {
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(
"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 " +
@ -366,29 +374,29 @@ func (c *Creator) createOpenStack(ctx context.Context, cl terraformClient, confi
vars := terraform.OpenStackClusterVariables{
CommonVariables: terraform.CommonVariables{
Name: config.Name,
CountControlPlanes: controlPlaneCount,
CountWorkers: workerCount,
StateDiskSizeGB: config.StateDiskSizeGB,
Name: opts.Config.Name,
CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: opts.WorkerCount,
StateDiskSizeGB: opts.Config.StateDiskSizeGB,
},
Cloud: config.Provider.OpenStack.Cloud,
AvailabilityZone: config.Provider.OpenStack.AvailabilityZone,
FloatingIPPoolID: config.Provider.OpenStack.FloatingIPPoolID,
FlavorID: config.Provider.OpenStack.FlavorID,
ImageURL: image,
DirectDownload: *config.Provider.OpenStack.DirectDownload,
OpenstackUserDomainName: config.Provider.OpenStack.UserDomainName,
OpenstackUsername: config.Provider.OpenStack.Username,
OpenstackPassword: config.Provider.OpenStack.Password,
Debug: config.IsDebugCluster(),
Cloud: opts.Config.Provider.OpenStack.Cloud,
AvailabilityZone: opts.Config.Provider.OpenStack.AvailabilityZone,
FloatingIPPoolID: opts.Config.Provider.OpenStack.FloatingIPPoolID,
FlavorID: opts.Config.Provider.OpenStack.FlavorID,
ImageURL: opts.image,
DirectDownload: *opts.Config.Provider.OpenStack.DirectDownload,
OpenstackUserDomainName: opts.Config.Provider.OpenStack.UserDomainName,
OpenstackUsername: opts.Config.Provider.OpenStack.Username,
OpenstackPassword: opts.Config.Provider.OpenStack.Password,
Debug: opts.Config.IsDebugCluster(),
}
if err := cl.PrepareWorkspace(path.Join("terraform", strings.ToLower(cloudprovider.OpenStack.String())), &vars); err != nil {
return clusterid.File{}, err
}
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl})
tfOutput, err := cl.CreateCluster(ctx)
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil {
return clusterid.File{}, err
}
@ -401,26 +409,29 @@ func (c *Creator) createOpenStack(ctx context.Context, cl terraformClient, confi
}, nil
}
func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirtRunner, config *config.Config,
controlPlaneCount, workerCount int, source string,
) (idFile clusterid.File, retErr error) {
type qemuCreateOptions struct {
source string
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}
defer rollbackOnError(c.out, &retErr, qemuRollbacker)
defer rollbackOnError(c.out, &retErr, qemuRollbacker, opts.TFLogLevel)
// TODO: render progress bar
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 {
return clusterid.File{}, fmt.Errorf("download raw image: %w", err)
}
libvirtURI := config.Provider.QEMU.LibvirtURI
libvirtURI := opts.Config.Provider.QEMU.LibvirtURI
libvirtSocketPath := "."
switch {
// if no libvirt URI is specified, start a libvirt container
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
}
libvirtURI = libvirt.LibvirtTCPConnectURI
@ -452,21 +463,21 @@ func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, lv libvirt
vars := terraform.QEMUVariables{
CommonVariables: terraform.CommonVariables{
Name: config.Name,
CountControlPlanes: controlPlaneCount,
CountWorkers: workerCount,
StateDiskSizeGB: config.StateDiskSizeGB,
Name: opts.Config.Name,
CountControlPlanes: opts.ControlPlaneCount,
CountWorkers: opts.WorkerCount,
StateDiskSizeGB: opts.Config.StateDiskSizeGB,
},
LibvirtURI: libvirtURI,
LibvirtSocketPath: libvirtSocketPath,
ImagePath: imagePath,
ImageFormat: config.Provider.QEMU.ImageFormat,
CPUCount: config.Provider.QEMU.VCPUs,
MemorySizeMiB: config.Provider.QEMU.Memory,
MetadataAPIImage: config.Provider.QEMU.MetadataAPIImage,
ImageFormat: opts.Config.Provider.QEMU.ImageFormat,
CPUCount: opts.Config.Provider.QEMU.VCPUs,
MemorySizeMiB: opts.Config.Provider.QEMU.Memory,
MetadataAPIImage: opts.Config.Provider.QEMU.MetadataAPIImage,
MetadataLibvirtURI: metadataLibvirtURI,
NVRAM: config.Provider.QEMU.NVRAM,
Firmware: config.Provider.QEMU.Firmware,
NVRAM: opts.Config.Provider.QEMU.NVRAM,
Firmware: opts.Config.Provider.QEMU.Firmware,
}
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
qemuRollbacker.createdWorkspace = true
tfOutput, err := cl.CreateCluster(ctx)
tfOutput, err := cl.CreateCluster(ctx, opts.TFLogLevel)
if err != nil {
return clusterid.File{}, err
}

View File

@ -215,7 +215,15 @@ func TestCreator(t *testing.T) {
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 {
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.
func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context) error {
if err := d.client.Destroy(ctx); err != nil {
func (d *IAMDestroyer) DestroyIAMConfiguration(ctx context.Context, logLevel terraform.LogLevel) error {
if err := d.client.Destroy(ctx, logLevel); err != nil {
return err
}
return d.client.CleanUpWorkspace()
@ -83,11 +83,12 @@ type IAMCreator struct {
newTerraformClient func(ctx context.Context) (terraformClient, error)
}
// IAMConfig holds the necessary values for IAM configuration.
type IAMConfig struct {
GCP GCPIAMConfig
Azure AzureIAMConfig
AWS AWSIAMConfig
// IAMConfigOptions holds the necessary values for IAM configuration.
type IAMConfigOptions struct {
GCP GCPIAMConfig
Azure AzureIAMConfig
AWS AWSIAMConfig
TFLogLevel terraform.LogLevel
}
// 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.
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 {
case cloudprovider.GCP:
cl, err := c.newTerraformClient(ctx)
@ -130,42 +131,42 @@ func (c *IAMCreator) Create(ctx context.Context, provider cloudprovider.Provider
return iamid.File{}, err
}
defer cl.RemoveInstaller()
return c.createGCP(ctx, cl, iamConfig)
return c.createGCP(ctx, cl, opts)
case cloudprovider.Azure:
cl, err := c.newTerraformClient(ctx)
if err != nil {
return iamid.File{}, err
}
defer cl.RemoveInstaller()
return c.createAzure(ctx, cl, iamConfig)
return c.createAzure(ctx, cl, opts)
case cloudprovider.AWS:
cl, err := c.newTerraformClient(ctx)
if err != nil {
return iamid.File{}, err
}
defer cl.RemoveInstaller()
return c.createAWS(ctx, cl, iamConfig)
return c.createAWS(ctx, cl, opts)
default:
return iamid.File{}, fmt.Errorf("unsupported cloud provider: %s", provider)
}
}
// createGCP creates the IAM configuration on GCP.
func (c *IAMCreator) createGCP(ctx context.Context, cl terraformClient, iamConfig *IAMConfig) (retFile iamid.File, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl})
func (c *IAMCreator) createGCP(ctx context.Context, cl terraformClient, opts *IAMConfigOptions) (retFile iamid.File, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
vars := terraform.GCPIAMVariables{
ServiceAccountID: iamConfig.GCP.ServiceAccountID,
Project: iamConfig.GCP.ProjectID,
Region: iamConfig.GCP.Region,
Zone: iamConfig.GCP.Zone,
ServiceAccountID: opts.GCP.ServiceAccountID,
Project: opts.GCP.ProjectID,
Region: opts.GCP.Region,
Zone: opts.GCP.Zone,
}
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.GCP.String())), &vars); err != nil {
return iamid.File{}, err
}
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.GCP)
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.GCP, opts.TFLogLevel)
if err != nil {
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.
func (c *IAMCreator) createAzure(ctx context.Context, cl terraformClient, iamConfig *IAMConfig) (retFile iamid.File, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl})
func (c *IAMCreator) createAzure(ctx context.Context, cl terraformClient, opts *IAMConfigOptions) (retFile iamid.File, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
vars := terraform.AzureIAMVariables{
Region: iamConfig.Azure.Region,
ResourceGroup: iamConfig.Azure.ResourceGroup,
ServicePrincipal: iamConfig.Azure.ServicePrincipal,
Region: opts.Azure.Region,
ResourceGroup: opts.Azure.ResourceGroup,
ServicePrincipal: opts.Azure.ServicePrincipal,
}
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.Azure.String())), &vars); err != nil {
return iamid.File{}, err
}
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.Azure)
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.Azure, opts.TFLogLevel)
if err != nil {
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.
func (c *IAMCreator) createAWS(ctx context.Context, cl terraformClient, iamConfig *IAMConfig) (retFile iamid.File, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl})
func (c *IAMCreator) createAWS(ctx context.Context, cl terraformClient, opts *IAMConfigOptions) (retFile iamid.File, retErr error) {
defer rollbackOnError(c.out, &retErr, &rollbackerTerraform{client: cl}, opts.TFLogLevel)
vars := terraform.AWSIAMVariables{
Region: iamConfig.AWS.Region,
Prefix: iamConfig.AWS.Prefix,
Region: opts.AWS.Region,
Prefix: opts.AWS.Prefix,
}
if err := cl.PrepareWorkspace(path.Join("terraform", "iam", strings.ToLower(cloudprovider.AWS.String())), &vars); err != nil {
return iamid.File{}, err
}
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.AWS)
iamOutput, err := cl.CreateIAMConfig(ctx, cloudprovider.AWS, opts.TFLogLevel)
if err != nil {
return iamid.File{}, err
}

View File

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

View File

@ -11,22 +11,24 @@ import (
"errors"
"fmt"
"io"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
)
// rollbacker does a rollback.
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,
// 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 {
return
}
fmt.Fprintf(w, "An error occurred: %s\n", *onErr)
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?
return
}
@ -37,8 +39,8 @@ type rollbackerTerraform struct {
client terraformClient
}
func (r *rollbackerTerraform) rollback(ctx context.Context) error {
if err := r.client.Destroy(ctx); err != nil {
func (r *rollbackerTerraform) rollback(ctx context.Context, logLevel terraform.LogLevel) error {
if err := r.client.Destroy(ctx, logLevel); err != nil {
return err
}
return r.client.CleanUpWorkspace()
@ -50,9 +52,9 @@ type rollbackerQEMU struct {
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 {
retErr = r.client.Destroy(ctx)
retErr = r.client.Destroy(ctx, logLevel)
}
if retErr := errors.Join(retErr, r.libvirt.Stop(ctx)); retErr != nil {
return retErr

View File

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

View File

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

View File

@ -11,6 +11,7 @@ import (
"errors"
"testing"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"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 {
assert.Error(err)

View File

@ -120,6 +120,7 @@ go_test(
"//cli/internal/helm",
"//cli/internal/iamid",
"//cli/internal/kubernetes",
"//cli/internal/terraform",
"//disk-mapper/recoverproto",
"//internal/atls",
"//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/clusterid"
"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/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/config"
)
type cloudCreator interface {
Create(
ctx context.Context,
provider cloudprovider.Provider,
config *config.Config,
insType string,
coordCount, nodeCount int,
opts cloudcmd.CreateOptions,
) (clusterid.File, error)
}
@ -31,15 +28,15 @@ type cloudIAMCreator interface {
Create(
ctx context.Context,
provider cloudprovider.Provider,
iamConfig *cloudcmd.IAMConfig,
opts *cloudcmd.IAMConfigOptions,
) (iamid.File, error)
}
type iamDestroyer interface {
DestroyIAMConfiguration(ctx context.Context) error
DestroyIAMConfiguration(ctx context.Context, logLevel terraform.LogLevel) error
GetTfstateServiceAccountKey(ctx context.Context) (gcpshared.ServiceAccountKey, error)
}
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/clusterid"
"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/gcpshared"
"github.com/edgelesssys/constellation/v2/internal/config"
"go.uber.org/goleak"
)
@ -34,13 +34,10 @@ type stubCloudCreator struct {
func (c *stubCloudCreator) Create(
_ context.Context,
provider cloudprovider.Provider,
_ *config.Config,
_ string,
_, _ int,
opts cloudcmd.CreateOptions,
) (clusterid.File, error) {
c.createCalled = true
c.id.CloudProvider = provider
c.id.CloudProvider = opts.Provider
return c.id, c.createErr
}
@ -49,7 +46,7 @@ type stubCloudTerminator struct {
terminateErr error
}
func (c *stubCloudTerminator) Terminate(context.Context) error {
func (c *stubCloudTerminator) Terminate(_ context.Context, _ terraform.LogLevel) error {
c.called = true
return c.terminateErr
}
@ -67,7 +64,7 @@ type stubIAMCreator struct {
func (c *stubIAMCreator) Create(
_ context.Context,
provider cloudprovider.Provider,
_ *cloudcmd.IAMConfig,
_ *cloudcmd.IAMConfigOptions,
) (iamid.File, error) {
c.createCalled = true
c.id.CloudProvider = provider
@ -82,7 +79,7 @@ type stubIAMDestroyer struct {
getTfstateKeyErr error
}
func (d *stubIAMDestroyer) DestroyIAMConfiguration(_ context.Context) error {
func (d *stubIAMDestroyer) DestroyIAMConfiguration(_ context.Context, _ terraform.LogLevel) error {
d.destroyCalled = true
return d.destroyErr
}

View File

@ -153,7 +153,15 @@ func (c *createCmd) create(cmd *cobra.Command, creator cloudCreator, fileHandler
}
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()
if err != nil {
return translateCreateErrors(cmd, err)
@ -190,7 +198,7 @@ func (c *createCmd) parseCreateFlags(cmd *cobra.Command) (createFlags, error) {
yes, err := cmd.Flags().GetBool("yes")
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)
@ -206,10 +214,21 @@ func (c *createCmd) parseCreateFlags(cmd *cobra.Command) (createFlags, error) {
}
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{
controllerCount: controllerCount,
workerCount: workerCount,
configPath: configPath,
tfLogLevel: logLevel,
force: force,
yes: yes,
}, nil
@ -220,6 +239,7 @@ type createFlags struct {
controllerCount int
workerCount int
configPath string
tfLogLevel terraform.LogLevel
force bool
yes bool
}

View File

@ -185,6 +185,7 @@ func TestCreate(t *testing.T) {
cmd.SetIn(bytes.NewBufferString(tc.stdin))
cmd.Flags().String("config", constants.ConfigFilename, "") // register persistent flag manually
cmd.Flags().Bool("force", true, "") // register persistent flag manually
cmd.Flags().String("tf-log", "NONE", "") // register persistent flag manually
if tc.yesFlag {
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/iamid"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
@ -147,8 +148,18 @@ func createRunIAMFunc(provider cloudprovider.Provider) func(cmd *cobra.Command,
return fmt.Errorf("unknown provider %s", provider)
}
}
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 {
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.
func newIAMCreator(cmd *cobra.Command) (*iamCreator, error) {
func newIAMCreator(cmd *cobra.Command, logLevel terraform.LogLevel) (*iamCreator, error) {
spinner, err := newSpinnerOrStderr(cmd)
if err != nil {
return nil, fmt.Errorf("creating spinner: %w", err)
@ -170,13 +181,17 @@ func newIAMCreator(cmd *cobra.Command) (*iamCreator, error) {
if err != nil {
return nil, fmt.Errorf("creating logger: %w", err)
}
log.Debugf("Terraform logs will be written into %s at level %s", constants.TerraformLogFile, logLevel.String())
return &iamCreator{
cmd: cmd,
spinner: spinner,
log: log,
creator: cloudcmd.NewIAMCreator(spinner),
fileHandler: file.NewHandler(afero.NewOsFs()),
iamConfig: &cloudcmd.IAMConfig{},
iamConfig: &cloudcmd.IAMConfigOptions{
TFLogLevel: logLevel,
},
}, nil
}
@ -188,7 +203,7 @@ type iamCreator struct {
fileHandler file.Handler
provider cloudprovider.Provider
providerCreator providerIAMCreator
iamConfig *cloudcmd.IAMConfig
iamConfig *cloudcmd.IAMConfigOptions
log debugLog
}
@ -361,7 +376,7 @@ type providerIAMCreator interface {
// writeOutputValuesToConfig writes the output values of the IAM creation to the constellation config 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(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(iamFile iamid.File, fileHandler file.Handler) error
}
@ -369,7 +384,7 @@ type providerIAMCreator interface {
// awsIAMCreator implements the providerIAMCreator interface for AWS.
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")
if err != nil {
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.
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")
if err != nil {
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.
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")
if err != nil {
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().Bool("yes", false, "")
cmd.Flags().String("name", "constell", "")
cmd.Flags().String("tf-log", "NONE", "")
if tc.zoneFlag != "" {
require.NoError(cmd.Flags().Set("zone", tc.zoneFlag))
@ -306,7 +307,7 @@ func TestIAMCreateAWS(t *testing.T) {
spinner: &nopSpinner{},
creator: tc.creator,
fileHandler: fileHandler,
iamConfig: &cloudcmd.IAMConfig{},
iamConfig: &cloudcmd.IAMConfigOptions{},
provider: tc.provider,
providerCreator: &awsIAMCreator{},
}
@ -550,12 +551,13 @@ func TestIAMCreateAzure(t *testing.T) {
cmd.SetErr(&bytes.Buffer{})
cmd.SetIn(bytes.NewBufferString(tc.stdin))
// register persistent flag manually
// register persistent flags manually
cmd.Flags().String("config", constants.ConfigFilename, "")
cmd.Flags().Bool("generate-config", false, "")
cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "")
cmd.Flags().Bool("yes", false, "")
cmd.Flags().String("name", "constell", "")
cmd.Flags().String("tf-log", "NONE", "")
if tc.regionFlag != "" {
require.NoError(cmd.Flags().Set("region", tc.regionFlag))
@ -587,7 +589,7 @@ func TestIAMCreateAzure(t *testing.T) {
spinner: &nopSpinner{},
creator: tc.creator,
fileHandler: fileHandler,
iamConfig: &cloudcmd.IAMConfig{},
iamConfig: &cloudcmd.IAMConfigOptions{},
provider: tc.provider,
providerCreator: &azureIAMCreator{},
}
@ -862,6 +864,7 @@ func TestIAMCreateGCP(t *testing.T) {
cmd.Flags().String("kubernetes", semver.MajorMinor(config.Default().KubernetesVersion), "")
cmd.Flags().Bool("yes", false, "")
cmd.Flags().String("name", "constell", "")
cmd.Flags().String("tf-log", "NONE", "")
if tc.zoneFlag != "" {
require.NoError(cmd.Flags().Set("zone", tc.zoneFlag))
@ -893,7 +896,7 @@ func TestIAMCreateGCP(t *testing.T) {
spinner: &nopSpinner{},
creator: tc.creator,
fileHandler: fileHandler,
iamConfig: &cloudcmd.IAMConfig{},
iamConfig: &cloudcmd.IAMConfigOptions{},
provider: tc.provider,
providerCreator: &gcpIAMCreator{},
}

View File

@ -11,6 +11,7 @@ import (
"os"
"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/constants"
"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 {
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
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) {
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)
}
yes, err := cmd.Flags().GetBool("yes")
if err != nil {
return err
}
c.log.Debugf("\"yes\" flag is set to %t", yes)
gcpFileExists := false
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
}
if !yes {
if !flags.yes {
// Confirmation
confirmString := "Do you really want to destroy your IAM configuration?"
if gcpFileExists {
@ -119,7 +119,7 @@ func (c *destroyCmd) iamDestroy(cmd *cobra.Command, spinner spinnerInterf, destr
spinner.Start("Destroying IAM configuration", false)
defer spinner.Stop()
if err := destroyer.DestroyIAMConfiguration(cmd.Context()); err != nil {
if err := destroyer.DestroyIAMConfiguration(cmd.Context(), flags.tfLogLevel); err != nil {
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)
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.SetErr(&bytes.Buffer{})
cmd.SetIn(bytes.NewBufferString(tc.stdin))
// register persistent flags manually
cmd.Flags().String("tf-log", "NONE", "")
assert.NoError(cmd.Flags().Set("yes", tc.yesFlag))
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/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/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)
}
flags, err := m.parseUpFlags(cmd)
if err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
fileHandler := file.NewHandler(afero.NewOsFs())
// 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 {
return fmt.Errorf("preparing config: %w", err)
}
// create cluster
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()
if err != nil {
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.
func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler) (*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)
}
func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler, flags upFlags) (*config.Config, error) {
// check for existing config
if configPath != "" {
conf, err := config.New(fileHandler, configPath, force)
if flags.configPath != "" {
conf, err := config.New(fileHandler, flags.configPath, flags.force)
var configValidationErr *config.ValidationError
if errors.As(err, &configValidationErr) {
cmd.PrintErrln(configValidationErr.LongMessage())
@ -199,11 +195,11 @@ func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler)
}
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 {
return nil, err
}
_, err = fileHandler.Stat(constants.ConfigFilename)
_, err := fileHandler.Stat(constants.ConfigFilename)
if err == nil {
// config already exists, prompt user to overwrite
cmd.PrintErrln("A config file already exists in the current workspace. Use --config to use an existing config file.")
@ -227,9 +223,17 @@ func (m *miniUpCmd) prepareConfig(cmd *cobra.Command, fileHandler file.Handler)
}
// 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")
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 {
return err
}
@ -271,3 +275,39 @@ func (m *miniUpCmd) initializeMiniCluster(cmd *cobra.Command, fileHandler file.H
m.log.Debugf("Initialized mini cluster")
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/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/file"
)
@ -48,12 +49,12 @@ func runTerminate(cmd *cobra.Command, _ []string) error {
func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.Handler, spinner spinnerInterf,
) error {
yesFlag, err := cmd.Flags().GetBool("yes")
flags, err := parseTerminateFlags(cmd)
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("All of its associated resources will be DESTROYED.")
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)
err = terminator.Terminate(cmd.Context())
err = terminator.Terminate(cmd.Context(), flags.logLevel)
spinner.Stop()
if err != nil {
return fmt.Errorf("terminating Constellation cluster: %w", err)
@ -87,3 +88,28 @@ func terminate(cmd *cobra.Command, terminator cloudTerminator, fileHandler file.
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.SetIn(bytes.NewBufferString(tc.stdin))
// register persistent flags manually
cmd.Flags().String("tf-log", "NONE", "")
require.NotNil(tc.setupFs)
fileHandler := file.NewHandler(tc.setupFs(require, tc.idFile))

View File

@ -5,6 +5,7 @@ go_library(
name = "terraform",
srcs = [
"loader.go",
"logging.go",
"terraform.go",
"variables.go",
],
@ -73,6 +74,7 @@ go_library(
visibility = ["//cli:__subpackages__"],
deps = [
"//internal/cloud/cloudprovider",
"//internal/constants",
"//internal/file",
"@com_github_hashicorp_go_version//:go-version",
"@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 (
"context"
"errors"
"fmt"
"path/filepath"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/file"
"github.com/hashicorp/go-version"
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.
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 {
return CreateOutput{}, err
return CreateOutput{}, fmt.Errorf("terraform init: %w", err)
}
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)
if err != nil {
return CreateOutput{}, err
return CreateOutput{}, fmt.Errorf("terraform show: %w", err)
}
ipOutput, ok := tfState.Values.Outputs["ip"]
@ -177,7 +183,11 @@ type AWSIAMOutput struct {
}
// 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 {
return IAMOutput{}, err
}
@ -285,9 +295,13 @@ func (c *Client) CreateIAMConfig(ctx context.Context, provider cloudprovider.Pro
}
// 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 {
return err
return fmt.Errorf("terraform init: %w", err)
}
return c.tf.Destroy(ctx)
}
@ -354,9 +368,24 @@ func (c *Client) writeVars(vars Variables) error {
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 {
Apply(context.Context, ...tfexec.ApplyOption) error
Destroy(context.Context, ...tfexec.DestroyOption) error
Init(context.Context, ...tfexec.InitOption) 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(),
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": {
pathBase: "terraform",
provider: cloudprovider.QEMU,
@ -406,7 +422,7 @@ func TestCreateCluster(t *testing.T) {
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
require.NoError(c.PrepareWorkspace(path, tc.vars))
tfOutput, err := c.CreateCluster(context.Background())
tfOutput, err := c.CreateCluster(context.Background(), LogLevelDebug)
if tc.wantErr {
assert.Error(err)
@ -481,6 +497,22 @@ func TestCreateIAM(t *testing.T) {
wantErr bool
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": {
pathBase: path.Join("terraform", "iam"),
provider: cloudprovider.GCP,
@ -685,7 +717,7 @@ func TestCreateIAM(t *testing.T) {
path := path.Join(tc.pathBase, strings.ToLower(tc.provider.String()))
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 {
assert.Error(err)
@ -698,6 +730,7 @@ func TestCreateIAM(t *testing.T) {
}
func TestDestroyInstances(t *testing.T) {
someErr := errors.New("some error")
testCases := map[string]struct {
tf *stubTerraform
wantErr bool
@ -706,9 +739,15 @@ func TestDestroyInstances(t *testing.T) {
tf: &stubTerraform{},
},
"destroy fails": {
tf: &stubTerraform{
destroyErr: errors.New("error"),
},
tf: &stubTerraform{destroyErr: someErr},
wantErr: true,
},
"setLog fails": {
tf: &stubTerraform{setLogErr: someErr},
wantErr: true,
},
"setLogPath fails": {
tf: &stubTerraform{setLogPathErr: someErr},
wantErr: true,
},
}
@ -721,7 +760,7 @@ func TestDestroyInstances(t *testing.T) {
tf: tc.tf,
}
err := c.Destroy(context.Background())
err := c.Destroy(context.Background(), LogLevelDebug)
if tc.wantErr {
assert.Error(err)
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 {
applyErr error
destroyErr error
initErr error
showErr error
showState *tfjson.State
applyErr error
destroyErr error
initErr error
showErr error
setLogErr error
setLogPathErr error
showState *tfjson.State
}
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) {
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")
--debug enable debug logging
--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
@ -84,6 +85,7 @@ constellation config generate {aws|azure|gcp|openstack|qemu} [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation config fetch-measurements
@ -114,6 +116,7 @@ constellation config fetch-measurements [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation config instance-types
@ -140,6 +143,7 @@ constellation config instance-types [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation config kubernetes-versions
@ -166,6 +170,7 @@ constellation config kubernetes-versions [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation create
@ -195,6 +200,7 @@ constellation create [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation init
@ -226,6 +232,7 @@ constellation init [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation mini
@ -248,6 +255,7 @@ Manage MiniConstellation clusters.
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation mini up
@ -275,8 +283,9 @@ constellation mini up [flags]
### Options inherited from parent commands
```
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--debug enable debug logging
--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
@ -304,6 +313,7 @@ constellation mini down [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation verify
@ -333,6 +343,7 @@ constellation verify [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## 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")
--debug enable debug logging
--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
@ -384,6 +396,7 @@ constellation upgrade check [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation upgrade apply
@ -413,6 +426,7 @@ constellation upgrade apply [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation recover
@ -443,6 +457,7 @@ constellation recover [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation terminate
@ -472,6 +487,7 @@ constellation terminate [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## constellation version
@ -498,6 +514,7 @@ constellation version [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```
## 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")
--debug enable debug logging
--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
@ -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")
--debug enable debug logging
--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
@ -576,6 +595,7 @@ constellation iam create aws [flags]
--force disable version compatibility checks - might result in corrupted clusters
--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")
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
-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
--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")
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
-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
--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")
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
-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")
--debug enable debug logging
--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
@ -699,5 +722,6 @@ constellation status [flags]
--config string path to the configuration file (default "constellation-conf.yaml")
--debug enable debug logging
--force disable version compatibility checks - might result in corrupted clusters
--tf-log string sets the Terraform log level (default "NONE" - no logs) (default "NONE")
```

View File

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