diff --git a/CHANGELOG.md b/CHANGELOG.md index fdccad855..8425889f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Autoscaling is now directly managed inside Kubernetes, by the Constellation node operator. +- The `constellation create` on GCP now uses Terraform to create and destroy cloud resources. +- GCP instances are now created without public IPs by default. ### Deprecated diff --git a/cli/internal/cloudcmd/clients.go b/cli/internal/cloudcmd/clients.go index 84ae7c43e..ebecb7aa6 100644 --- a/cli/internal/cloudcmd/clients.go +++ b/cli/internal/cloudcmd/clients.go @@ -10,23 +10,16 @@ import ( "context" azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client" - gcpcl "github.com/edgelesssys/constellation/v2/cli/internal/gcp/client" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/state" ) -type gcpclient interface { +type terraformClient interface { GetState() state.ConstellationState - SetState(state.ConstellationState) - CreateVPCs(ctx context.Context) error - CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error - CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error - CreateLoadBalancers(ctx context.Context, isDebugCluster bool) error - TerminateFirewall(ctx context.Context) error - TerminateVPCs(context.Context) error - TerminateLoadBalancers(context.Context) error - TerminateInstances(context.Context) error - Close() error + CreateCluster(ctx context.Context, name string, input terraform.Variables) error + DestroyCluster(ctx context.Context) error + CleanUpWorkspace() error + RemoveInstaller() } type azureclient interface { @@ -39,11 +32,3 @@ type azureclient interface { CreateInstances(ctx context.Context, input azurecl.CreateInstancesInput) error TerminateResourceGroupResources(ctx context.Context) error } - -type qemuclient interface { - GetState() state.ConstellationState - CreateCluster(ctx context.Context, name string, input terraform.CreateClusterInput) error - DestroyCluster(ctx context.Context) error - CleanUpWorkspace() error - RemoveInstaller() -} diff --git a/cli/internal/cloudcmd/clients_test.go b/cli/internal/cloudcmd/clients_test.go index 8cb65a926..626712921 100644 --- a/cli/internal/cloudcmd/clients_test.go +++ b/cli/internal/cloudcmd/clients_test.go @@ -8,12 +8,11 @@ package cloudcmd import ( "context" - "errors" "strconv" "testing" azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client" - gcpcl "github.com/edgelesssys/constellation/v2/cli/internal/gcp/client" + "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/azureshared" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes" @@ -195,196 +194,34 @@ func (c *stubAzureClient) TerminateServicePrincipal(ctx context.Context) error { return c.terminateServicePrincipalErr } -type fakeGcpClient struct { - workers cloudtypes.Instances - controlPlanes cloudtypes.Instances - - workerInstanceGroup string - controlPlaneInstanceGroup string - controlPlaneTemplate string - workerTemplate string - network string - subnetwork string - firewalls []string - project string - uid string - name string - zone string - loadbalancers []string +type stubTerraformClient struct { + state state.ConstellationState + cleanUpWorkspaceCalled bool + removeInstallerCalled bool + destroyClusterCalled bool + createClusterErr error + destroyClusterErr error + cleanUpWorkspaceErr error } -func (c *fakeGcpClient) GetState() state.ConstellationState { - return state.ConstellationState{ - CloudProvider: cloudprovider.GCP.String(), - GCPWorkerInstances: c.workers, - GCPControlPlaneInstances: c.controlPlanes, - GCPWorkerInstanceGroup: c.workerInstanceGroup, - GCPControlPlaneInstanceGroup: c.controlPlaneInstanceGroup, - GCPWorkerInstanceTemplate: c.workerTemplate, - GCPControlPlaneInstanceTemplate: c.controlPlaneTemplate, - GCPNetwork: c.network, - GCPSubnetwork: c.subnetwork, - GCPFirewalls: c.firewalls, - GCPProject: c.project, - Name: c.name, - UID: c.uid, - GCPZone: c.zone, - GCPLoadbalancers: c.loadbalancers, - } +func (c *stubTerraformClient) GetState() state.ConstellationState { + return c.state } -func (c *fakeGcpClient) SetState(stat state.ConstellationState) { - c.workers = stat.GCPWorkerInstances - c.controlPlanes = stat.GCPControlPlaneInstances - c.workerInstanceGroup = stat.GCPWorkerInstanceGroup - c.controlPlaneInstanceGroup = stat.GCPControlPlaneInstanceGroup - c.workerTemplate = stat.GCPWorkerInstanceTemplate - c.controlPlaneTemplate = stat.GCPControlPlaneInstanceTemplate - c.network = stat.GCPNetwork - c.subnetwork = stat.GCPSubnetwork - c.firewalls = stat.GCPFirewalls - c.project = stat.GCPProject - c.name = stat.Name - c.uid = stat.UID - c.zone = stat.GCPZone - c.loadbalancers = stat.GCPLoadbalancers +func (c *stubTerraformClient) CreateCluster(ctx context.Context, name string, input terraform.Variables) error { + return c.createClusterErr } -func (c *fakeGcpClient) CreateVPCs(ctx context.Context) error { - c.network = "network" - c.subnetwork = "subnetwork" - return nil +func (c *stubTerraformClient) DestroyCluster(ctx context.Context) error { + c.destroyClusterCalled = true + return c.destroyClusterErr } -func (c *fakeGcpClient) CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error { - if c.network == "" { - return errors.New("client has not network") - } - for _, rule := range input.Ingress { - c.firewalls = append(c.firewalls, rule.Name) - } - return nil +func (c *stubTerraformClient) CleanUpWorkspace() error { + c.cleanUpWorkspaceCalled = true + return c.cleanUpWorkspaceErr } -func (c *fakeGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error { - c.controlPlaneInstanceGroup = "controlplane-group" - c.workerInstanceGroup = "workers-group" - c.workerTemplate = "worker-template" - c.controlPlaneTemplate = "controlplane-template" - c.workers = make(cloudtypes.Instances) - for i := 0; i < input.CountWorkers; i++ { - id := "id-" + strconv.Itoa(i) - c.workers[id] = cloudtypes.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} - } - c.controlPlanes = make(cloudtypes.Instances) - for i := 0; i < input.CountControlPlanes; i++ { - id := "id-" + strconv.Itoa(i) - c.controlPlanes[id] = cloudtypes.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} - } - return nil -} - -func (c *fakeGcpClient) CreateLoadBalancers(ctx context.Context, isDebugCluster bool) error { - c.loadbalancers = []string{"kube-lb", "boot-lb", "verify-lb"} - return nil -} - -func (c *fakeGcpClient) TerminateFirewall(ctx context.Context) error { - if len(c.firewalls) == 0 { - return nil - } - c.firewalls = nil - return nil -} - -func (c *fakeGcpClient) TerminateVPCs(context.Context) error { - if len(c.firewalls) != 0 { - return errors.New("client has firewalls, which must be deleted first") - } - c.network = "" - c.subnetwork = "" - return nil -} - -func (c *fakeGcpClient) TerminateInstances(context.Context) error { - c.workerTemplate = "" - c.controlPlaneTemplate = "" - c.workerInstanceGroup = "" - c.controlPlaneInstanceGroup = "" - c.workers = nil - c.controlPlanes = nil - return nil -} - -func (c *fakeGcpClient) TerminateLoadBalancers(context.Context) error { - c.loadbalancers = nil - return nil -} - -func (c *fakeGcpClient) Close() error { - return nil -} - -type stubGcpClient struct { - terminateFirewallCalled bool - terminateInstancesCalled bool - terminateVPCsCalled bool - closeCalled bool - - createVPCsErr error - createFirewallErr error - createInstancesErr error - createLoadBalancerErr error - terminateFirewallErr error - terminateVPCsErr error - terminateInstancesErr error - terminateLoadBalancerErr error - closeErr error -} - -func (c *stubGcpClient) GetState() state.ConstellationState { - return state.ConstellationState{} -} - -func (c *stubGcpClient) SetState(state.ConstellationState) { -} - -func (c *stubGcpClient) CreateVPCs(ctx context.Context) error { - return c.createVPCsErr -} - -func (c *stubGcpClient) CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error { - return c.createFirewallErr -} - -func (c *stubGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error { - return c.createInstancesErr -} - -func (c *stubGcpClient) CreateLoadBalancers(ctx context.Context, isDebugClient bool) error { - return c.createLoadBalancerErr -} - -func (c *stubGcpClient) TerminateFirewall(ctx context.Context) error { - c.terminateFirewallCalled = true - return c.terminateFirewallErr -} - -func (c *stubGcpClient) TerminateVPCs(context.Context) error { - c.terminateVPCsCalled = true - return c.terminateVPCsErr -} - -func (c *stubGcpClient) TerminateInstances(context.Context) error { - c.terminateInstancesCalled = true - return c.terminateInstancesErr -} - -func (c *stubGcpClient) TerminateLoadBalancers(context.Context) error { - return c.terminateLoadBalancerErr -} - -func (c *stubGcpClient) Close() error { - c.closeCalled = true - return c.closeErr +func (c *stubTerraformClient) RemoveInstaller() { + c.removeInstallerCalled = true } diff --git a/cli/internal/cloudcmd/create.go b/cli/internal/cloudcmd/create.go index 8381ed0b3..3c0e53c24 100644 --- a/cli/internal/cloudcmd/create.go +++ b/cli/internal/cloudcmd/create.go @@ -13,8 +13,6 @@ import ( "runtime" azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client" - "github.com/edgelesssys/constellation/v2/cli/internal/gcp" - gcpcl "github.com/edgelesssys/constellation/v2/cli/internal/gcp/client" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes" @@ -25,28 +23,21 @@ import ( // Creator creates cloud resources. type Creator struct { - out io.Writer - newGCPClient func(ctx context.Context, project, zone, region, name string) (gcpclient, error) - newAzureClient func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) - newQEMUClient func(ctx context.Context) (qemuclient, error) + out io.Writer + newTerraformClient func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) + newAzureClient func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) } // NewCreator creates a new creator. func NewCreator(out io.Writer) *Creator { return &Creator{ out: out, - newGCPClient: func(ctx context.Context, project, zone, region, name string) (gcpclient, error) { - return gcpcl.NewInitialized(ctx, project, zone, region, name) + newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) { + return terraform.New(ctx, provider) }, newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) { return azurecl.NewInitialized(subscriptionID, tenantID, name, location, resourceGroup) }, - newQEMUClient: func(ctx context.Context) (qemuclient, error) { - if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" { - return nil, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH) - } - return terraform.New(ctx, cloudprovider.QEMU) - }, } } @@ -63,18 +54,12 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c switch provider { case cloudprovider.GCP: - cl, err := c.newGCPClient( - ctx, - config.Provider.GCP.Project, - config.Provider.GCP.Zone, - config.Provider.GCP.Region, - name, - ) + cl, err := c.newTerraformClient(ctx, provider) if err != nil { return state.ConstellationState{}, err } - defer cl.Close() - return c.createGCP(ctx, cl, config, insType, controlPlaneCount, workerCount, ingressRules) + defer cl.RemoveInstaller() + return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount) case cloudprovider.Azure: cl, err := c.newAzureClient( config.Provider.Azure.SubscriptionID, @@ -88,7 +73,10 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c } return c.createAzure(ctx, cl, config, insType, controlPlaneCount, workerCount, ingressRules) case cloudprovider.QEMU: - cl, err := c.newQEMUClient(ctx) + if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" { + return state.ConstellationState{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH) + } + cl, err := c.newTerraformClient(ctx, provider) if err != nil { return state.ConstellationState{}, err } @@ -99,75 +87,29 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c } } -func (c *Creator) createGCP(ctx context.Context, cl gcpclient, config *config.Config, insType string, controlPlaneCount, workerCount int, ingressRules cloudtypes.Firewall, +func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *config.Config, + name, insType string, controlPlaneCount, workerCount int, ) (stat state.ConstellationState, retErr error) { - defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerGCP{client: cl}) + defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl}) - if err := cl.CreateVPCs(ctx); err != nil { - return state.ConstellationState{}, err - } - - if err := cl.CreateFirewall(ctx, gcpcl.FirewallInput{ - Ingress: ingressRules, - Egress: constants.EgressRules, - }); err != nil { - return state.ConstellationState{}, err - } - - // additionally create allow-internal rules - internalFirewallInput := gcpcl.FirewallInput{ - Ingress: cloudtypes.Firewall{ - { - Name: "allow-cluster-internal-tcp", - Protocol: "tcp", - IPRange: gcpcl.SubnetExtCIDR, - }, - { - Name: "allow-cluster-internal-udp", - Protocol: "udp", - IPRange: gcpcl.SubnetExtCIDR, - }, - { - Name: "allow-cluster-internal-icmp", - Protocol: "icmp", - IPRange: gcpcl.SubnetExtCIDR, - }, - { - Name: "allow-node-internal-tcp", - Protocol: "tcp", - IPRange: gcpcl.SubnetCIDR, - }, - { - Name: "allow-node-internal-udp", - Protocol: "udp", - IPRange: gcpcl.SubnetCIDR, - }, - { - Name: "allow-node-internal-icmp", - Protocol: "icmp", - IPRange: gcpcl.SubnetCIDR, - }, + vars := &terraform.GCPVariables{ + CommonVariables: terraform.CommonVariables{ + Name: name, + CountControlPlanes: controlPlaneCount, + CountWorkers: workerCount, + StateDiskSizeGB: config.StateDiskSizeGB, }, - } - if err := cl.CreateFirewall(ctx, internalFirewallInput); err != nil { - return state.ConstellationState{}, err + 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: config.Provider.GCP.Image, + Debug: config.IsDebugCluster(), } - createInput := gcpcl.CreateInstancesInput{ - EnableSerialConsole: config.IsDebugCluster(), - CountControlPlanes: controlPlaneCount, - CountWorkers: workerCount, - ImageID: config.Provider.GCP.Image, - InstanceType: insType, - StateDiskSizeGB: config.StateDiskSizeGB, - StateDiskType: config.Provider.GCP.StateDiskType, - KubeEnv: gcp.KubeEnv, - } - if err := cl.CreateInstances(ctx, createInput); err != nil { - return state.ConstellationState{}, err - } - - if err := cl.CreateLoadBalancers(ctx, config.IsDebugCluster()); err != nil { + if err := cl.CreateCluster(ctx, name, vars); err != nil { return state.ConstellationState{}, err } @@ -211,25 +153,27 @@ func (c *Creator) createAzure(ctx context.Context, cl azureclient, config *confi return cl.GetState(), nil } -func (c *Creator) createQEMU(ctx context.Context, cl qemuclient, name string, config *config.Config, controlPlaneCount, workerCount int, +func (c *Creator) createQEMU(ctx context.Context, cl terraformClient, name string, config *config.Config, + controlPlaneCount, workerCount int, ) (stat state.ConstellationState, retErr error) { - defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerQEMU{client: cl}) + defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl}) - input := terraform.CreateClusterInput{ - CountControlPlanes: controlPlaneCount, - CountWorkers: workerCount, - QEMU: terraform.QEMUInput{ - ImagePath: config.Provider.QEMU.Image, - ImageFormat: config.Provider.QEMU.ImageFormat, - CPUCount: config.Provider.QEMU.VCPUs, - MemorySizeMiB: config.Provider.QEMU.Memory, - StateDiskSizeGB: config.StateDiskSizeGB, - IPRangeStart: config.Provider.QEMU.IPRangeStart, - MetadataAPIImage: config.Provider.QEMU.MetadataAPIImage, + vars := &terraform.QEMUVariables{ + CommonVariables: terraform.CommonVariables{ + Name: name, + CountControlPlanes: controlPlaneCount, + CountWorkers: workerCount, + StateDiskSizeGB: config.StateDiskSizeGB, }, + ImagePath: config.Provider.QEMU.Image, + ImageFormat: config.Provider.QEMU.ImageFormat, + CPUCount: config.Provider.QEMU.VCPUs, + MemorySizeMiB: config.Provider.QEMU.Memory, + IPRangeStart: config.Provider.QEMU.IPRangeStart, + MetadataAPIImage: config.Provider.QEMU.MetadataAPIImage, } - if err := cl.CreateCluster(ctx, name, input); err != nil { + if err := cl.CreateCluster(ctx, name, vars); err != nil { return state.ConstellationState{}, err } diff --git a/cli/internal/cloudcmd/create_test.go b/cli/internal/cloudcmd/create_test.go index 7de442571..f59b6f8dd 100644 --- a/cli/internal/cloudcmd/create_test.go +++ b/cli/internal/cloudcmd/create_test.go @@ -21,29 +21,8 @@ import ( func TestCreator(t *testing.T) { wantGCPState := state.ConstellationState{ - CloudProvider: cloudprovider.GCP.String(), - GCPProject: "project", - GCPControlPlaneInstances: cloudtypes.Instances{ - "id-0": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"}, - "id-1": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"}, - }, - GCPWorkerInstances: cloudtypes.Instances{ - "id-0": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"}, - "id-1": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"}, - "id-2": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"}, - }, - GCPWorkerInstanceGroup: "workers-group", - GCPControlPlaneInstanceGroup: "controlplane-group", - GCPWorkerInstanceTemplate: "worker-template", - GCPControlPlaneInstanceTemplate: "controlplane-template", - GCPNetwork: "network", - GCPSubnetwork: "subnetwork", - GCPLoadbalancers: []string{"kube-lb", "boot-lb", "verify-lb"}, - GCPFirewalls: []string{ - "bootstrapper", "ssh", "nodeport", "kubernetes", "konnectivity", "recovery", - "allow-cluster-internal-tcp", "allow-cluster-internal-udp", "allow-cluster-internal-icmp", - "allow-node-internal-tcp", "allow-node-internal-udp", "allow-node-internal-icmp", - }, + CloudProvider: cloudprovider.GCP.String(), + LoadBalancerIP: "192.0.2.1", } wantAzureState := state.ConstellationState{ @@ -66,8 +45,8 @@ func TestCreator(t *testing.T) { someErr := errors.New("failed") testCases := map[string]struct { - gcpclient gcpclient - newGCPClientErr error + tfClient terraformClient + newTfClientErr error azureclient azureclient newAzureClientErr error provider cloudprovider.Provider @@ -77,40 +56,19 @@ func TestCreator(t *testing.T) { wantRollback bool // Use only together with stubClients. }{ "gcp": { - gcpclient: &fakeGcpClient{project: "project"}, + tfClient: &stubTerraformClient{state: wantGCPState}, provider: cloudprovider.GCP, config: config.Default(), wantState: wantGCPState, }, "gcp newGCPClient error": { - newGCPClientErr: someErr, - provider: cloudprovider.GCP, - config: config.Default(), - wantErr: true, + newTfClientErr: someErr, + provider: cloudprovider.GCP, + config: config.Default(), + wantErr: true, }, - "gcp CreateVPCs error": { - gcpclient: &stubGcpClient{createVPCsErr: someErr}, - provider: cloudprovider.GCP, - config: config.Default(), - wantErr: true, - wantRollback: true, - }, - "gcp CreateFirewall error": { - gcpclient: &stubGcpClient{createFirewallErr: someErr}, - provider: cloudprovider.GCP, - config: config.Default(), - wantErr: true, - wantRollback: true, - }, - "gcp CreateInstances error": { - gcpclient: &stubGcpClient{createInstancesErr: someErr}, - provider: cloudprovider.GCP, - config: config.Default(), - wantErr: true, - wantRollback: true, - }, - "gcp CreateLoadBalancer error": { - gcpclient: &stubGcpClient{createLoadBalancerErr: someErr}, + "gcp create cluster error": { + tfClient: &stubTerraformClient{createClusterErr: someErr}, provider: cloudprovider.GCP, config: config.Default(), wantErr: true, @@ -162,8 +120,8 @@ func TestCreator(t *testing.T) { creator := &Creator{ out: &bytes.Buffer{}, - newGCPClient: func(ctx context.Context, project, zone, region, name string) (gcpclient, error) { - return tc.gcpclient, tc.newGCPClientErr + newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) { + return tc.tfClient, tc.newTfClientErr }, newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) { return tc.azureclient, tc.newAzureClientErr @@ -176,12 +134,12 @@ func TestCreator(t *testing.T) { assert.Error(err) if tc.wantRollback { switch tc.provider { + case cloudprovider.QEMU: + fallthrough case cloudprovider.GCP: - cl := tc.gcpclient.(*stubGcpClient) - assert.True(cl.terminateFirewallCalled) - assert.True(cl.terminateInstancesCalled) - assert.True(cl.terminateVPCsCalled) - assert.True(cl.closeCalled) + cl := tc.tfClient.(*stubTerraformClient) + assert.True(cl.destroyClusterCalled) + assert.True(cl.cleanUpWorkspaceCalled) case cloudprovider.Azure: cl := tc.azureclient.(*stubAzureClient) assert.True(cl.terminateResourceGroupResourcesCalled) diff --git a/cli/internal/cloudcmd/rollback.go b/cli/internal/cloudcmd/rollback.go index bc9460c99..f0a2e4f92 100644 --- a/cli/internal/cloudcmd/rollback.go +++ b/cli/internal/cloudcmd/rollback.go @@ -34,19 +34,6 @@ func rollbackOnError(ctx context.Context, w io.Writer, onErr *error, roll rollba fmt.Fprintln(w, "Rollback succeeded.") } -type rollbackerGCP struct { - client gcpclient -} - -func (r *rollbackerGCP) rollback(ctx context.Context) error { - var err error - err = multierr.Append(err, r.client.TerminateLoadBalancers(ctx)) - err = multierr.Append(err, r.client.TerminateInstances(ctx)) - err = multierr.Append(err, r.client.TerminateFirewall(ctx)) - err = multierr.Append(err, r.client.TerminateVPCs(ctx)) - return err -} - type rollbackerAzure struct { client azureclient } @@ -55,11 +42,11 @@ func (r *rollbackerAzure) rollback(ctx context.Context) error { return r.client.TerminateResourceGroupResources(ctx) } -type rollbackerQEMU struct { - client qemuclient +type rollbackerTerraform struct { + client terraformClient } -func (r *rollbackerQEMU) rollback(ctx context.Context) error { +func (r *rollbackerTerraform) rollback(ctx context.Context) error { var err error err = multierr.Append(err, r.client.DestroyCluster(ctx)) err = multierr.Append(err, r.client.CleanUpWorkspace()) diff --git a/cli/internal/cloudcmd/terminate.go b/cli/internal/cloudcmd/terminate.go index 0c5e2f993..33d934e7c 100644 --- a/cli/internal/cloudcmd/terminate.go +++ b/cli/internal/cloudcmd/terminate.go @@ -11,7 +11,6 @@ import ( "fmt" azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client" - gcpcl "github.com/edgelesssys/constellation/v2/cli/internal/gcp/client" "github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/state" @@ -19,23 +18,19 @@ import ( // Terminator deletes cloud provider resources. type Terminator struct { - newGCPClient func(ctx context.Context) (gcpclient, error) - newAzureClient func(subscriptionID, tenantID string) (azureclient, error) - newQEMUClient func(ctx context.Context) (qemuclient, error) + newTerraformClient func(ctx context.Context) (terraformClient, error) + newAzureClient func(subscriptionID, tenantID string) (azureclient, error) } // NewTerminator create a new cloud terminator. func NewTerminator() *Terminator { return &Terminator{ - newGCPClient: func(ctx context.Context) (gcpclient, error) { - return gcpcl.NewFromDefault(ctx) + newTerraformClient: func(ctx context.Context) (terraformClient, error) { + return terraform.New(ctx, cloudprovider.GCP) }, newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) { return azurecl.NewFromDefault(subscriptionID, tenantID) }, - newQEMUClient: func(ctx context.Context) (qemuclient, error) { - return terraform.New(ctx, cloudprovider.QEMU) - }, } } @@ -43,57 +38,33 @@ func NewTerminator() *Terminator { func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationState) error { provider := cloudprovider.FromString(state.CloudProvider) switch provider { - case cloudprovider.GCP: - cl, err := t.newGCPClient(ctx) - if err != nil { - return err - } - defer cl.Close() - return t.terminateGCP(ctx, cl, state) case cloudprovider.Azure: cl, err := t.newAzureClient(state.AzureSubscription, state.AzureTenant) if err != nil { return err } return t.terminateAzure(ctx, cl, state) + case cloudprovider.GCP: + fallthrough case cloudprovider.QEMU: - cl, err := t.newQEMUClient(ctx) + cl, err := t.newTerraformClient(ctx) if err != nil { return err } defer cl.RemoveInstaller() - return t.terminateQEMU(ctx, cl) + return t.terminateTerraform(ctx, cl) default: return fmt.Errorf("unsupported provider: %s", provider) } } -func (t *Terminator) terminateGCP(ctx context.Context, cl gcpclient, state state.ConstellationState) error { - cl.SetState(state) - - if err := cl.TerminateLoadBalancers(ctx); err != nil { - return err - } - if err := cl.TerminateInstances(ctx); err != nil { - return err - } - if err := cl.TerminateFirewall(ctx); err != nil { - return err - } - if err := cl.TerminateVPCs(ctx); err != nil { - return err - } - - return nil -} - func (t *Terminator) terminateAzure(ctx context.Context, cl azureclient, state state.ConstellationState) error { cl.SetState(state) return cl.TerminateResourceGroupResources(ctx) } -func (t *Terminator) terminateQEMU(ctx context.Context, cl qemuclient) error { +func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient) error { if err := cl.DestroyCluster(ctx); err != nil { return err } diff --git a/cli/internal/cloudcmd/terminate_test.go b/cli/internal/cloudcmd/terminate_test.go index 16c08624a..14d7a81b1 100644 --- a/cli/internal/cloudcmd/terminate_test.go +++ b/cli/internal/cloudcmd/terminate_test.go @@ -18,25 +18,6 @@ import ( ) func TestTerminator(t *testing.T) { - someGCPState := func() state.ConstellationState { - return state.ConstellationState{ - CloudProvider: cloudprovider.GCP.String(), - GCPProject: "project", - GCPWorkerInstances: cloudtypes.Instances{ - "id-0": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"}, - "id-1": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"}, - }, - GCPControlPlaneInstances: cloudtypes.Instances{ - "id-c": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"}, - }, - GCPWorkerInstanceGroup: "worker-group", - GCPControlPlaneInstanceGroup: "controlplane-group", - GCPWorkerInstanceTemplate: "template", - GCPControlPlaneInstanceTemplate: "template", - GCPNetwork: "network", - GCPFirewalls: []string{"a", "b", "c"}, - } - } someAzureState := func() state.ConstellationState { return state.ConstellationState{ CloudProvider: cloudprovider.Azure.String(), @@ -53,36 +34,45 @@ func TestTerminator(t *testing.T) { someErr := errors.New("failed") testCases := map[string]struct { - gcpclient gcpclient - newGCPClientErr error + tfClient terraformClient + newTfClientErr error azureclient azureclient newAzureClientErr error state state.ConstellationState wantErr bool }{ "gcp": { - gcpclient: &stubGcpClient{}, - state: someGCPState(), + tfClient: &stubTerraformClient{}, + state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, }, - "gcp newGCPClient error": { - newGCPClientErr: someErr, - state: someGCPState(), - wantErr: true, + "gcp newTfClientErr": { + newTfClientErr: someErr, + state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, + wantErr: true, }, - "gcp terminateInstances error": { - gcpclient: &stubGcpClient{terminateInstancesErr: someErr}, - state: someGCPState(), - wantErr: true, + "gcp destroy cluster error": { + tfClient: &stubTerraformClient{destroyClusterErr: someErr}, + state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, + wantErr: true, }, - "gcp terminateFirewall error": { - gcpclient: &stubGcpClient{terminateFirewallErr: someErr}, - state: someGCPState(), - wantErr: true, + "gcp clean up workspace error": { + tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr}, + state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()}, + wantErr: true, }, - "gcp terminateVPCs error": { - gcpclient: &stubGcpClient{terminateVPCsErr: someErr}, - state: someGCPState(), - wantErr: true, + "qemu": { + tfClient: &stubTerraformClient{}, + state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, + }, + "qemu destroy cluster error": { + tfClient: &stubTerraformClient{destroyClusterErr: someErr}, + state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, + wantErr: true, + }, + "qemu clean up workspace error": { + tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr}, + state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, + wantErr: true, }, "azure": { azureclient: &stubAzureClient{}, @@ -109,8 +99,8 @@ func TestTerminator(t *testing.T) { assert := assert.New(t) terminator := &Terminator{ - newGCPClient: func(ctx context.Context) (gcpclient, error) { - return tc.gcpclient, tc.newGCPClientErr + newTerraformClient: func(ctx context.Context) (terraformClient, error) { + return tc.tfClient, tc.newTfClientErr }, newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) { return tc.azureclient, tc.newAzureClientErr @@ -125,11 +115,11 @@ func TestTerminator(t *testing.T) { assert.NoError(err) switch cloudprovider.FromString(tc.state.CloudProvider) { case cloudprovider.GCP: - cl := tc.gcpclient.(*stubGcpClient) - assert.True(cl.terminateFirewallCalled) - assert.True(cl.terminateInstancesCalled) - assert.True(cl.terminateVPCsCalled) - assert.True(cl.closeCalled) + fallthrough + case cloudprovider.QEMU: + cl := tc.tfClient.(*stubTerraformClient) + assert.True(cl.destroyClusterCalled) + assert.True(cl.removeInstallerCalled) case cloudprovider.Azure: cl := tc.azureclient.(*stubAzureClient) assert.True(cl.terminateResourceGroupResourcesCalled) diff --git a/cli/internal/cmd/init_test.go b/cli/internal/cmd/init_test.go index 0275c4f3a..38ecce4de 100644 --- a/cli/internal/cmd/init_test.go +++ b/cli/internal/cmd/init_test.go @@ -49,8 +49,7 @@ func TestInitArgumentValidation(t *testing.T) { func TestInitialize(t *testing.T) { testGcpState := &state.ConstellationState{ - CloudProvider: "GCP", - GCPWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}}, + CloudProvider: "GCP", } gcpServiceAccKey := &gcpshared.ServiceAccountKey{ Type: "service_account", diff --git a/cli/internal/terraform/input.go b/cli/internal/terraform/input.go deleted file mode 100644 index 29b8ed6bb..000000000 --- a/cli/internal/terraform/input.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright (c) Edgeless Systems GmbH - -SPDX-License-Identifier: AGPL-3.0-only -*/ - -package terraform - -// CreateClusterInput is user configuration for creating a cluster with Terraform. -type CreateClusterInput struct { - // CountControlPlanes is the number of control-plane nodes to create. - CountControlPlanes int - // CountWorkers is the number of worker nodes to create. - CountWorkers int - // QEMU is the configuration for QEMU clusters. - QEMU QEMUInput -} - -// QEMUInput is user configuration for creating a QEMU cluster with Terraform. -type QEMUInput struct { - // CPUCount is the number of CPUs to allocate to each node. - CPUCount int - // MemorySizeMiB is the amount of memory to allocate to each node, in MiB. - MemorySizeMiB int - // StateDiskSizeGB is the size of the state disk to allocate to each node, in GB. - StateDiskSizeGB int - // IPRangeStart is the first IP address in the IP range to allocate to the cluster. - IPRangeStart int - // ImagePath is the path to the image to use for the nodes. - ImagePath string - // ImageFormat is the format of the image from ImagePath. - ImageFormat string - // MetadataAPIImage is the container image to use for the metadata API. - MetadataAPIImage string -} diff --git a/cli/internal/terraform/terraform.go b/cli/internal/terraform/terraform.go index 10b76576e..f0e1283fa 100644 --- a/cli/internal/terraform/terraform.go +++ b/cli/internal/terraform/terraform.go @@ -9,7 +9,6 @@ package terraform import ( "context" "errors" - "fmt" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/file" @@ -59,7 +58,7 @@ func New(ctx context.Context, provider cloudprovider.Provider) (*Client, error) } // CreateCluster creates a Constellation cluster using Terraform. -func (c *Client) CreateCluster(ctx context.Context, name string, input CreateClusterInput) error { +func (c *Client) CreateCluster(ctx context.Context, name string, vars Variables) error { if err := prepareWorkspace(c.file, c.provider); err != nil { return err } @@ -68,7 +67,7 @@ func (c *Client) CreateCluster(ctx context.Context, name string, input CreateClu return err } - if err := writeUserConfig(c.file, c.provider, name, input); err != nil { + if err := c.file.Write(terraformVarsFile, []byte(vars.String())); err != nil { return err } @@ -137,35 +136,6 @@ func (c *Client) GetState() state.ConstellationState { return c.state } -// writeUserConfig writes the user config file for Terraform. -func writeUserConfig(file file.Handler, provider cloudprovider.Provider, name string, input CreateClusterInput) error { - var userConfig string - switch provider { - case cloudprovider.QEMU: - userConfig = fmt.Sprintf(` -constellation_coreos_image = "%s" -image_format = "%s" -control_plane_count = %d -worker_count = %d -vcpus = %d -memory = %d -state_disk_size = %d -ip_range_start = %d -metadata_api_image = "%s" -name = "%s" -`, - input.QEMU.ImagePath, input.QEMU.ImageFormat, - input.CountControlPlanes, input.CountWorkers, - input.QEMU.CPUCount, input.QEMU.MemorySizeMiB, input.QEMU.StateDiskSizeGB, - input.QEMU.IPRangeStart, - input.QEMU.MetadataAPIImage, - name, - ) - } - - return file.Write(terraformVarsFile, []byte(userConfig)) -} - // GetExecutable returns a Terraform executable either from the local filesystem, // or downloads the latest version fulfilling the version constraint. func GetExecutable(ctx context.Context, workingDir string) (terraform *tfexec.Terraform, remove func(), err error) { diff --git a/terraform/gcp/main.tf b/cli/internal/terraform/terraform/gcp/main.tf similarity index 100% rename from terraform/gcp/main.tf rename to cli/internal/terraform/terraform/gcp/main.tf diff --git a/terraform/gcp/modules/instance_group/main.tf b/cli/internal/terraform/terraform/gcp/modules/instance_group/main.tf similarity index 100% rename from terraform/gcp/modules/instance_group/main.tf rename to cli/internal/terraform/terraform/gcp/modules/instance_group/main.tf diff --git a/terraform/gcp/modules/instance_group/outputs.tf b/cli/internal/terraform/terraform/gcp/modules/instance_group/outputs.tf similarity index 100% rename from terraform/gcp/modules/instance_group/outputs.tf rename to cli/internal/terraform/terraform/gcp/modules/instance_group/outputs.tf diff --git a/terraform/gcp/modules/instance_group/variables.tf b/cli/internal/terraform/terraform/gcp/modules/instance_group/variables.tf similarity index 100% rename from terraform/gcp/modules/instance_group/variables.tf rename to cli/internal/terraform/terraform/gcp/modules/instance_group/variables.tf diff --git a/terraform/gcp/modules/loadbalancer/main.tf b/cli/internal/terraform/terraform/gcp/modules/loadbalancer/main.tf similarity index 100% rename from terraform/gcp/modules/loadbalancer/main.tf rename to cli/internal/terraform/terraform/gcp/modules/loadbalancer/main.tf diff --git a/terraform/gcp/modules/loadbalancer/variables.tf b/cli/internal/terraform/terraform/gcp/modules/loadbalancer/variables.tf similarity index 100% rename from terraform/gcp/modules/loadbalancer/variables.tf rename to cli/internal/terraform/terraform/gcp/modules/loadbalancer/variables.tf diff --git a/cli/internal/terraform/terraform/gcp/outputs.tf b/cli/internal/terraform/terraform/gcp/outputs.tf new file mode 100644 index 000000000..68bf1de9b --- /dev/null +++ b/cli/internal/terraform/terraform/gcp/outputs.tf @@ -0,0 +1,3 @@ +output "ip" { + value = google_compute_global_address.loadbalancer_ip.address +} diff --git a/terraform/gcp/variables.tf b/cli/internal/terraform/terraform/gcp/variables.tf similarity index 100% rename from terraform/gcp/variables.tf rename to cli/internal/terraform/terraform/gcp/variables.tf index b78723a22..faf8477f1 100644 --- a/terraform/gcp/variables.tf +++ b/cli/internal/terraform/terraform/gcp/variables.tf @@ -4,6 +4,22 @@ variable "name" { description = "Base name of the cluster." } +variable "control_plane_count" { + type = number + description = "The number of control plane nodes to deploy." +} + +variable "worker_count" { + type = number + description = "The number of worker nodes to deploy." +} + +variable "state_disk_size" { + type = number + default = 30 + description = "The size of the state disk in GB." +} + variable "project" { type = string description = "The GCP project to deploy the cluster in." @@ -24,27 +40,11 @@ variable "credentials_file" { description = "The path to the GCP credentials file." } -variable "control_plane_count" { - type = number - description = "The number of control plane nodes to deploy." -} - -variable "worker_count" { - type = number - description = "The number of worker nodes to deploy." -} - variable "instance_type" { type = string description = "The GCP instance type to deploy." } -variable "state_disk_size" { - type = number - default = 30 - description = "The size of the state disk in GB." -} - variable "state_disk_type" { type = string default = "pd-ssd" diff --git a/cli/internal/terraform/terraform_test.go b/cli/internal/terraform/terraform_test.go index b8d483fc0..381573688 100644 --- a/cli/internal/terraform/terraform_test.go +++ b/cli/internal/terraform/terraform_test.go @@ -22,9 +22,9 @@ import ( "go.uber.org/multierr" ) -func TestCreateInstances(t *testing.T) { - someErr := errors.New("error") - getState := func() *tfjson.State { +func TestCreateCluster(t *testing.T) { + someErr := errors.New("failed") + newTestState := func() *tfjson.State { workingState := tfjson.State{ Values: &tfjson.StateValues{ Outputs: map[string]*tfjson.StateOutput{ @@ -34,52 +34,59 @@ func TestCreateInstances(t *testing.T) { }, }, } - return &workingState } + qemuVars := &QEMUVariables{ + CommonVariables: CommonVariables{ + Name: "name", + CountControlPlanes: 1, + CountWorkers: 2, + StateDiskSizeGB: 11, + }, + CPUCount: 1, + MemorySizeMiB: 1024, + IPRangeStart: 100, + ImagePath: "path", + ImageFormat: "format", + MetadataAPIImage: "api", + } testCases := map[string]struct { provider cloudprovider.Provider - input CreateClusterInput + vars Variables tf *stubTerraform fs afero.Fs wantErr bool }{ "works": { provider: cloudprovider.QEMU, - tf: &stubTerraform{ - showState: getState(), - }, - fs: afero.NewMemMapFs(), + vars: qemuVars, + tf: &stubTerraform{showState: newTestState()}, + fs: afero.NewMemMapFs(), }, "init fails": { provider: cloudprovider.QEMU, - tf: &stubTerraform{ - initErr: someErr, - showState: getState(), - }, - fs: afero.NewMemMapFs(), - wantErr: true, + tf: &stubTerraform{initErr: someErr}, + fs: afero.NewMemMapFs(), + wantErr: true, }, "apply fails": { provider: cloudprovider.QEMU, - tf: &stubTerraform{ - applyErr: someErr, - showState: getState(), - }, - fs: afero.NewMemMapFs(), - wantErr: true, + vars: qemuVars, + tf: &stubTerraform{applyErr: someErr}, + fs: afero.NewMemMapFs(), + wantErr: true, }, "show fails": { provider: cloudprovider.QEMU, - tf: &stubTerraform{ - showErr: someErr, - }, - fs: afero.NewMemMapFs(), - wantErr: true, + vars: qemuVars, + tf: &stubTerraform{showErr: someErr}, + fs: afero.NewMemMapFs(), + wantErr: true, }, "no ip": { provider: cloudprovider.QEMU, + vars: qemuVars, tf: &stubTerraform{ showState: &tfjson.State{ Values: &tfjson.StateValues{ @@ -90,13 +97,24 @@ func TestCreateInstances(t *testing.T) { fs: afero.NewMemMapFs(), wantErr: true, }, + "ip has wrong type": { + provider: cloudprovider.QEMU, + vars: qemuVars, + tf: &stubTerraform{ + showState: &tfjson.State{ + Values: &tfjson.StateValues{ + Outputs: map[string]*tfjson.StateOutput{"ip": {Value: 42}}, + }, + }, + }, + fs: afero.NewMemMapFs(), + wantErr: true, + }, "prepare workspace fails": { provider: cloudprovider.QEMU, - tf: &stubTerraform{ - showState: getState(), - }, - fs: afero.NewReadOnlyFs(afero.NewMemMapFs()), - wantErr: true, + tf: &stubTerraform{showState: newTestState()}, + fs: afero.NewReadOnlyFs(afero.NewMemMapFs()), + wantErr: true, }, } @@ -110,12 +128,12 @@ func TestCreateInstances(t *testing.T) { file: file.NewHandler(tc.fs), } - err := c.CreateCluster(context.Background(), "test", tc.input) + err := c.CreateCluster(context.Background(), "test", tc.vars) + if tc.wantErr { assert.Error(err) return } - assert.NoError(err) }) } diff --git a/cli/internal/terraform/variables.go b/cli/internal/terraform/variables.go new file mode 100644 index 000000000..b910cf90c --- /dev/null +++ b/cli/internal/terraform/variables.go @@ -0,0 +1,117 @@ +/* +Copyright (c) Edgeless Systems GmbH + +SPDX-License-Identifier: AGPL-3.0-only +*/ + +package terraform + +import ( + "fmt" + "strings" +) + +// Variables is a struct that holds all variables that are passed to Terraform. +type Variables interface { + fmt.Stringer +} + +// CommonVariables is user configuration for creating a cluster with Terraform. +type CommonVariables struct { + // Name of the cluster. + Name string + // CountControlPlanes is the number of control-plane nodes to create. + CountControlPlanes int + // CountWorkers is the number of worker nodes to create. + CountWorkers int + // StateDiskSizeGB is the size of the state disk to allocate to each node, in GB. + StateDiskSizeGB int +} + +// String returns a string representation of the variables, formatted as Terraform variables. +func (v *CommonVariables) String() string { + b := &strings.Builder{} + writeLinef(b, "name = %q", v.Name) + writeLinef(b, "control_plane_count = %d", v.CountControlPlanes) + writeLinef(b, "worker_count = %d", v.CountWorkers) + writeLinef(b, "state_disk_size = %d", v.StateDiskSizeGB) + + return b.String() +} + +// GCPVariables is user configuration for creating a cluster with Terraform on GCP. +type GCPVariables struct { + // CommonVariables contains common variables. + CommonVariables + + // Project is the ID of the GCP project to use. + Project string + // Region is the GCP region to use. + Region string + // Zone is the GCP zone to use. + Zone string + // CredentialsFile is the path to the GCP credentials file. + CredentialsFile string + // InstanceType is the GCP instance type to use. + InstanceType string + // StateDiskType is the GCP disk type to use for the state disk. + StateDiskType string + // ImageID is the ID of the GCP image to use. + ImageID string + // Debug is true if debug mode is enabled. + Debug bool +} + +// String returns a string representation of the variables, formatted as Terraform variables. +func (v *GCPVariables) String() string { + b := &strings.Builder{} + b.WriteString(v.CommonVariables.String()) + writeLinef(b, "project = %q", v.Project) + writeLinef(b, "region = %q", v.Region) + writeLinef(b, "zone = %q", v.Zone) + writeLinef(b, "credentials_file = %q", v.CredentialsFile) + writeLinef(b, "instance_type = %q", v.InstanceType) + writeLinef(b, "state_disk_type = %q", v.StateDiskType) + writeLinef(b, "image_id = %q", v.ImageID) + writeLinef(b, "debug = %t", v.Debug) + + return b.String() +} + +// QEMUVariables is user configuration for creating a QEMU cluster with Terraform. +type QEMUVariables struct { + // CommonVariables contains common variables. + CommonVariables + + // CPUCount is the number of CPUs to allocate to each node. + CPUCount int + // MemorySizeMiB is the amount of memory to allocate to each node, in MiB. + MemorySizeMiB int + // IPRangeStart is the first IP address in the IP range to allocate to the cluster. + IPRangeStart int + // ImagePath is the path to the image to use for the nodes. + ImagePath string + // ImageFormat is the format of the image from ImagePath. + ImageFormat string + // MetadataAPIImage is the container image to use for the metadata API. + MetadataAPIImage string +} + +// String returns a string representation of the variables, formatted as Terraform variables. +func (v *QEMUVariables) String() string { + b := &strings.Builder{} + b.WriteString(v.CommonVariables.String()) + writeLinef(b, "constellation_coreos_image = %q", v.ImagePath) + writeLinef(b, "image_format = %q", v.ImageFormat) + writeLinef(b, "vcpus = %d", v.CPUCount) + writeLinef(b, "memory = %d", v.MemorySizeMiB) + writeLinef(b, "ip_range_start = %d", v.IPRangeStart) + writeLinef(b, "metadata_api_image = %q", v.MetadataAPIImage) + + return b.String() +} + +func writeLinef(builder *strings.Builder, format string, a ...interface{}) { + builder.WriteString(fmt.Sprintf(format, a...)) + builder.WriteByte('\n') +} diff --git a/go.mod b/go.mod index f0d144407..870c8b994 100644 --- a/go.mod +++ b/go.mod @@ -34,10 +34,8 @@ replace github.com/google/go-attestation => github.com/malt3/go-attestation v0.0 require ( cloud.google.com/go/compute v1.7.0 - cloud.google.com/go/iam v0.3.0 cloud.google.com/go/kms v1.4.0 cloud.google.com/go/logging v1.4.2 - cloud.google.com/go/resourcemanager v1.2.0 cloud.google.com/go/storage v1.22.1 github.com/Azure/azure-sdk-for-go v66.0.0+incompatible github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 @@ -106,13 +104,9 @@ require ( k8s.io/utils v0.0.0-20220812165043-ad590609e2e5 ) -require ( - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/zclconf/go-cty v1.11.0 // indirect -) - require ( cloud.google.com/go v0.102.0 // indirect + cloud.google.com/go/iam v0.3.0 // indirect code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 // indirect @@ -198,6 +192,7 @@ require ( github.com/gosuri/uitable v0.0.4 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/icholy/replace v0.5.0 github.com/imdario/mergo v0.3.12 // indirect @@ -257,6 +252,7 @@ require ( github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.1.0 // indirect + github.com/zclconf/go-cty v1.11.0 // indirect go.opencensus.io v0.23.0 // indirect go.starlark.net v0.0.0-20220223235035-243c74974e97 // indirect go.uber.org/atomic v1.9.0 // indirect diff --git a/go.sum b/go.sum index 4dc887dde..cf5fbcdb1 100644 --- a/go.sum +++ b/go.sum @@ -71,8 +71,6 @@ cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w= -cloud.google.com/go/resourcemanager v1.2.0 h1:Oyt8+J80B51HgIPNk3p1ezTamu1wVj2bj7rBwL5Qd6k= -cloud.google.com/go/resourcemanager v1.2.0/go.mod h1:hFYbG0p7E8vVfQO3yfeaqEQVFO6n9gg9W2czYIdSEy4= cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= cloud.google.com/go/spanner v1.17.0/go.mod h1:+17t2ixFwRG4lWRwE+5kipDR9Ef07Jkmc8z0IbMDKUs= cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA= diff --git a/hack/go.mod b/hack/go.mod index 7ce58ac88..729ce546d 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -56,22 +56,10 @@ require ( libvirt.org/go/libvirtxml v1.8007.0 ) -require ( - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/hc-install v0.4.0 // indirect - github.com/hashicorp/terraform-exec v0.17.3 // indirect - github.com/hashicorp/terraform-json v0.14.0 // indirect - github.com/zclconf/go-cty v1.11.0 // indirect -) - require ( cloud.google.com/go v0.102.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect cloud.google.com/go/kms v1.4.0 // indirect - cloud.google.com/go/resourcemanager v1.2.0 // indirect cloud.google.com/go/storage v1.22.1 // indirect code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect @@ -155,6 +143,13 @@ require ( github.com/googleapis/gax-go/v2 v2.4.0 // indirect github.com/googleapis/go-type-adapters v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hc-install v0.4.0 // indirect + github.com/hashicorp/terraform-exec v0.17.3 // indirect + github.com/hashicorp/terraform-json v0.14.0 // indirect github.com/imdario/mergo v0.3.12 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -184,6 +179,7 @@ require ( github.com/theupdateframework/go-tuf v0.3.2 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect + github.com/zclconf/go-cty v1.11.0 // indirect go.opencensus.io v0.23.0 // indirect go.opentelemetry.io/otel/trace v1.3.0 // indirect go.uber.org/atomic v1.9.0 // indirect diff --git a/hack/go.sum b/hack/go.sum index 79b45241b..e215cdaca 100644 --- a/hack/go.sum +++ b/hack/go.sum @@ -65,8 +65,6 @@ cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w= -cloud.google.com/go/resourcemanager v1.2.0 h1:Oyt8+J80B51HgIPNk3p1ezTamu1wVj2bj7rBwL5Qd6k= -cloud.google.com/go/resourcemanager v1.2.0/go.mod h1:hFYbG0p7E8vVfQO3yfeaqEQVFO6n9gg9W2czYIdSEy4= cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk= cloud.google.com/go/spanner v1.17.0/go.mod h1:+17t2ixFwRG4lWRwE+5kipDR9Ef07Jkmc8z0IbMDKUs= cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA=