mirror of
https://github.com/edgelesssys/constellation.git
synced 2024-12-24 23:19:39 -05:00
Use Terraform for create on GCP
This commit is contained in:
parent
f990c4d692
commit
d973740b03
@ -29,6 +29,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
<!-- For changes in existing functionality. -->
|
||||
- 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
|
||||
<!-- For soon-to-be removed features. -->
|
||||
|
@ -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"
|
||||
@ -26,27 +24,20 @@ import (
|
||||
// Creator creates cloud resources.
|
||||
type Creator struct {
|
||||
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)
|
||||
newQEMUClient func(ctx context.Context) (qemuclient, 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,
|
||||
},
|
||||
},
|
||||
}
|
||||
if err := cl.CreateFirewall(ctx, internalFirewallInput); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
|
||||
createInput := gcpcl.CreateInstancesInput{
|
||||
EnableSerialConsole: config.IsDebugCluster(),
|
||||
vars := &terraform.GCPVariables{
|
||||
CommonVariables: terraform.CommonVariables{
|
||||
Name: name,
|
||||
CountControlPlanes: controlPlaneCount,
|
||||
CountWorkers: workerCount,
|
||||
ImageID: config.Provider.GCP.Image,
|
||||
InstanceType: insType,
|
||||
StateDiskSizeGB: config.StateDiskSizeGB,
|
||||
},
|
||||
Project: config.Provider.GCP.Project,
|
||||
Region: config.Provider.GCP.Region,
|
||||
Zone: config.Provider.GCP.Zone,
|
||||
CredentialsFile: config.Provider.GCP.ServiceAccountKeyPath,
|
||||
InstanceType: insType,
|
||||
StateDiskType: config.Provider.GCP.StateDiskType,
|
||||
KubeEnv: gcp.KubeEnv,
|
||||
}
|
||||
if err := cl.CreateInstances(ctx, createInput); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
ImageID: config.Provider.GCP.Image,
|
||||
Debug: config.IsDebugCluster(),
|
||||
}
|
||||
|
||||
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{
|
||||
vars := &terraform.QEMUVariables{
|
||||
CommonVariables: terraform.CommonVariables{
|
||||
Name: name,
|
||||
CountControlPlanes: controlPlaneCount,
|
||||
CountWorkers: workerCount,
|
||||
QEMU: terraform.QEMUInput{
|
||||
StateDiskSizeGB: config.StateDiskSizeGB,
|
||||
},
|
||||
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,
|
||||
},
|
||||
}
|
||||
|
||||
if err := cl.CreateCluster(ctx, name, input); err != nil {
|
||||
if err := cl.CreateCluster(ctx, name, vars); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
|
||||
|
@ -22,28 +22,7 @@ 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",
|
||||
},
|
||||
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,
|
||||
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)
|
||||
newTerraformClient func(ctx context.Context) (terraformClient, error)
|
||||
newAzureClient func(subscriptionID, tenantID string) (azureclient, error)
|
||||
newQEMUClient func(ctx context.Context) (qemuclient, 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,35 +34,44 @@ 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(),
|
||||
"gcp newTfClientErr": {
|
||||
newTfClientErr: someErr,
|
||||
state: state.ConstellationState{CloudProvider: cloudprovider.GCP.String()},
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp terminateInstances error": {
|
||||
gcpclient: &stubGcpClient{terminateInstancesErr: someErr},
|
||||
state: someGCPState(),
|
||||
"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(),
|
||||
"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(),
|
||||
"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": {
|
||||
@ -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)
|
||||
|
@ -50,7 +50,6 @@ func TestInitArgumentValidation(t *testing.T) {
|
||||
func TestInitialize(t *testing.T) {
|
||||
testGcpState := &state.ConstellationState{
|
||||
CloudProvider: "GCP",
|
||||
GCPWorkerInstances: cloudtypes.Instances{"id-0": {}, "id-1": {}},
|
||||
}
|
||||
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) {
|
||||
|
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
|
||||
}
|
@ -4,6 +4,22 @@ variable "name" {
|
||||
description = "Base name of the cluster."
|
||||
}
|
||||
|
||||
variable "control_plane_count" {
|
||||
type = number
|
||||
description = "The number of control plane nodes to deploy."
|
||||
}
|
||||
|
||||
variable "worker_count" {
|
||||
type = number
|
||||
description = "The number of worker nodes to deploy."
|
||||
}
|
||||
|
||||
variable "state_disk_size" {
|
||||
type = number
|
||||
default = 30
|
||||
description = "The size of the state disk in GB."
|
||||
}
|
||||
|
||||
variable "project" {
|
||||
type = string
|
||||
description = "The GCP project to deploy the cluster in."
|
||||
@ -24,27 +40,11 @@ variable "credentials_file" {
|
||||
description = "The path to the GCP credentials file."
|
||||
}
|
||||
|
||||
variable "control_plane_count" {
|
||||
type = number
|
||||
description = "The number of control plane nodes to deploy."
|
||||
}
|
||||
|
||||
variable "worker_count" {
|
||||
type = number
|
||||
description = "The number of worker nodes to deploy."
|
||||
}
|
||||
|
||||
variable "instance_type" {
|
||||
type = string
|
||||
description = "The GCP instance type to deploy."
|
||||
}
|
||||
|
||||
variable "state_disk_size" {
|
||||
type = number
|
||||
default = 30
|
||||
description = "The size of the state disk in GB."
|
||||
}
|
||||
|
||||
variable "state_disk_type" {
|
||||
type = string
|
||||
default = "pd-ssd"
|
@ -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(),
|
||||
},
|
||||
vars: qemuVars,
|
||||
tf: &stubTerraform{showState: newTestState()},
|
||||
fs: afero.NewMemMapFs(),
|
||||
},
|
||||
"init fails": {
|
||||
provider: cloudprovider.QEMU,
|
||||
tf: &stubTerraform{
|
||||
initErr: someErr,
|
||||
showState: getState(),
|
||||
},
|
||||
tf: &stubTerraform{initErr: someErr},
|
||||
fs: afero.NewMemMapFs(),
|
||||
wantErr: true,
|
||||
},
|
||||
"apply fails": {
|
||||
provider: cloudprovider.QEMU,
|
||||
tf: &stubTerraform{
|
||||
applyErr: someErr,
|
||||
showState: getState(),
|
||||
},
|
||||
vars: qemuVars,
|
||||
tf: &stubTerraform{applyErr: someErr},
|
||||
fs: afero.NewMemMapFs(),
|
||||
wantErr: true,
|
||||
},
|
||||
"show fails": {
|
||||
provider: cloudprovider.QEMU,
|
||||
tf: &stubTerraform{
|
||||
showErr: someErr,
|
||||
},
|
||||
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,11 +97,22 @@ 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(),
|
||||
},
|
||||
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')
|
||||
}
|
10
go.mod
10
go.mod
@ -34,10 +34,8 @@ replace github.com/google/go-attestation => github.com/malt3/go-attestation v0.0
|
||||
|
||||
require (
|
||||
cloud.google.com/go/compute v1.7.0
|
||||
cloud.google.com/go/iam v0.3.0
|
||||
cloud.google.com/go/kms v1.4.0
|
||||
cloud.google.com/go/logging v1.4.2
|
||||
cloud.google.com/go/resourcemanager v1.2.0
|
||||
cloud.google.com/go/storage v1.22.1
|
||||
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.1.1
|
||||
@ -106,13 +104,9 @@ require (
|
||||
k8s.io/utils v0.0.0-20220812165043-ad590609e2e5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/zclconf/go-cty v1.11.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.102.0 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.5.0 // indirect
|
||||
@ -198,6 +192,7 @@ require (
|
||||
github.com/gosuri/uitable v0.0.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/icholy/replace v0.5.0
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
@ -257,6 +252,7 @@ require (
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xlab/treeprint v1.1.0 // indirect
|
||||
github.com/zclconf/go-cty v1.11.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.starlark.net v0.0.0-20220223235035-243c74974e97 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -71,8 +71,6 @@ cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w=
|
||||
cloud.google.com/go/resourcemanager v1.2.0 h1:Oyt8+J80B51HgIPNk3p1ezTamu1wVj2bj7rBwL5Qd6k=
|
||||
cloud.google.com/go/resourcemanager v1.2.0/go.mod h1:hFYbG0p7E8vVfQO3yfeaqEQVFO6n9gg9W2czYIdSEy4=
|
||||
cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk=
|
||||
cloud.google.com/go/spanner v1.17.0/go.mod h1:+17t2ixFwRG4lWRwE+5kipDR9Ef07Jkmc8z0IbMDKUs=
|
||||
cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA=
|
||||
|
20
hack/go.mod
20
hack/go.mod
@ -56,22 +56,10 @@ require (
|
||||
libvirt.org/go/libvirtxml v1.8007.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hc-install v0.4.0 // indirect
|
||||
github.com/hashicorp/terraform-exec v0.17.3 // indirect
|
||||
github.com/hashicorp/terraform-json v0.14.0 // indirect
|
||||
github.com/zclconf/go-cty v1.11.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.102.0 // indirect
|
||||
cloud.google.com/go/iam v0.3.0 // indirect
|
||||
cloud.google.com/go/kms v1.4.0 // indirect
|
||||
cloud.google.com/go/resourcemanager v1.2.0 // indirect
|
||||
cloud.google.com/go/storage v1.22.1 // indirect
|
||||
code.cloudfoundry.org/clock v0.0.0-20180518195852-02e53af36e6c // indirect
|
||||
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible // indirect
|
||||
@ -155,6 +143,13 @@ require (
|
||||
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
|
||||
github.com/googleapis/go-type-adapters v1.0.0 // indirect
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hc-install v0.4.0 // indirect
|
||||
github.com/hashicorp/terraform-exec v0.17.3 // indirect
|
||||
github.com/hashicorp/terraform-json v0.14.0 // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
@ -184,6 +179,7 @@ require (
|
||||
github.com/theupdateframework/go-tuf v0.3.2 // indirect
|
||||
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.0 // indirect
|
||||
github.com/zclconf/go-cty v1.11.0 // indirect
|
||||
go.opencensus.io v0.23.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.3.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
|
@ -65,8 +65,6 @@ cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
|
||||
cloud.google.com/go/pubsub v1.5.0/go.mod h1:ZEwJccE3z93Z2HWvstpri00jOg7oO4UZDtKhwDwqF0w=
|
||||
cloud.google.com/go/resourcemanager v1.2.0 h1:Oyt8+J80B51HgIPNk3p1ezTamu1wVj2bj7rBwL5Qd6k=
|
||||
cloud.google.com/go/resourcemanager v1.2.0/go.mod h1:hFYbG0p7E8vVfQO3yfeaqEQVFO6n9gg9W2czYIdSEy4=
|
||||
cloud.google.com/go/spanner v1.7.0/go.mod h1:sd3K2gZ9Fd0vMPLXzeCrF6fq4i63Q7aTLW/lBIfBkIk=
|
||||
cloud.google.com/go/spanner v1.17.0/go.mod h1:+17t2ixFwRG4lWRwE+5kipDR9Ef07Jkmc8z0IbMDKUs=
|
||||
cloud.google.com/go/spanner v1.18.0/go.mod h1:LvAjUXPeJRGNuGpikMULjhLj/t9cRvdc+fxRoLiugXA=
|
||||
|
Loading…
Reference in New Issue
Block a user