Use Terraform for create on GCP

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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())

View file

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

View file

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