Use Terraform for create on GCP

This commit is contained in:
katexochen 2022-09-27 09:22:29 +02:00 committed by Paul Meyer
parent f990c4d692
commit d973740b03
25 changed files with 341 additions and 607 deletions

View File

@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
<!-- For changes in existing functionality. --> <!-- For changes in existing functionality. -->
- Autoscaling is now directly managed inside Kubernetes, by the Constellation node operator. - 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 ### Deprecated
<!-- For soon-to-be removed features. --> <!-- For soon-to-be removed features. -->

View File

@ -10,23 +10,16 @@ import (
"context" "context"
azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client" 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/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/state" "github.com/edgelesssys/constellation/v2/internal/state"
) )
type gcpclient interface { type terraformClient interface {
GetState() state.ConstellationState GetState() state.ConstellationState
SetState(state.ConstellationState) CreateCluster(ctx context.Context, name string, input terraform.Variables) error
CreateVPCs(ctx context.Context) error DestroyCluster(ctx context.Context) error
CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error CleanUpWorkspace() error
CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error RemoveInstaller()
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
} }
type azureclient interface { type azureclient interface {
@ -39,11 +32,3 @@ type azureclient interface {
CreateInstances(ctx context.Context, input azurecl.CreateInstancesInput) error CreateInstances(ctx context.Context, input azurecl.CreateInstancesInput) error
TerminateResourceGroupResources(ctx context.Context) 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()
}

View File

@ -8,12 +8,11 @@ package cloudcmd
import ( import (
"context" "context"
"errors"
"strconv" "strconv"
"testing" "testing"
azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client" 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/azureshared"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
@ -195,196 +194,34 @@ func (c *stubAzureClient) TerminateServicePrincipal(ctx context.Context) error {
return c.terminateServicePrincipalErr return c.terminateServicePrincipalErr
} }
type fakeGcpClient struct { type stubTerraformClient struct {
workers cloudtypes.Instances state state.ConstellationState
controlPlanes cloudtypes.Instances cleanUpWorkspaceCalled bool
removeInstallerCalled bool
workerInstanceGroup string destroyClusterCalled bool
controlPlaneInstanceGroup string createClusterErr error
controlPlaneTemplate string destroyClusterErr error
workerTemplate string cleanUpWorkspaceErr error
network string
subnetwork string
firewalls []string
project string
uid string
name string
zone string
loadbalancers []string
} }
func (c *fakeGcpClient) GetState() state.ConstellationState { func (c *stubTerraformClient) GetState() state.ConstellationState {
return state.ConstellationState{ return c.state
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 *fakeGcpClient) SetState(stat state.ConstellationState) { func (c *stubTerraformClient) CreateCluster(ctx context.Context, name string, input terraform.Variables) error {
c.workers = stat.GCPWorkerInstances return c.createClusterErr
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 *fakeGcpClient) CreateVPCs(ctx context.Context) error { func (c *stubTerraformClient) DestroyCluster(ctx context.Context) error {
c.network = "network" c.destroyClusterCalled = true
c.subnetwork = "subnetwork" return c.destroyClusterErr
return nil
} }
func (c *fakeGcpClient) CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error { func (c *stubTerraformClient) CleanUpWorkspace() error {
if c.network == "" { c.cleanUpWorkspaceCalled = true
return errors.New("client has not network") return c.cleanUpWorkspaceErr
}
for _, rule := range input.Ingress {
c.firewalls = append(c.firewalls, rule.Name)
}
return nil
} }
func (c *fakeGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error { func (c *stubTerraformClient) RemoveInstaller() {
c.controlPlaneInstanceGroup = "controlplane-group" c.removeInstallerCalled = true
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
} }

View File

@ -13,8 +13,6 @@ import (
"runtime" "runtime"
azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client" 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/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
@ -25,28 +23,21 @@ import (
// Creator creates cloud resources. // Creator creates cloud resources.
type Creator struct { type Creator struct {
out io.Writer out io.Writer
newGCPClient func(ctx context.Context, project, zone, region, name string) (gcpclient, error) newTerraformClient func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error)
newAzureClient func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) newAzureClient func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error)
newQEMUClient func(ctx context.Context) (qemuclient, error)
} }
// NewCreator creates a new creator. // NewCreator creates a new creator.
func NewCreator(out io.Writer) *Creator { func NewCreator(out io.Writer) *Creator {
return &Creator{ return &Creator{
out: out, out: out,
newGCPClient: func(ctx context.Context, project, zone, region, name string) (gcpclient, error) { newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) {
return gcpcl.NewInitialized(ctx, project, zone, region, name) return terraform.New(ctx, provider)
}, },
newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) { newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) {
return azurecl.NewInitialized(subscriptionID, tenantID, name, location, resourceGroup) 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 { switch provider {
case cloudprovider.GCP: case cloudprovider.GCP:
cl, err := c.newGCPClient( cl, err := c.newTerraformClient(ctx, provider)
ctx,
config.Provider.GCP.Project,
config.Provider.GCP.Zone,
config.Provider.GCP.Region,
name,
)
if err != nil { if err != nil {
return state.ConstellationState{}, err return state.ConstellationState{}, err
} }
defer cl.Close() defer cl.RemoveInstaller()
return c.createGCP(ctx, cl, config, insType, controlPlaneCount, workerCount, ingressRules) return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
case cloudprovider.Azure: case cloudprovider.Azure:
cl, err := c.newAzureClient( cl, err := c.newAzureClient(
config.Provider.Azure.SubscriptionID, 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) return c.createAzure(ctx, cl, config, insType, controlPlaneCount, workerCount, ingressRules)
case cloudprovider.QEMU: 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 { if err != nil {
return state.ConstellationState{}, err 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) { ) (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 { vars := &terraform.GCPVariables{
return state.ConstellationState{}, err CommonVariables: terraform.CommonVariables{
} Name: name,
CountControlPlanes: controlPlaneCount,
if err := cl.CreateFirewall(ctx, gcpcl.FirewallInput{ CountWorkers: workerCount,
Ingress: ingressRules, StateDiskSizeGB: config.StateDiskSizeGB,
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,
},
}, },
} Project: config.Provider.GCP.Project,
if err := cl.CreateFirewall(ctx, internalFirewallInput); err != nil { Region: config.Provider.GCP.Region,
return state.ConstellationState{}, err 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{ if err := cl.CreateCluster(ctx, name, vars); err != nil {
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 {
return state.ConstellationState{}, err return state.ConstellationState{}, err
} }
@ -211,25 +153,27 @@ func (c *Creator) createAzure(ctx context.Context, cl azureclient, config *confi
return cl.GetState(), nil 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) { ) (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{ vars := &terraform.QEMUVariables{
CountControlPlanes: controlPlaneCount, CommonVariables: terraform.CommonVariables{
CountWorkers: workerCount, Name: name,
QEMU: terraform.QEMUInput{ CountControlPlanes: controlPlaneCount,
ImagePath: config.Provider.QEMU.Image, CountWorkers: workerCount,
ImageFormat: config.Provider.QEMU.ImageFormat, StateDiskSizeGB: config.StateDiskSizeGB,
CPUCount: config.Provider.QEMU.VCPUs,
MemorySizeMiB: config.Provider.QEMU.Memory,
StateDiskSizeGB: config.StateDiskSizeGB,
IPRangeStart: config.Provider.QEMU.IPRangeStart,
MetadataAPIImage: config.Provider.QEMU.MetadataAPIImage,
}, },
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 return state.ConstellationState{}, err
} }

View File

@ -21,29 +21,8 @@ import (
func TestCreator(t *testing.T) { func TestCreator(t *testing.T) {
wantGCPState := state.ConstellationState{ wantGCPState := state.ConstellationState{
CloudProvider: cloudprovider.GCP.String(), CloudProvider: cloudprovider.GCP.String(),
GCPProject: "project", LoadBalancerIP: "192.0.2.1",
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",
},
} }
wantAzureState := state.ConstellationState{ wantAzureState := state.ConstellationState{
@ -66,8 +45,8 @@ func TestCreator(t *testing.T) {
someErr := errors.New("failed") someErr := errors.New("failed")
testCases := map[string]struct { testCases := map[string]struct {
gcpclient gcpclient tfClient terraformClient
newGCPClientErr error newTfClientErr error
azureclient azureclient azureclient azureclient
newAzureClientErr error newAzureClientErr error
provider cloudprovider.Provider provider cloudprovider.Provider
@ -77,40 +56,19 @@ func TestCreator(t *testing.T) {
wantRollback bool // Use only together with stubClients. wantRollback bool // Use only together with stubClients.
}{ }{
"gcp": { "gcp": {
gcpclient: &fakeGcpClient{project: "project"}, tfClient: &stubTerraformClient{state: wantGCPState},
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
config: config.Default(), config: config.Default(),
wantState: wantGCPState, wantState: wantGCPState,
}, },
"gcp newGCPClient error": { "gcp newGCPClient error": {
newGCPClientErr: someErr, newTfClientErr: someErr,
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
config: config.Default(), config: config.Default(),
wantErr: true, wantErr: true,
}, },
"gcp CreateVPCs error": { "gcp create cluster error": {
gcpclient: &stubGcpClient{createVPCsErr: someErr}, tfClient: &stubTerraformClient{createClusterErr: 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},
provider: cloudprovider.GCP, provider: cloudprovider.GCP,
config: config.Default(), config: config.Default(),
wantErr: true, wantErr: true,
@ -162,8 +120,8 @@ func TestCreator(t *testing.T) {
creator := &Creator{ creator := &Creator{
out: &bytes.Buffer{}, out: &bytes.Buffer{},
newGCPClient: func(ctx context.Context, project, zone, region, name string) (gcpclient, error) { newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) {
return tc.gcpclient, tc.newGCPClientErr return tc.tfClient, tc.newTfClientErr
}, },
newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) { newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) {
return tc.azureclient, tc.newAzureClientErr return tc.azureclient, tc.newAzureClientErr
@ -176,12 +134,12 @@ func TestCreator(t *testing.T) {
assert.Error(err) assert.Error(err)
if tc.wantRollback { if tc.wantRollback {
switch tc.provider { switch tc.provider {
case cloudprovider.QEMU:
fallthrough
case cloudprovider.GCP: case cloudprovider.GCP:
cl := tc.gcpclient.(*stubGcpClient) cl := tc.tfClient.(*stubTerraformClient)
assert.True(cl.terminateFirewallCalled) assert.True(cl.destroyClusterCalled)
assert.True(cl.terminateInstancesCalled) assert.True(cl.cleanUpWorkspaceCalled)
assert.True(cl.terminateVPCsCalled)
assert.True(cl.closeCalled)
case cloudprovider.Azure: case cloudprovider.Azure:
cl := tc.azureclient.(*stubAzureClient) cl := tc.azureclient.(*stubAzureClient)
assert.True(cl.terminateResourceGroupResourcesCalled) assert.True(cl.terminateResourceGroupResourcesCalled)

View File

@ -34,19 +34,6 @@ func rollbackOnError(ctx context.Context, w io.Writer, onErr *error, roll rollba
fmt.Fprintln(w, "Rollback succeeded.") 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 { type rollbackerAzure struct {
client azureclient client azureclient
} }
@ -55,11 +42,11 @@ func (r *rollbackerAzure) rollback(ctx context.Context) error {
return r.client.TerminateResourceGroupResources(ctx) return r.client.TerminateResourceGroupResources(ctx)
} }
type rollbackerQEMU struct { type rollbackerTerraform struct {
client qemuclient client terraformClient
} }
func (r *rollbackerQEMU) rollback(ctx context.Context) error { func (r *rollbackerTerraform) rollback(ctx context.Context) error {
var err error var err error
err = multierr.Append(err, r.client.DestroyCluster(ctx)) err = multierr.Append(err, r.client.DestroyCluster(ctx))
err = multierr.Append(err, r.client.CleanUpWorkspace()) err = multierr.Append(err, r.client.CleanUpWorkspace())

View File

@ -11,7 +11,6 @@ import (
"fmt" "fmt"
azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client" 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/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/state" "github.com/edgelesssys/constellation/v2/internal/state"
@ -19,23 +18,19 @@ import (
// Terminator deletes cloud provider resources. // Terminator deletes cloud provider resources.
type Terminator struct { type Terminator struct {
newGCPClient func(ctx context.Context) (gcpclient, error) newTerraformClient func(ctx context.Context) (terraformClient, error)
newAzureClient func(subscriptionID, tenantID string) (azureclient, error) newAzureClient func(subscriptionID, tenantID string) (azureclient, error)
newQEMUClient func(ctx context.Context) (qemuclient, error)
} }
// NewTerminator create a new cloud terminator. // NewTerminator create a new cloud terminator.
func NewTerminator() *Terminator { func NewTerminator() *Terminator {
return &Terminator{ return &Terminator{
newGCPClient: func(ctx context.Context) (gcpclient, error) { newTerraformClient: func(ctx context.Context) (terraformClient, error) {
return gcpcl.NewFromDefault(ctx) return terraform.New(ctx, cloudprovider.GCP)
}, },
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) { newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
return azurecl.NewFromDefault(subscriptionID, tenantID) 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 { func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationState) error {
provider := cloudprovider.FromString(state.CloudProvider) provider := cloudprovider.FromString(state.CloudProvider)
switch provider { 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: case cloudprovider.Azure:
cl, err := t.newAzureClient(state.AzureSubscription, state.AzureTenant) cl, err := t.newAzureClient(state.AzureSubscription, state.AzureTenant)
if err != nil { if err != nil {
return err return err
} }
return t.terminateAzure(ctx, cl, state) return t.terminateAzure(ctx, cl, state)
case cloudprovider.GCP:
fallthrough
case cloudprovider.QEMU: case cloudprovider.QEMU:
cl, err := t.newQEMUClient(ctx) cl, err := t.newTerraformClient(ctx)
if err != nil { if err != nil {
return err return err
} }
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return t.terminateQEMU(ctx, cl) return t.terminateTerraform(ctx, cl)
default: default:
return fmt.Errorf("unsupported provider: %s", provider) 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 { func (t *Terminator) terminateAzure(ctx context.Context, cl azureclient, state state.ConstellationState) error {
cl.SetState(state) cl.SetState(state)
return cl.TerminateResourceGroupResources(ctx) 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 { if err := cl.DestroyCluster(ctx); err != nil {
return err return err
} }

View File

@ -18,25 +18,6 @@ import (
) )
func TestTerminator(t *testing.T) { 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 { someAzureState := func() state.ConstellationState {
return state.ConstellationState{ return state.ConstellationState{
CloudProvider: cloudprovider.Azure.String(), CloudProvider: cloudprovider.Azure.String(),
@ -53,36 +34,45 @@ func TestTerminator(t *testing.T) {
someErr := errors.New("failed") someErr := errors.New("failed")
testCases := map[string]struct { testCases := map[string]struct {
gcpclient gcpclient tfClient terraformClient
newGCPClientErr error newTfClientErr error
azureclient azureclient azureclient azureclient
newAzureClientErr error newAzureClientErr error
state state.ConstellationState state state.ConstellationState
wantErr bool wantErr bool
}{ }{
"gcp": { "gcp": {
gcpclient: &stubGcpClient{}, tfClient: &stubTerraformClient{},
state: someGCPState(), state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()},
}, },
"gcp newGCPClient error": { "gcp newTfClientErr": {
newGCPClientErr: someErr, newTfClientErr: someErr,
state: someGCPState(), state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()},
wantErr: true, wantErr: true,
}, },
"gcp terminateInstances error": { "gcp destroy cluster error": {
gcpclient: &stubGcpClient{terminateInstancesErr: someErr}, tfClient: &stubTerraformClient{destroyClusterErr: someErr},
state: someGCPState(), state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()},
wantErr: true, wantErr: true,
}, },
"gcp terminateFirewall error": { "gcp clean up workspace error": {
gcpclient: &stubGcpClient{terminateFirewallErr: someErr}, tfClient: &stubTerraformClient{cleanUpWorkspaceErr: someErr},
state: someGCPState(), state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()},
wantErr: true, wantErr: true,
}, },
"gcp terminateVPCs error": { "qemu": {
gcpclient: &stubGcpClient{terminateVPCsErr: someErr}, tfClient: &stubTerraformClient{},
state: someGCPState(), state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()},
wantErr: true, },
"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": { "azure": {
azureclient: &stubAzureClient{}, azureclient: &stubAzureClient{},
@ -109,8 +99,8 @@ func TestTerminator(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
terminator := &Terminator{ terminator := &Terminator{
newGCPClient: func(ctx context.Context) (gcpclient, error) { newTerraformClient: func(ctx context.Context) (terraformClient, error) {
return tc.gcpclient, tc.newGCPClientErr return tc.tfClient, tc.newTfClientErr
}, },
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) { newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
return tc.azureclient, tc.newAzureClientErr return tc.azureclient, tc.newAzureClientErr
@ -125,11 +115,11 @@ func TestTerminator(t *testing.T) {
assert.NoError(err) assert.NoError(err)
switch cloudprovider.FromString(tc.state.CloudProvider) { switch cloudprovider.FromString(tc.state.CloudProvider) {
case cloudprovider.GCP: case cloudprovider.GCP:
cl := tc.gcpclient.(*stubGcpClient) fallthrough
assert.True(cl.terminateFirewallCalled) case cloudprovider.QEMU:
assert.True(cl.terminateInstancesCalled) cl := tc.tfClient.(*stubTerraformClient)
assert.True(cl.terminateVPCsCalled) assert.True(cl.destroyClusterCalled)
assert.True(cl.closeCalled) assert.True(cl.removeInstallerCalled)
case cloudprovider.Azure: case cloudprovider.Azure:
cl := tc.azureclient.(*stubAzureClient) cl := tc.azureclient.(*stubAzureClient)
assert.True(cl.terminateResourceGroupResourcesCalled) assert.True(cl.terminateResourceGroupResourcesCalled)

View File

@ -49,8 +49,7 @@ func TestInitArgumentValidation(t *testing.T) {
func TestInitialize(t *testing.T) { func TestInitialize(t *testing.T) {
testGcpState := &state.ConstellationState{ testGcpState := &state.ConstellationState{
CloudProvider: "GCP", CloudProvider: "GCP",
GCPWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}},
} }
gcpServiceAccKey := &gcpshared.ServiceAccountKey{ gcpServiceAccKey := &gcpshared.ServiceAccountKey{
Type: "service_account", Type: "service_account",

View File

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

View File

@ -9,7 +9,6 @@ package terraform
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/file" "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. // 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 { if err := prepareWorkspace(c.file, c.provider); err != nil {
return err return err
} }
@ -68,7 +67,7 @@ func (c *Client) CreateCluster(ctx context.Context, name string, input CreateClu
return err 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 return err
} }
@ -137,35 +136,6 @@ func (c *Client) GetState() state.ConstellationState {
return c.state 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, // GetExecutable returns a Terraform executable either from the local filesystem,
// or downloads the latest version fulfilling the version constraint. // or downloads the latest version fulfilling the version constraint.
func GetExecutable(ctx context.Context, workingDir string) (terraform *tfexec.Terraform, remove func(), err error) { func GetExecutable(ctx context.Context, workingDir string) (terraform *tfexec.Terraform, remove func(), err error) {

View File

@ -0,0 +1,3 @@
output "ip" {
value = google_compute_global_address.loadbalancer_ip.address
}

View File

@ -4,6 +4,22 @@ variable "name" {
description = "Base name of the cluster." 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" { variable "project" {
type = string type = string
description = "The GCP project to deploy the cluster in." description = "The GCP project to deploy the cluster in."
@ -24,27 +40,11 @@ variable "credentials_file" {
description = "The path to the GCP 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" { variable "instance_type" {
type = string type = string
description = "The GCP instance type to deploy." 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" { variable "state_disk_type" {
type = string type = string
default = "pd-ssd" default = "pd-ssd"

View File

@ -22,9 +22,9 @@ import (
"go.uber.org/multierr" "go.uber.org/multierr"
) )
func TestCreateInstances(t *testing.T) { func TestCreateCluster(t *testing.T) {
someErr := errors.New("error") someErr := errors.New("failed")
getState := func() *tfjson.State { newTestState := func() *tfjson.State {
workingState := tfjson.State{ workingState := tfjson.State{
Values: &tfjson.StateValues{ Values: &tfjson.StateValues{
Outputs: map[string]*tfjson.StateOutput{ Outputs: map[string]*tfjson.StateOutput{
@ -34,52 +34,59 @@ func TestCreateInstances(t *testing.T) {
}, },
}, },
} }
return &workingState 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 { testCases := map[string]struct {
provider cloudprovider.Provider provider cloudprovider.Provider
input CreateClusterInput vars Variables
tf *stubTerraform tf *stubTerraform
fs afero.Fs fs afero.Fs
wantErr bool wantErr bool
}{ }{
"works": { "works": {
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
tf: &stubTerraform{ vars: qemuVars,
showState: getState(), tf: &stubTerraform{showState: newTestState()},
}, fs: afero.NewMemMapFs(),
fs: afero.NewMemMapFs(),
}, },
"init fails": { "init fails": {
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
tf: &stubTerraform{ tf: &stubTerraform{initErr: someErr},
initErr: someErr, fs: afero.NewMemMapFs(),
showState: getState(), wantErr: true,
},
fs: afero.NewMemMapFs(),
wantErr: true,
}, },
"apply fails": { "apply fails": {
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
tf: &stubTerraform{ vars: qemuVars,
applyErr: someErr, tf: &stubTerraform{applyErr: someErr},
showState: getState(), fs: afero.NewMemMapFs(),
}, wantErr: true,
fs: afero.NewMemMapFs(),
wantErr: true,
}, },
"show fails": { "show fails": {
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
tf: &stubTerraform{ vars: qemuVars,
showErr: someErr, tf: &stubTerraform{showErr: someErr},
}, fs: afero.NewMemMapFs(),
fs: afero.NewMemMapFs(), wantErr: true,
wantErr: true,
}, },
"no ip": { "no ip": {
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
vars: qemuVars,
tf: &stubTerraform{ tf: &stubTerraform{
showState: &tfjson.State{ showState: &tfjson.State{
Values: &tfjson.StateValues{ Values: &tfjson.StateValues{
@ -90,13 +97,24 @@ func TestCreateInstances(t *testing.T) {
fs: afero.NewMemMapFs(), fs: afero.NewMemMapFs(),
wantErr: true, 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": { "prepare workspace fails": {
provider: cloudprovider.QEMU, provider: cloudprovider.QEMU,
tf: &stubTerraform{ tf: &stubTerraform{showState: newTestState()},
showState: getState(), fs: afero.NewReadOnlyFs(afero.NewMemMapFs()),
}, wantErr: true,
fs: afero.NewReadOnlyFs(afero.NewMemMapFs()),
wantErr: true,
}, },
} }
@ -110,12 +128,12 @@ func TestCreateInstances(t *testing.T) {
file: file.NewHandler(tc.fs), file: file.NewHandler(tc.fs),
} }
err := c.CreateCluster(context.Background(), "test", tc.input) err := c.CreateCluster(context.Background(), "test", tc.vars)
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
return return
} }
assert.NoError(err) assert.NoError(err)
}) })
} }

View File

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

10
go.mod
View File

@ -34,10 +34,8 @@ replace github.com/google/go-attestation => github.com/malt3/go-attestation v0.0
require ( require (
cloud.google.com/go/compute v1.7.0 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/kms v1.4.0
cloud.google.com/go/logging v1.4.2 cloud.google.com/go/logging v1.4.2
cloud.google.com/go/resourcemanager v1.2.0
cloud.google.com/go/storage v1.22.1 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 v66.0.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1 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 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 ( require (
cloud.google.com/go v0.102.0 // indirect 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 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/internal v1.0.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.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/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
github.com/hashicorp/errwrap v1.1.0 // 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/huandu/xstrings v1.3.2 // indirect
github.com/icholy/replace v0.5.0 github.com/icholy/replace v0.5.0
github.com/imdario/mergo v0.3.12 // indirect 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/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xlab/treeprint v1.1.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.opencensus.io v0.23.0 // indirect
go.starlark.net v0.0.0-20220223235035-243c74974e97 // indirect go.starlark.net v0.0.0-20220223235035-243c74974e97 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect

2
go.sum
View File

@ -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.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.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/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.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.17.0/go.mod h1:+17t2ixFwRG4lWRwE+5kipDR9Ef07Jkmc8z0IbMDKUs=
cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA= cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA=

View File

@ -56,22 +56,10 @@ require (
libvirt.org/go/libvirtxml v1.8007.0 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 ( require (
cloud.google.com/go v0.102.0 // indirect cloud.google.com/go v0.102.0 // indirect
cloud.google.com/go/iam v0.3.0 // indirect cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/kms v1.4.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 cloud.google.com/go/storage v1.22.1 // indirect
code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // 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/gax-go/v2 v2.4.0 // indirect
github.com/googleapis/go-type-adapters v1.0.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/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/imdario/mergo v0.3.12 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // 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/theupdateframework/go-tuf v0.3.2 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/xanzy/ssh-agent v0.3.0 // 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.opencensus.io v0.23.0 // indirect
go.opentelemetry.io/otel/trace v1.3.0 // indirect go.opentelemetry.io/otel/trace v1.3.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect

View File

@ -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.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.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/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.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.17.0/go.mod h1:+17t2ixFwRG4lWRwE+5kipDR9Ef07Jkmc8z0IbMDKUs=
cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA= cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA=