Use Terraform for create Azure

This commit is contained in:
katexochen 2022-10-06 11:52:19 +02:00 committed by Paul Meyer
parent 98a16b2b47
commit f4af9c56f5
9 changed files with 97 additions and 400 deletions

View file

@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
<!-- For changes in existing functionality. --> <!-- For changes in existing functionality. -->
- Verify measurements using [Rekor](https://github.com/sigstore/rekor) transparency log. - Verify measurements using [Rekor](https://github.com/sigstore/rekor) transparency log.
- The `constellation create` on Azure now uses Terraform to create and destroy cloud resources.
### Deprecated ### Deprecated
<!-- For soon-to-be removed features. --> <!-- For soon-to-be removed features. -->

View file

@ -9,7 +9,6 @@ package cloudcmd
import ( import (
"context" "context"
azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/state" "github.com/edgelesssys/constellation/v2/internal/state"
) )
@ -22,17 +21,6 @@ type terraformClient interface {
RemoveInstaller() RemoveInstaller()
} }
type azureclient interface {
GetState() state.ConstellationState
SetState(state.ConstellationState)
CreateApplicationInsight(ctx context.Context) error
CreateExternalLoadBalancer(ctx context.Context, isDebugCluster bool) error
CreateVirtualNetwork(ctx context.Context) error
CreateSecurityGroup(ctx context.Context, input azurecl.NetworkSecurityGroupInput) error
CreateInstances(ctx context.Context, input azurecl.CreateInstancesInput) error
TerminateResourceGroupResources(ctx context.Context) error
}
type libvirtRunner interface { type libvirtRunner interface {
Start(ctx context.Context, containerName, imageName string) error Start(ctx context.Context, containerName, imageName string) error
Stop(ctx context.Context) error Stop(ctx context.Context) error

View file

@ -8,14 +8,9 @@ package cloudcmd
import ( import (
"context" "context"
"strconv"
"testing" "testing"
azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "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"
"github.com/edgelesssys/constellation/v2/internal/state" "github.com/edgelesssys/constellation/v2/internal/state"
"go.uber.org/goleak" "go.uber.org/goleak"
) )
@ -27,173 +22,6 @@ func TestMain(m *testing.M) {
) )
} }
type fakeAzureClient struct {
workers cloudtypes.Instances
controlPlanes cloudtypes.Instances
resourceGroup string
name string
uid string
location string
subscriptionID string
tenantID string
subnetID string
loadBalancerName string
controlPlaneScaleSet string
workerScaleSet string
networkSecurityGroup string
adAppObjectID string
}
func (c *fakeAzureClient) GetState() state.ConstellationState {
return state.ConstellationState{
CloudProvider: cloudprovider.Azure.String(),
AzureWorkerInstances: c.workers,
AzureControlPlaneInstances: c.controlPlanes,
Name: c.name,
UID: c.uid,
AzureResourceGroup: c.resourceGroup,
AzureLocation: c.location,
AzureSubscription: c.subscriptionID,
AzureTenant: c.tenantID,
AzureSubnet: c.subnetID,
AzureNetworkSecurityGroup: c.networkSecurityGroup,
AzureWorkerScaleSet: c.workerScaleSet,
AzureControlPlaneScaleSet: c.controlPlaneScaleSet,
AzureADAppObjectID: c.adAppObjectID,
}
}
func (c *fakeAzureClient) SetState(stat state.ConstellationState) {
c.workers = stat.AzureWorkerInstances
c.controlPlanes = stat.AzureControlPlaneInstances
c.name = stat.Name
c.uid = stat.UID
c.resourceGroup = stat.AzureResourceGroup
c.location = stat.AzureLocation
c.subscriptionID = stat.AzureSubscription
c.tenantID = stat.AzureTenant
c.subnetID = stat.AzureSubnet
c.networkSecurityGroup = stat.AzureNetworkSecurityGroup
c.workerScaleSet = stat.AzureWorkerScaleSet
c.controlPlaneScaleSet = stat.AzureControlPlaneScaleSet
c.adAppObjectID = stat.AzureADAppObjectID
}
func (c *fakeAzureClient) CreateApplicationInsight(ctx context.Context) error {
return nil
}
func (c *fakeAzureClient) CreateVirtualNetwork(ctx context.Context) error {
c.subnetID = "subnet"
return nil
}
func (c *fakeAzureClient) CreateExternalLoadBalancer(ctx context.Context, isDebugCluster bool) error {
c.loadBalancerName = "loadBalancer"
return nil
}
func (c *fakeAzureClient) CreateSecurityGroup(ctx context.Context, input azurecl.NetworkSecurityGroupInput) error {
c.networkSecurityGroup = "network-security-group"
return nil
}
func (c *fakeAzureClient) CreateInstances(ctx context.Context, input azurecl.CreateInstancesInput) error {
c.controlPlaneScaleSet = "controlplanes-scale-set"
c.workerScaleSet = "workers-scale-set"
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 *fakeAzureClient) CreateServicePrincipal(ctx context.Context) (string, error) {
c.adAppObjectID = "00000000-0000-0000-0000-000000000001"
return azureshared.ApplicationCredentials{
AppClientID: "client-id",
ClientSecretValue: "client-secret",
}.ToCloudServiceAccountURI(), nil
}
func (c *fakeAzureClient) TerminateResourceGroupResources(ctx context.Context) error {
// TODO(katexochen)
return nil
}
func (c *fakeAzureClient) TerminateServicePrincipal(ctx context.Context) error {
if c.adAppObjectID == "" {
return nil
}
c.adAppObjectID = ""
return nil
}
type stubAzureClient struct {
terminateResourceGroupResourcesCalled bool
terminateServicePrincipalCalled bool
createApplicationInsightErr error
createVirtualNetworkErr error
createSecurityGroupErr error
createLoadBalancerErr error
createInstancesErr error
createServicePrincipalErr error
terminateResourceGroupResourcesErr error
terminateServicePrincipalErr error
}
func (c *stubAzureClient) GetState() state.ConstellationState {
return state.ConstellationState{}
}
func (c *stubAzureClient) SetState(state.ConstellationState) {
}
func (c *stubAzureClient) CreateExternalLoadBalancer(ctx context.Context, isDebugCluster bool) error {
return c.createLoadBalancerErr
}
func (c *stubAzureClient) CreateApplicationInsight(ctx context.Context) error {
return c.createApplicationInsightErr
}
func (c *stubAzureClient) CreateVirtualNetwork(ctx context.Context) error {
return c.createVirtualNetworkErr
}
func (c *stubAzureClient) CreateSecurityGroup(ctx context.Context, input azurecl.NetworkSecurityGroupInput) error {
return c.createSecurityGroupErr
}
func (c *stubAzureClient) CreateInstances(ctx context.Context, input azurecl.CreateInstancesInput) error {
return c.createInstancesErr
}
func (c *stubAzureClient) CreateServicePrincipal(ctx context.Context) (string, error) {
return azureshared.ApplicationCredentials{
AppClientID: "00000000-0000-0000-0000-000000000000",
ClientSecretValue: "secret",
}.ToCloudServiceAccountURI(), c.createServicePrincipalErr
}
func (c *stubAzureClient) TerminateResourceGroupResources(ctx context.Context) error {
c.terminateResourceGroupResourcesCalled = true
return c.terminateResourceGroupResourcesErr
}
func (c *stubAzureClient) TerminateServicePrincipal(ctx context.Context) error {
c.terminateServicePrincipalCalled = true
return c.terminateServicePrincipalErr
}
type stubTerraformClient struct { type stubTerraformClient struct {
state state.ConstellationState state state.ConstellationState
cleanUpWorkspaceCalled bool cleanUpWorkspaceCalled bool

View file

@ -15,13 +15,10 @@ import (
"runtime" "runtime"
"strings" "strings"
azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/constants"
"github.com/edgelesssys/constellation/v2/internal/state" "github.com/edgelesssys/constellation/v2/internal/state"
) )
@ -29,7 +26,6 @@ import (
type Creator struct { type Creator struct {
out io.Writer out io.Writer
newTerraformClient func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) newTerraformClient func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error)
newAzureClient func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error)
newLibvirtRunner func() libvirtRunner newLibvirtRunner func() libvirtRunner
} }
@ -40,9 +36,6 @@ func NewCreator(out io.Writer) *Creator {
newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) { newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) {
return terraform.New(ctx, provider) return terraform.New(ctx, provider)
}, },
newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) {
return azurecl.NewInitialized(subscriptionID, tenantID, name, location, resourceGroup)
},
newLibvirtRunner: func() libvirtRunner { newLibvirtRunner: func() libvirtRunner {
return libvirt.New() return libvirt.New()
}, },
@ -52,14 +45,6 @@ func NewCreator(out io.Writer) *Creator {
// Create creates the handed amount of instances and all the needed resources. // Create creates the handed amount of instances and all the needed resources.
func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, config *config.Config, name, insType string, controlPlaneCount, workerCount int, func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, config *config.Config, name, insType string, controlPlaneCount, workerCount int,
) (state.ConstellationState, error) { ) (state.ConstellationState, error) {
// Use debug ingress firewall rules when debug mode / image is enabled
var ingressRules cloudtypes.Firewall
if config.IsDebugCluster() {
ingressRules = constants.IngressRulesDebug
} else {
ingressRules = constants.IngressRulesNoDebug
}
switch provider { switch provider {
case cloudprovider.GCP: case cloudprovider.GCP:
cl, err := c.newTerraformClient(ctx, provider) cl, err := c.newTerraformClient(ctx, provider)
@ -69,17 +54,12 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c
defer cl.RemoveInstaller() defer cl.RemoveInstaller()
return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount) return c.createGCP(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
case cloudprovider.Azure: case cloudprovider.Azure:
cl, err := c.newAzureClient( cl, err := c.newTerraformClient(ctx, provider)
config.Provider.Azure.SubscriptionID,
config.Provider.Azure.TenantID,
name,
config.Provider.Azure.Location,
config.Provider.Azure.ResourceGroup,
)
if err != nil { if err != nil {
return state.ConstellationState{}, err return state.ConstellationState{}, err
} }
return c.createAzure(ctx, cl, config, insType, controlPlaneCount, workerCount, ingressRules) defer cl.RemoveInstaller()
return c.createAzure(ctx, cl, config, name, insType, controlPlaneCount, workerCount)
case cloudprovider.QEMU: case cloudprovider.QEMU:
if runtime.GOARCH != "amd64" || runtime.GOOS != "linux" { 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) return state.ConstellationState{}, fmt.Errorf("creation of a QEMU based Constellation is not supported for %s/%s", runtime.GOOS, runtime.GOARCH)
@ -125,37 +105,29 @@ func (c *Creator) createGCP(ctx context.Context, cl terraformClient, config *con
return cl.GetState(), nil return cl.GetState(), nil
} }
func (c *Creator) createAzure(ctx context.Context, cl azureclient, config *config.Config, insType string, controlPlaneCount, workerCount int, ingressRules cloudtypes.Firewall, func (c *Creator) createAzure(ctx context.Context, cl terraformClient, config *config.Config,
name, insType string, controlPlaneCount, workerCount int,
) (stat state.ConstellationState, retErr error) { ) (stat state.ConstellationState, retErr error) {
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerAzure{client: cl}) defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerTerraform{client: cl})
if err := cl.CreateApplicationInsight(ctx); err != nil { vars := &terraform.AzureVariables{
return state.ConstellationState{}, err CommonVariables: terraform.CommonVariables{
} Name: name,
if err := cl.CreateExternalLoadBalancer(ctx, config.IsDebugCluster()); err != nil { CountControlPlanes: controlPlaneCount,
return state.ConstellationState{}, err CountWorkers: workerCount,
} StateDiskSizeGB: config.StateDiskSizeGB,
if err := cl.CreateVirtualNetwork(ctx); err != nil { },
return state.ConstellationState{}, err Location: config.Provider.Azure.Location,
} ResourceGroup: config.Provider.Azure.ResourceGroup,
UserAssignedIdentity: config.Provider.Azure.UserAssignedIdentity,
if err := cl.CreateSecurityGroup(ctx, azurecl.NetworkSecurityGroupInput{
Ingress: ingressRules,
Egress: constants.EgressRules,
}); err != nil {
return state.ConstellationState{}, err
}
createInput := azurecl.CreateInstancesInput{
CountControlPlanes: controlPlaneCount,
CountWorkers: workerCount,
InstanceType: insType, InstanceType: insType,
StateDiskSizeGB: config.StateDiskSizeGB,
StateDiskType: config.Provider.Azure.StateDiskType, StateDiskType: config.Provider.Azure.StateDiskType,
Image: config.Provider.Azure.Image, ImageID: config.Provider.Azure.Image,
UserAssingedIdentity: config.Provider.Azure.UserAssignedIdentity,
ConfidentialVM: *config.Provider.Azure.ConfidentialVM, ConfidentialVM: *config.Provider.Azure.ConfidentialVM,
Debug: config.IsDebugCluster(),
} }
if err := cl.CreateInstances(ctx, createInput); err != nil {
if err := cl.CreateCluster(ctx, name, vars); err != nil {
return state.ConstellationState{}, err return state.ConstellationState{}, err
} }

View file

@ -14,7 +14,6 @@ import (
"testing" "testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/v2/internal/config" "github.com/edgelesssys/constellation/v2/internal/config"
"github.com/edgelesssys/constellation/v2/internal/state" "github.com/edgelesssys/constellation/v2/internal/state"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -33,36 +32,17 @@ func TestCreator(t *testing.T) {
LoadBalancerIP: "192.0.2.1", LoadBalancerIP: "192.0.2.1",
} }
wantAzureState := state.ConstellationState{
CloudProvider: cloudprovider.Azure.String(),
AzureControlPlaneInstances: 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"},
},
AzureWorkerInstances: 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"},
},
AzureSubnet: "subnet",
AzureNetworkSecurityGroup: "network-security-group",
AzureWorkerScaleSet: "workers-scale-set",
AzureControlPlaneScaleSet: "controlplanes-scale-set",
}
someErr := errors.New("failed") someErr := errors.New("failed")
testCases := map[string]struct { testCases := map[string]struct {
tfClient terraformClient tfClient terraformClient
newTfClientErr error newTfClientErr error
azureclient azureclient libvirt *stubLibvirtRunner
newAzureClientErr error provider cloudprovider.Provider
libvirt *stubLibvirtRunner config *config.Config
provider cloudprovider.Provider wantState state.ConstellationState
config *config.Config wantErr bool
wantState state.ConstellationState wantRollback bool // Use only together with stubClients.
wantErr bool
wantRollback bool // Use only together with stubClients.
}{ }{
"gcp": { "gcp": {
tfClient: &stubTerraformClient{state: wantGCPState}, tfClient: &stubTerraformClient{state: wantGCPState},
@ -114,39 +94,6 @@ func TestCreator(t *testing.T) {
wantErr: true, wantErr: true,
wantRollback: !failOnNonAMD64, wantRollback: !failOnNonAMD64,
}, },
"azure": {
azureclient: &fakeAzureClient{},
provider: cloudprovider.Azure,
config: config.Default(),
wantState: wantAzureState,
},
"azure newAzureClient error": {
newAzureClientErr: someErr,
provider: cloudprovider.Azure,
config: config.Default(),
wantErr: true,
},
"azure CreateVirtualNetwork error": {
azureclient: &stubAzureClient{createVirtualNetworkErr: someErr},
provider: cloudprovider.Azure,
config: config.Default(),
wantErr: true,
wantRollback: true,
},
"azure CreateSecurityGroup error": {
azureclient: &stubAzureClient{createSecurityGroupErr: someErr},
provider: cloudprovider.Azure,
config: config.Default(),
wantErr: true,
wantRollback: true,
},
"azure CreateInstances error": {
azureclient: &stubAzureClient{createInstancesErr: someErr},
provider: cloudprovider.Azure,
config: config.Default(),
wantErr: true,
wantRollback: true,
},
"unknown provider": { "unknown provider": {
provider: cloudprovider.Unknown, provider: cloudprovider.Unknown,
config: config.Default(), config: config.Default(),
@ -163,9 +110,6 @@ func TestCreator(t *testing.T) {
newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) { newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) {
return tc.tfClient, tc.newTfClientErr return tc.tfClient, tc.newTfClientErr
}, },
newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) {
return tc.azureclient, tc.newAzureClientErr
},
newLibvirtRunner: func() libvirtRunner { newLibvirtRunner: func() libvirtRunner {
return tc.libvirt return tc.libvirt
}, },
@ -176,19 +120,11 @@ func TestCreator(t *testing.T) {
if tc.wantErr { if tc.wantErr {
assert.Error(err) assert.Error(err)
if tc.wantRollback { if tc.wantRollback {
switch tc.provider { cl := tc.tfClient.(*stubTerraformClient)
case cloudprovider.QEMU: assert.True(cl.destroyClusterCalled)
cl := tc.tfClient.(*stubTerraformClient) assert.True(cl.cleanUpWorkspaceCalled)
assert.True(cl.destroyClusterCalled) if tc.provider == cloudprovider.QEMU {
assert.True(cl.cleanUpWorkspaceCalled)
assert.True(tc.libvirt.stopCalled) assert.True(tc.libvirt.stopCalled)
case cloudprovider.GCP:
cl := tc.tfClient.(*stubTerraformClient)
assert.True(cl.destroyClusterCalled)
assert.True(cl.cleanUpWorkspaceCalled)
case cloudprovider.Azure:
cl := tc.azureclient.(*stubAzureClient)
assert.True(cl.terminateResourceGroupResourcesCalled)
} }
} }
} else { } else {

View file

@ -34,14 +34,6 @@ func rollbackOnError(ctx context.Context, w io.Writer, onErr *error, roll rollba
fmt.Fprintln(w, "Rollback succeeded.") fmt.Fprintln(w, "Rollback succeeded.")
} }
type rollbackerAzure struct {
client azureclient
}
func (r *rollbackerAzure) rollback(ctx context.Context) error {
return r.client.TerminateResourceGroupResources(ctx)
}
type rollbackerTerraform struct { type rollbackerTerraform struct {
client terraformClient client terraformClient
} }

View file

@ -10,7 +10,6 @@ import (
"context" "context"
"fmt" "fmt"
azurecl "github.com/edgelesssys/constellation/v2/cli/internal/azure/client"
"github.com/edgelesssys/constellation/v2/cli/internal/libvirt" "github.com/edgelesssys/constellation/v2/cli/internal/libvirt"
"github.com/edgelesssys/constellation/v2/cli/internal/terraform" "github.com/edgelesssys/constellation/v2/cli/internal/terraform"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
@ -19,19 +18,15 @@ import (
// Terminator deletes cloud provider resources. // Terminator deletes cloud provider resources.
type Terminator struct { type Terminator struct {
newTerraformClient func(ctx context.Context) (terraformClient, error) newTerraformClient func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error)
newAzureClient func(subscriptionID, tenantID string) (azureclient, error)
newLibvirtRunner func() libvirtRunner newLibvirtRunner func() libvirtRunner
} }
// NewTerminator create a new cloud terminator. // NewTerminator create a new cloud terminator.
func NewTerminator() *Terminator { func NewTerminator() *Terminator {
return &Terminator{ return &Terminator{
newTerraformClient: func(ctx context.Context) (terraformClient, error) { newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) {
return terraform.New(ctx, cloudprovider.GCP) return terraform.New(ctx, provider)
},
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
return azurecl.NewFromDefault(subscriptionID, tenantID)
}, },
newLibvirtRunner: func() libvirtRunner { newLibvirtRunner: func() libvirtRunner {
return libvirt.New() return libvirt.New()
@ -42,37 +37,22 @@ func NewTerminator() *Terminator {
// Terminate deletes the could provider resources defined in the constellation state. // Terminate deletes the could provider resources defined in the constellation state.
func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationState) error { func (t *Terminator) Terminate(ctx context.Context, state state.ConstellationState) error {
provider := cloudprovider.FromString(state.CloudProvider) provider := cloudprovider.FromString(state.CloudProvider)
switch provider { if provider == cloudprovider.Unknown {
case cloudprovider.Azure: return fmt.Errorf("unknown cloud provider %s", state.CloudProvider)
cl, err := t.newAzureClient(state.AzureSubscription, state.AzureTenant) }
if err != nil {
return err cl, err := t.newTerraformClient(ctx, provider)
} if err != nil {
return t.terminateAzure(ctx, cl, state) return err
case cloudprovider.GCP: }
cl, err := t.newTerraformClient(ctx) defer cl.RemoveInstaller()
if err != nil {
return err if provider == cloudprovider.QEMU {
}
defer cl.RemoveInstaller()
return t.terminateTerraform(ctx, cl)
case cloudprovider.QEMU:
cl, err := t.newTerraformClient(ctx)
if err != nil {
return err
}
defer cl.RemoveInstaller()
libvirt := t.newLibvirtRunner() libvirt := t.newLibvirtRunner()
return t.terminateQEMU(ctx, cl, libvirt) return t.terminateQEMU(ctx, cl, libvirt)
default:
return fmt.Errorf("unsupported provider: %s", provider)
} }
}
func (t *Terminator) terminateAzure(ctx context.Context, cl azureclient, state state.ConstellationState) error { return t.terminateTerraform(ctx, cl)
cl.SetState(state)
return cl.TerminateResourceGroupResources(ctx)
} }
func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient) error { func (t *Terminator) terminateTerraform(ctx context.Context, cl terraformClient) error {

View file

@ -12,35 +12,19 @@ import (
"testing" "testing"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider" "github.com/edgelesssys/constellation/v2/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/v2/internal/cloud/cloudtypes"
"github.com/edgelesssys/constellation/v2/internal/state" "github.com/edgelesssys/constellation/v2/internal/state"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestTerminator(t *testing.T) { func TestTerminator(t *testing.T) {
someAzureState := func() state.ConstellationState {
return state.ConstellationState{
CloudProvider: cloudprovider.Azure.String(),
AzureWorkerInstances: 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"},
},
AzureControlPlaneInstances: cloudtypes.Instances{
"id-c": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
},
AzureADAppObjectID: "00000000-0000-0000-0000-000000000001",
}
}
someErr := errors.New("failed") someErr := errors.New("failed")
testCases := map[string]struct { testCases := map[string]struct {
tfClient terraformClient tfClient terraformClient
newTfClientErr error newTfClientErr error
azureclient azureclient libvirt *stubLibvirtRunner
newAzureClientErr error state state.ConstellationState
libvirt *stubLibvirtRunner wantErr bool
state state.ConstellationState
wantErr bool
}{ }{
"gcp": { "gcp": {
tfClient: &stubTerraformClient{}, tfClient: &stubTerraformClient{},
@ -84,20 +68,6 @@ func TestTerminator(t *testing.T) {
state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()}, state: state.ConstellationState{CloudProvider: cloudprovider.QEMU.String()},
wantErr: true, wantErr: true,
}, },
"azure": {
azureclient: &stubAzureClient{},
state: someAzureState(),
},
"azure newAzureClient error": {
newAzureClientErr: someErr,
state: someAzureState(),
wantErr: true,
},
"azure terminateResourceGroupResources error": {
azureclient: &stubAzureClient{terminateResourceGroupResourcesErr: someErr},
state: someAzureState(),
wantErr: true,
},
"unknown cloud provider": { "unknown cloud provider": {
state: state.ConstellationState{}, state: state.ConstellationState{},
wantErr: true, wantErr: true,
@ -109,12 +79,9 @@ func TestTerminator(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
terminator := &Terminator{ terminator := &Terminator{
newTerraformClient: func(ctx context.Context) (terraformClient, error) { newTerraformClient: func(ctx context.Context, provider cloudprovider.Provider) (terraformClient, error) {
return tc.tfClient, tc.newTfClientErr return tc.tfClient, tc.newTfClientErr
}, },
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
return tc.azureclient, tc.newAzureClientErr
},
newLibvirtRunner: func() libvirtRunner { newLibvirtRunner: func() libvirtRunner {
return tc.libvirt return tc.libvirt
}, },
@ -126,17 +93,11 @@ func TestTerminator(t *testing.T) {
assert.Error(err) assert.Error(err)
} else { } else {
assert.NoError(err) assert.NoError(err)
switch cloudprovider.FromString(tc.state.CloudProvider) { cl := tc.tfClient.(*stubTerraformClient)
case cloudprovider.QEMU: assert.True(cl.destroyClusterCalled)
assert.True(cl.removeInstallerCalled)
if cloudprovider.FromString(tc.state.CloudProvider) == cloudprovider.QEMU {
assert.True(tc.libvirt.stopCalled) assert.True(tc.libvirt.stopCalled)
fallthrough
case cloudprovider.GCP:
cl := tc.tfClient.(*stubTerraformClient)
assert.True(cl.destroyClusterCalled)
assert.True(cl.removeInstallerCalled)
case cloudprovider.Azure:
cl := tc.azureclient.(*stubAzureClient)
assert.True(cl.terminateResourceGroupResourcesCalled)
} }
} }
}) })

View file

@ -78,6 +78,45 @@ func (v *GCPVariables) String() string {
return b.String() return b.String()
} }
// AzureVariables is user configuration for creating a cluster with Terraform on Azure.
type AzureVariables struct {
// CommonVariables contains common variables.
CommonVariables
// ResourceGroup is the name of the Azure resource group to use.
ResourceGroup string
// Location is the Azure location to use.
Location string
// UserAssignedIdentity is the name of the Azure user-assigned identity to use.
UserAssignedIdentity string
// InstanceType is the Azure instance type to use.
InstanceType string
// StateDiskType is the Azure disk type to use for the state disk.
StateDiskType string
// ImageID is the ID of the Azure image to use.
ImageID string
// ConfidentialVM sets the VM to be confidential.
ConfidentialVM bool
// Debug is true if debug mode is enabled.
Debug bool
}
// String returns a string representation of the variables, formatted as Terraform variables.
func (v *AzureVariables) String() string {
b := &strings.Builder{}
b.WriteString(v.CommonVariables.String())
writeLinef(b, "resource_group = %q", v.ResourceGroup)
writeLinef(b, "location = %q", v.Location)
writeLinef(b, "user_assigned_identity = %q", v.UserAssignedIdentity)
writeLinef(b, "instance_type = %q", v.InstanceType)
writeLinef(b, "state_disk_type = %q", v.StateDiskType)
writeLinef(b, "image_id = %q", v.ImageID)
writeLinef(b, "confidential_vm = %t", v.ConfidentialVM)
writeLinef(b, "debug = %t", v.Debug)
return b.String()
}
// QEMUVariables is user configuration for creating a QEMU cluster with Terraform. // QEMUVariables is user configuration for creating a QEMU cluster with Terraform.
type QEMUVariables struct { type QEMUVariables struct {
// CommonVariables contains common variables. // CommonVariables contains common variables.