mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-06-04 12:49:54 -04:00
Use Terraform for create on GCP
This commit is contained in:
parent
f990c4d692
commit
d973740b03
25 changed files with 341 additions and 607 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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) {
|
||||
|
|
210
cli/internal/terraform/terraform/gcp/main.tf
Normal file
210
cli/internal/terraform/terraform/gcp/main.tf
Normal file
|
@ -0,0 +1,210 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = "4.34.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "3.4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "google" {
|
||||
credentials = file(var.credentials_file)
|
||||
|
||||
project = var.project
|
||||
region = var.region
|
||||
zone = var.zone
|
||||
}
|
||||
|
||||
locals {
|
||||
uid = random_id.uid.hex
|
||||
name = "${var.name}-${local.uid}"
|
||||
tag = "constellation-${local.uid}"
|
||||
ports_node_range = "30000-32767"
|
||||
ports_kubernetes = "6443"
|
||||
ports_bootstrapper = "9000"
|
||||
ports_konnectivity = "8132"
|
||||
ports_verify = "30081"
|
||||
ports_recovery = "9999"
|
||||
ports_debugd = "4000"
|
||||
cidr_vpc_subnet_nodes = "192.168.178.0/24"
|
||||
cidr_vpc_subnet_pods = "10.10.0.0/16"
|
||||
kube_env = "AUTOSCALER_ENV_VARS: kube_reserved=cpu=1060m,memory=1019Mi,ephemeral-storage=41Gi;node_labels=;os=linux;os_distribution=cos;evictionHard="
|
||||
}
|
||||
|
||||
resource "random_id" "uid" {
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
resource "google_compute_network" "vpc_network" {
|
||||
name = local.name
|
||||
description = "Constellation VPC network"
|
||||
auto_create_subnetworks = false
|
||||
}
|
||||
|
||||
resource "google_compute_subnetwork" "vpc_subnetwork" {
|
||||
name = local.name
|
||||
description = "Constellation VPC subnetwork"
|
||||
network = google_compute_network.vpc_network.id
|
||||
ip_cidr_range = local.cidr_vpc_subnet_nodes
|
||||
secondary_ip_range = [
|
||||
{
|
||||
range_name = local.name,
|
||||
ip_cidr_range = local.cidr_vpc_subnet_pods,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
resource "google_compute_firewall" "firewall_external" {
|
||||
name = local.name
|
||||
description = "Constellation VPC firewall"
|
||||
network = google_compute_network.vpc_network.id
|
||||
source_ranges = ["0.0.0.0/0"]
|
||||
direction = "INGRESS"
|
||||
|
||||
allow {
|
||||
protocol = "tcp"
|
||||
ports = flatten([
|
||||
local.ports_node_range,
|
||||
local.ports_bootstrapper,
|
||||
local.ports_kubernetes,
|
||||
local.ports_konnectivity,
|
||||
local.ports_recovery,
|
||||
var.debug ? [local.ports_debugd] : [],
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
resource "google_compute_firewall" "firewall_internal_nodes" {
|
||||
name = "${local.name}-nodes"
|
||||
description = "Constellation VPC firewall"
|
||||
network = google_compute_network.vpc_network.id
|
||||
source_ranges = [local.cidr_vpc_subnet_nodes]
|
||||
direction = "INGRESS"
|
||||
|
||||
allow { protocol = "tcp" }
|
||||
allow { protocol = "udp" }
|
||||
allow { protocol = "icmp" }
|
||||
}
|
||||
|
||||
resource "google_compute_firewall" "firewall_internal_pods" {
|
||||
name = "${local.name}-pods"
|
||||
description = "Constellation VPC firewall"
|
||||
network = google_compute_network.vpc_network.id
|
||||
source_ranges = [local.cidr_vpc_subnet_pods]
|
||||
direction = "INGRESS"
|
||||
|
||||
allow { protocol = "tcp" }
|
||||
allow { protocol = "udp" }
|
||||
allow { protocol = "icmp" }
|
||||
}
|
||||
|
||||
module "instance_group_control_plane" {
|
||||
source = "./modules/instance_group"
|
||||
name = local.name
|
||||
role = "ControlPlane"
|
||||
uid = local.uid
|
||||
instance_type = var.instance_type
|
||||
instance_count = var.control_plane_count
|
||||
image_id = var.image_id
|
||||
disk_size = var.state_disk_size
|
||||
disk_type = var.state_disk_type
|
||||
network = google_compute_network.vpc_network.id
|
||||
subnetwork = google_compute_subnetwork.vpc_subnetwork.id
|
||||
kube_env = local.kube_env
|
||||
named_ports = flatten([
|
||||
{ name = "kubernetes", port = local.ports_kubernetes },
|
||||
{ name = "bootstrapper", port = local.ports_bootstrapper },
|
||||
{ name = "verify", port = local.ports_verify },
|
||||
{ name = "konnectivity", port = local.ports_konnectivity },
|
||||
{ name = "recovery", port = local.ports_recovery },
|
||||
var.debug ? [{ name = "debugd", port = local.ports_debugd }] : [],
|
||||
])
|
||||
}
|
||||
|
||||
module "instance_group_worker" {
|
||||
source = "./modules/instance_group"
|
||||
name = local.name
|
||||
role = "Worker"
|
||||
uid = local.uid
|
||||
instance_type = var.instance_type
|
||||
instance_count = var.worker_count
|
||||
image_id = var.image_id
|
||||
disk_size = var.state_disk_size
|
||||
disk_type = var.state_disk_type
|
||||
network = google_compute_network.vpc_network.id
|
||||
subnetwork = google_compute_subnetwork.vpc_subnetwork.id
|
||||
kube_env = local.kube_env
|
||||
}
|
||||
|
||||
resource "google_compute_global_address" "loadbalancer_ip" {
|
||||
name = local.name
|
||||
}
|
||||
|
||||
module "loadbalancer_kube" {
|
||||
source = "./modules/loadbalancer"
|
||||
name = local.name
|
||||
health_check = "HTTPS"
|
||||
backend_port_name = "kubernetes"
|
||||
backend_instance_group = module.instance_group_control_plane.instance_group
|
||||
ip_address = google_compute_global_address.loadbalancer_ip.self_link
|
||||
port = local.ports_kubernetes
|
||||
frontend_labels = {
|
||||
constellation-uid = local.uid
|
||||
}
|
||||
}
|
||||
|
||||
module "loadbalancer_boot" {
|
||||
source = "./modules/loadbalancer"
|
||||
name = local.name
|
||||
health_check = "TCP"
|
||||
backend_port_name = "bootstrapper"
|
||||
backend_instance_group = module.instance_group_control_plane.instance_group
|
||||
ip_address = google_compute_global_address.loadbalancer_ip.self_link
|
||||
port = local.ports_bootstrapper
|
||||
}
|
||||
|
||||
module "loadbalancer_verify" {
|
||||
source = "./modules/loadbalancer"
|
||||
name = local.name
|
||||
health_check = "TCP"
|
||||
backend_port_name = "verify"
|
||||
backend_instance_group = module.instance_group_control_plane.instance_group
|
||||
ip_address = google_compute_global_address.loadbalancer_ip.self_link
|
||||
port = local.ports_verify
|
||||
}
|
||||
|
||||
module "loadbalancer_konnectivity" {
|
||||
source = "./modules/loadbalancer"
|
||||
name = local.name
|
||||
health_check = "TCP"
|
||||
backend_port_name = "konnectivity"
|
||||
backend_instance_group = module.instance_group_control_plane.instance_group
|
||||
ip_address = google_compute_global_address.loadbalancer_ip.self_link
|
||||
port = local.ports_konnectivity
|
||||
}
|
||||
|
||||
module "loadbalancer_recovery" {
|
||||
source = "./modules/loadbalancer"
|
||||
name = local.name
|
||||
health_check = "TCP"
|
||||
backend_port_name = "recovery"
|
||||
backend_instance_group = module.instance_group_control_plane.instance_group
|
||||
ip_address = google_compute_global_address.loadbalancer_ip.self_link
|
||||
port = local.ports_recovery
|
||||
}
|
||||
|
||||
module "loadbalancer_debugd" {
|
||||
count = var.debug ? 1 : 0 // only deploy debugd in debug mode
|
||||
source = "./modules/loadbalancer"
|
||||
name = local.name
|
||||
health_check = "TCP"
|
||||
backend_port_name = "debugd"
|
||||
backend_instance_group = module.instance_group_control_plane.instance_group
|
||||
ip_address = google_compute_global_address.loadbalancer_ip.self_link
|
||||
port = local.ports_debugd
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = "4.34.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
role_dashed = var.role == "ControlPlane" ? "control-plane" : "worker"
|
||||
name = "${var.name}-${local.role_dashed}"
|
||||
}
|
||||
|
||||
resource "google_compute_instance_template" "template" {
|
||||
name = local.name
|
||||
machine_type = var.instance_type
|
||||
tags = ["constellation-${var.uid}"]
|
||||
|
||||
confidential_instance_config {
|
||||
enable_confidential_compute = true
|
||||
}
|
||||
|
||||
disk {
|
||||
disk_size_gb = 10
|
||||
source_image = var.image_id
|
||||
auto_delete = true
|
||||
boot = true
|
||||
mode = "READ_WRITE"
|
||||
}
|
||||
|
||||
disk {
|
||||
disk_size_gb = var.disk_size
|
||||
disk_type = var.disk_type
|
||||
auto_delete = true
|
||||
device_name = "state-disk" // This name is used by disk mapper to find the disk
|
||||
boot = false
|
||||
mode = "READ_WRITE"
|
||||
type = "PERSISTENT"
|
||||
}
|
||||
|
||||
metadata = {
|
||||
kube-env = var.kube_env
|
||||
constellation-uid = var.uid
|
||||
constellation-role = var.role
|
||||
}
|
||||
|
||||
network_interface {
|
||||
network = var.network
|
||||
subnetwork = var.subnetwork
|
||||
access_config {}
|
||||
alias_ip_range {
|
||||
ip_cidr_range = "/24"
|
||||
subnetwork_range_name = var.name
|
||||
}
|
||||
}
|
||||
|
||||
scheduling {
|
||||
on_host_maintenance = "TERMINATE"
|
||||
}
|
||||
|
||||
service_account {
|
||||
scopes = [
|
||||
"https://www.googleapis.com/auth/compute",
|
||||
"https://www.googleapis.com/auth/servicecontrol",
|
||||
"https://www.googleapis.com/auth/service.management.readonly",
|
||||
"https://www.googleapis.com/auth/devstorage.read_only",
|
||||
"https://www.googleapis.com/auth/logging.write",
|
||||
"https://www.googleapis.com/auth/monitoring.write",
|
||||
"https://www.googleapis.com/auth/trace.append",
|
||||
]
|
||||
}
|
||||
|
||||
shielded_instance_config {
|
||||
enable_secure_boot = true
|
||||
enable_vtpm = true
|
||||
enable_integrity_monitoring = true
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_instance_group_manager" "instance_group_manager" {
|
||||
name = local.name
|
||||
base_instance_name = local.name
|
||||
target_size = var.instance_count
|
||||
|
||||
version {
|
||||
instance_template = google_compute_instance_template.template.id
|
||||
}
|
||||
|
||||
dynamic "named_port" {
|
||||
for_each = toset(var.named_ports)
|
||||
content {
|
||||
name = named_port.value.name
|
||||
port = named_port.value.port
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
output "instance_group" {
|
||||
value = google_compute_instance_group_manager.instance_group_manager.instance_group
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
variable "name" {
|
||||
type = string
|
||||
description = "Base name of the instance group."
|
||||
}
|
||||
|
||||
variable "role" {
|
||||
type = string
|
||||
description = "The role of the instance group. Has to be 'ControlPlane' or 'Worker'."
|
||||
}
|
||||
|
||||
variable "uid" {
|
||||
type = string
|
||||
description = "UID of the cluster. This is used for tags."
|
||||
}
|
||||
|
||||
variable "instance_type" {
|
||||
type = string
|
||||
description = "Instance type for the nodes."
|
||||
}
|
||||
|
||||
variable "instance_count" {
|
||||
type = number
|
||||
description = "Number of instances in the instance group."
|
||||
}
|
||||
|
||||
variable "image_id" {
|
||||
type = string
|
||||
description = "Image ID for the nodes."
|
||||
}
|
||||
|
||||
variable "disk_size" {
|
||||
type = number
|
||||
description = "Disk size for the nodes, in GB."
|
||||
}
|
||||
|
||||
variable "disk_type" {
|
||||
type = string
|
||||
description = "Disk type for the nodes. Has to be 'pd-standard' or 'pd-ssd'."
|
||||
}
|
||||
|
||||
variable "network" {
|
||||
type = string
|
||||
description = "Name of the network to use."
|
||||
}
|
||||
|
||||
variable "subnetwork" {
|
||||
type = string
|
||||
description = "Name of the subnetwork to use."
|
||||
}
|
||||
|
||||
variable "kube_env" {
|
||||
type = string
|
||||
description = "Kubernetes env."
|
||||
}
|
||||
|
||||
variable "named_ports" {
|
||||
type = list(object({ name = string, port = number }))
|
||||
default = []
|
||||
description = "Named ports for the instance group."
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = "4.34.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
name = "${var.name}-${var.backend_port_name}"
|
||||
}
|
||||
|
||||
resource "google_compute_health_check" "health" {
|
||||
name = local.name
|
||||
check_interval_sec = 1
|
||||
timeout_sec = 1
|
||||
|
||||
dynamic "tcp_health_check" {
|
||||
for_each = var.health_check == "TCP" ? [1] : []
|
||||
content {
|
||||
port = var.port
|
||||
}
|
||||
}
|
||||
|
||||
dynamic "https_health_check" {
|
||||
for_each = var.health_check == "HTTPS" ? [1] : []
|
||||
content {
|
||||
host = ""
|
||||
port = var.port
|
||||
request_path = "/readyz"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_backend_service" "backend" {
|
||||
name = local.name
|
||||
protocol = "TCP"
|
||||
load_balancing_scheme = "EXTERNAL"
|
||||
health_checks = [google_compute_health_check.health.self_link]
|
||||
port_name = var.backend_port_name
|
||||
|
||||
backend {
|
||||
group = var.backend_instance_group
|
||||
balancing_mode = "UTILIZATION"
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_target_tcp_proxy" "proxy" {
|
||||
name = local.name
|
||||
backend_service = google_compute_backend_service.backend.self_link
|
||||
}
|
||||
|
||||
resource "google_compute_global_forwarding_rule" "forwarding" {
|
||||
name = local.name
|
||||
ip_address = var.ip_address
|
||||
ip_protocol = "TCP"
|
||||
load_balancing_scheme = "EXTERNAL"
|
||||
port_range = var.port
|
||||
target = google_compute_target_tcp_proxy.proxy.self_link
|
||||
labels = var.frontend_labels
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
variable "name" {
|
||||
type = string
|
||||
description = "Base name of the load balancer."
|
||||
}
|
||||
|
||||
variable "health_check" {
|
||||
type = string
|
||||
description = "The type of the health check. 'HTTPS' or 'TCP'."
|
||||
}
|
||||
|
||||
variable "backend_port_name" {
|
||||
type = string
|
||||
description = "Name of backend port. The same name should appear in the instance groups referenced by this service."
|
||||
}
|
||||
|
||||
variable "backend_instance_group" {
|
||||
type = string
|
||||
description = "The URL of the instance group resource from which the load balancer will direct traffic."
|
||||
}
|
||||
|
||||
variable "ip_address" {
|
||||
type = string
|
||||
description = "The IP address that this forwarding rule serves. An address can be specified either by a literal IP address or a reference to an existing Address resource."
|
||||
}
|
||||
|
||||
variable "port" {
|
||||
type = number
|
||||
description = "The port on which to listen for incoming traffic."
|
||||
}
|
||||
|
||||
variable "frontend_labels" {
|
||||
type = map(string)
|
||||
default = {}
|
||||
description = "Labels to apply to the forwarding rule."
|
||||
}
|
3
cli/internal/terraform/terraform/gcp/outputs.tf
Normal file
3
cli/internal/terraform/terraform/gcp/outputs.tf
Normal file
|
@ -0,0 +1,3 @@
|
|||
output "ip" {
|
||||
value = google_compute_global_address.loadbalancer_ip.address
|
||||
}
|
63
cli/internal/terraform/terraform/gcp/variables.tf
Normal file
63
cli/internal/terraform/terraform/gcp/variables.tf
Normal file
|
@ -0,0 +1,63 @@
|
|||
variable "name" {
|
||||
type = string
|
||||
default = "constell"
|
||||
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."
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
type = string
|
||||
description = "The GCP region to deploy the cluster in."
|
||||
}
|
||||
|
||||
variable "zone" {
|
||||
type = string
|
||||
description = "The GCP zone to deploy the cluster in."
|
||||
}
|
||||
|
||||
variable "credentials_file" {
|
||||
type = string
|
||||
description = "The path to the GCP credentials file."
|
||||
}
|
||||
|
||||
variable "instance_type" {
|
||||
type = string
|
||||
description = "The GCP instance type to deploy."
|
||||
}
|
||||
|
||||
variable "state_disk_type" {
|
||||
type = string
|
||||
default = "pd-ssd"
|
||||
description = "The type of the state disk."
|
||||
}
|
||||
|
||||
variable "image_id" {
|
||||
type = string
|
||||
description = "The GCP image to use for the cluster nodes."
|
||||
}
|
||||
|
||||
variable "debug" {
|
||||
type = bool
|
||||
default = false
|
||||
description = "Enable debug mode. This opens up a debugd port that can be used to deploy a custom bootstrapper."
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
117
cli/internal/terraform/variables.go
Normal file
117
cli/internal/terraform/variables.go
Normal 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')
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue