mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-07-24 07:50:40 -04:00
Split cmd package
This commit is contained in:
parent
63898c42bf
commit
de52bf14da
36 changed files with 1875 additions and 2302 deletions
37
cli/cloud/cloudcmd/clients.go
Normal file
37
cli/cloud/cloudcmd/clients.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
package cloudcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
azurecl "github.com/edgelesssys/constellation/cli/azure/client"
|
||||
gcpcl "github.com/edgelesssys/constellation/cli/gcp/client"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
)
|
||||
|
||||
type gcpclient interface {
|
||||
GetState() (state.ConstellationState, error)
|
||||
SetState(state.ConstellationState) error
|
||||
CreateVPCs(ctx context.Context, input gcpcl.VPCsInput) error
|
||||
CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error
|
||||
CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error
|
||||
CreateServiceAccount(ctx context.Context, input gcpcl.ServiceAccountInput) (string, error)
|
||||
TerminateFirewall(ctx context.Context) error
|
||||
TerminateVPCs(context.Context) error
|
||||
TerminateInstances(context.Context) error
|
||||
TerminateServiceAccount(ctx context.Context) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type azureclient interface {
|
||||
GetState() (state.ConstellationState, error)
|
||||
SetState(state.ConstellationState) error
|
||||
CreateResourceGroup(ctx context.Context) error
|
||||
CreateVirtualNetwork(ctx context.Context) error
|
||||
CreateSecurityGroup(ctx context.Context, input azurecl.NetworkSecurityGroupInput) error
|
||||
CreateInstances(ctx context.Context, input azurecl.CreateInstancesInput) error
|
||||
// TODO: deprecate as soon as scale sets are available
|
||||
CreateInstancesVMs(ctx context.Context, input azurecl.CreateInstancesInput) error
|
||||
CreateServicePrincipal(ctx context.Context) (string, error)
|
||||
TerminateResourceGroup(ctx context.Context) error
|
||||
TerminateServicePrincipal(ctx context.Context) error
|
||||
}
|
419
cli/cloud/cloudcmd/clients_test.go
Normal file
419
cli/cloud/cloudcmd/clients_test.go
Normal file
|
@ -0,0 +1,419 @@
|
|||
package cloudcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/edgelesssys/constellation/cli/azure"
|
||||
azurecl "github.com/edgelesssys/constellation/cli/azure/client"
|
||||
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/cli/gcp"
|
||||
gcpcl "github.com/edgelesssys/constellation/cli/gcp/client"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
)
|
||||
|
||||
type fakeAzureClient struct {
|
||||
nodes azure.Instances
|
||||
coordinators azure.Instances
|
||||
|
||||
resourceGroup string
|
||||
name string
|
||||
uid string
|
||||
location string
|
||||
subscriptionID string
|
||||
tenantID string
|
||||
subnetID string
|
||||
coordinatorsScaleSet string
|
||||
nodesScaleSet string
|
||||
networkSecurityGroup string
|
||||
adAppObjectID string
|
||||
}
|
||||
|
||||
func (c *fakeAzureClient) GetState() (state.ConstellationState, error) {
|
||||
stat := state.ConstellationState{
|
||||
CloudProvider: cloudprovider.Azure.String(),
|
||||
AzureNodes: c.nodes,
|
||||
AzureCoordinators: c.coordinators,
|
||||
Name: c.name,
|
||||
UID: c.uid,
|
||||
AzureResourceGroup: c.resourceGroup,
|
||||
AzureLocation: c.location,
|
||||
AzureSubscription: c.subscriptionID,
|
||||
AzureTenant: c.tenantID,
|
||||
AzureSubnet: c.subnetID,
|
||||
AzureNetworkSecurityGroup: c.networkSecurityGroup,
|
||||
AzureNodesScaleSet: c.nodesScaleSet,
|
||||
AzureCoordinatorsScaleSet: c.coordinatorsScaleSet,
|
||||
AzureADAppObjectID: c.adAppObjectID,
|
||||
}
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func (c *fakeAzureClient) SetState(stat state.ConstellationState) error {
|
||||
c.nodes = stat.AzureNodes
|
||||
c.coordinators = stat.AzureCoordinators
|
||||
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.nodesScaleSet = stat.AzureNodesScaleSet
|
||||
c.coordinatorsScaleSet = stat.AzureCoordinatorsScaleSet
|
||||
c.adAppObjectID = stat.AzureADAppObjectID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeAzureClient) CreateResourceGroup(ctx context.Context) error {
|
||||
c.resourceGroup = "resource-group"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeAzureClient) CreateVirtualNetwork(ctx context.Context) error {
|
||||
c.subnetID = "subnet"
|
||||
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.coordinatorsScaleSet = "coordinators-scale-set"
|
||||
c.nodesScaleSet = "nodes-scale-set"
|
||||
c.nodes = make(azure.Instances)
|
||||
for i := 0; i < input.CountNodes; i++ {
|
||||
id := "id-" + strconv.Itoa(i)
|
||||
c.nodes[id] = azure.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"}
|
||||
}
|
||||
c.coordinators = make(azure.Instances)
|
||||
for i := 0; i < input.CountCoordinators; i++ {
|
||||
id := "id-" + strconv.Itoa(i)
|
||||
c.coordinators[id] = azure.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: deprecate as soon as scale sets are available.
|
||||
func (c *fakeAzureClient) CreateInstancesVMs(ctx context.Context, input azurecl.CreateInstancesInput) error {
|
||||
c.nodes = make(azure.Instances)
|
||||
for i := 0; i < input.CountNodes; i++ {
|
||||
id := "id-" + strconv.Itoa(i)
|
||||
c.nodes[id] = azure.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"}
|
||||
}
|
||||
c.coordinators = make(azure.Instances)
|
||||
for i := 0; i < input.CountCoordinators; i++ {
|
||||
id := "id-" + strconv.Itoa(i)
|
||||
c.coordinators[id] = azure.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 azurecl.ApplicationCredentials{
|
||||
ClientID: "client-id",
|
||||
ClientSecret: "client-secret",
|
||||
}.ConvertToCloudServiceAccountURI(), nil
|
||||
}
|
||||
|
||||
func (c *fakeAzureClient) TerminateResourceGroup(ctx context.Context) error {
|
||||
if c.resourceGroup == "" {
|
||||
return nil
|
||||
}
|
||||
c.nodes = nil
|
||||
c.coordinators = nil
|
||||
c.resourceGroup = ""
|
||||
c.subnetID = ""
|
||||
c.networkSecurityGroup = ""
|
||||
c.nodesScaleSet = ""
|
||||
c.coordinatorsScaleSet = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeAzureClient) TerminateServicePrincipal(ctx context.Context) error {
|
||||
if c.adAppObjectID == "" {
|
||||
return nil
|
||||
}
|
||||
c.adAppObjectID = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubAzureClient struct {
|
||||
terminateResourceGroupCalled bool
|
||||
terminateServicePrincipalCalled bool
|
||||
|
||||
getStateErr error
|
||||
setStateErr error
|
||||
createResourceGroupErr error
|
||||
createVirtualNetworkErr error
|
||||
createSecurityGroupErr error
|
||||
createInstancesErr error
|
||||
createServicePrincipalErr error
|
||||
terminateResourceGroupErr error
|
||||
terminateServicePrincipalErr error
|
||||
}
|
||||
|
||||
func (c *stubAzureClient) GetState() (state.ConstellationState, error) {
|
||||
return state.ConstellationState{}, c.getStateErr
|
||||
}
|
||||
|
||||
func (c *stubAzureClient) SetState(state.ConstellationState) error {
|
||||
return c.setStateErr
|
||||
}
|
||||
|
||||
func (c *stubAzureClient) CreateResourceGroup(ctx context.Context) error {
|
||||
return c.createResourceGroupErr
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: deprecate as soon as scale sets are available.
|
||||
func (c *stubAzureClient) CreateInstancesVMs(ctx context.Context, input azurecl.CreateInstancesInput) error {
|
||||
return c.createInstancesErr
|
||||
}
|
||||
|
||||
func (c *stubAzureClient) CreateServicePrincipal(ctx context.Context) (string, error) {
|
||||
return azurecl.ApplicationCredentials{
|
||||
ClientID: "00000000-0000-0000-0000-000000000000",
|
||||
ClientSecret: "secret",
|
||||
}.ConvertToCloudServiceAccountURI(), c.createServicePrincipalErr
|
||||
}
|
||||
|
||||
func (c *stubAzureClient) TerminateResourceGroup(ctx context.Context) error {
|
||||
c.terminateResourceGroupCalled = true
|
||||
return c.terminateResourceGroupErr
|
||||
}
|
||||
|
||||
func (c *stubAzureClient) TerminateServicePrincipal(ctx context.Context) error {
|
||||
c.terminateServicePrincipalCalled = true
|
||||
return c.terminateServicePrincipalErr
|
||||
}
|
||||
|
||||
type fakeGcpClient struct {
|
||||
nodes gcp.Instances
|
||||
coordinators gcp.Instances
|
||||
|
||||
nodesInstanceGroup string
|
||||
coordinatorInstanceGroup string
|
||||
coordinatorTemplate string
|
||||
nodeTemplate string
|
||||
network string
|
||||
subnetwork string
|
||||
firewalls []string
|
||||
project string
|
||||
uid string
|
||||
name string
|
||||
zone string
|
||||
serviceAccount string
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) GetState() (state.ConstellationState, error) {
|
||||
stat := state.ConstellationState{
|
||||
CloudProvider: cloudprovider.GCP.String(),
|
||||
GCPNodes: c.nodes,
|
||||
GCPCoordinators: c.coordinators,
|
||||
GCPNodeInstanceGroup: c.nodesInstanceGroup,
|
||||
GCPCoordinatorInstanceGroup: c.coordinatorInstanceGroup,
|
||||
GCPNodeInstanceTemplate: c.nodeTemplate,
|
||||
GCPCoordinatorInstanceTemplate: c.coordinatorTemplate,
|
||||
GCPNetwork: c.network,
|
||||
GCPSubnetwork: c.subnetwork,
|
||||
GCPFirewalls: c.firewalls,
|
||||
GCPProject: c.project,
|
||||
Name: c.name,
|
||||
UID: c.uid,
|
||||
GCPZone: c.zone,
|
||||
GCPServiceAccount: c.serviceAccount,
|
||||
}
|
||||
return stat, nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) SetState(stat state.ConstellationState) error {
|
||||
c.nodes = stat.GCPNodes
|
||||
c.coordinators = stat.GCPCoordinators
|
||||
c.nodesInstanceGroup = stat.GCPNodeInstanceGroup
|
||||
c.coordinatorInstanceGroup = stat.GCPCoordinatorInstanceGroup
|
||||
c.nodeTemplate = stat.GCPNodeInstanceTemplate
|
||||
c.coordinatorTemplate = stat.GCPCoordinatorInstanceTemplate
|
||||
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.serviceAccount = stat.GCPServiceAccount
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) CreateVPCs(ctx context.Context, input gcpcl.VPCsInput) error {
|
||||
c.network = "network"
|
||||
c.subnetwork = "subnetwork"
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) CreateFirewall(ctx context.Context, input gcpcl.FirewallInput) error {
|
||||
if c.network == "" {
|
||||
return errors.New("client has not network")
|
||||
}
|
||||
var firewalls []string
|
||||
for _, rule := range input.Ingress {
|
||||
firewalls = append(firewalls, rule.Name)
|
||||
}
|
||||
c.firewalls = firewalls
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) CreateInstances(ctx context.Context, input gcpcl.CreateInstancesInput) error {
|
||||
c.coordinatorInstanceGroup = "coordinator-group"
|
||||
c.nodesInstanceGroup = "nodes-group"
|
||||
c.nodeTemplate = "node-template"
|
||||
c.coordinatorTemplate = "coordinator-template"
|
||||
c.nodes = make(gcp.Instances)
|
||||
for i := 0; i < input.CountNodes; i++ {
|
||||
id := "id-" + strconv.Itoa(i)
|
||||
c.nodes[id] = gcp.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"}
|
||||
}
|
||||
c.coordinators = make(gcp.Instances)
|
||||
for i := 0; i < input.CountCoordinators; i++ {
|
||||
id := "id-" + strconv.Itoa(i)
|
||||
c.coordinators[id] = gcp.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) CreateServiceAccount(ctx context.Context, input gcpcl.ServiceAccountInput) (string, error) {
|
||||
c.serviceAccount = "service-account@" + c.project + ".iam.gserviceaccount.com"
|
||||
return gcpcl.ServiceAccountKey{
|
||||
Type: "service_account",
|
||||
ProjectID: c.project,
|
||||
PrivateKeyID: "key-id",
|
||||
PrivateKey: "-----BEGIN PRIVATE KEY-----\nprivate-key\n-----END PRIVATE KEY-----\n",
|
||||
ClientEmail: c.serviceAccount,
|
||||
ClientID: "client-id",
|
||||
AuthURI: "https://accounts.google.com/o/oauth2/auth",
|
||||
TokenURI: "https://accounts.google.com/o/oauth2/token",
|
||||
AuthProviderX509CertURL: "https://www.googleapis.com/oauth2/v1/certs",
|
||||
ClientX509CertURL: "https://www.googleapis.com/robot/v1/metadata/x509/service-account-email",
|
||||
}.ConvertToCloudServiceAccountURI(), 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.nodeTemplate = ""
|
||||
c.coordinatorTemplate = ""
|
||||
c.nodesInstanceGroup = ""
|
||||
c.coordinatorInstanceGroup = ""
|
||||
c.nodes = nil
|
||||
c.coordinators = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) TerminateServiceAccount(context.Context) error {
|
||||
c.serviceAccount = ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeGcpClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type stubGcpClient struct {
|
||||
terminateFirewallCalled bool
|
||||
terminateInstancesCalled bool
|
||||
terminateVPCsCalled bool
|
||||
terminateServiceAccountCalled bool
|
||||
closeCalled bool
|
||||
|
||||
getStateErr error
|
||||
setStateErr error
|
||||
createVPCsErr error
|
||||
createFirewallErr error
|
||||
createInstancesErr error
|
||||
createServiceAccountErr error
|
||||
terminateFirewallErr error
|
||||
terminateVPCsErr error
|
||||
terminateInstancesErr error
|
||||
terminateServiceAccountErr error
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func (c *stubGcpClient) GetState() (state.ConstellationState, error) {
|
||||
return state.ConstellationState{}, c.getStateErr
|
||||
}
|
||||
|
||||
func (c *stubGcpClient) SetState(state.ConstellationState) error {
|
||||
return c.setStateErr
|
||||
}
|
||||
|
||||
func (c *stubGcpClient) CreateVPCs(ctx context.Context, input gcpcl.VPCsInput) 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) CreateServiceAccount(ctx context.Context, input gcpcl.ServiceAccountInput) (string, error) {
|
||||
return gcpcl.ServiceAccountKey{}.ConvertToCloudServiceAccountURI(), c.createServiceAccountErr
|
||||
}
|
||||
|
||||
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) TerminateServiceAccount(context.Context) error {
|
||||
c.terminateServiceAccountCalled = true
|
||||
return c.terminateServiceAccountErr
|
||||
}
|
||||
|
||||
func (c *stubGcpClient) Close() error {
|
||||
c.closeCalled = true
|
||||
return c.closeErr
|
||||
}
|
123
cli/cloud/cloudcmd/create.go
Normal file
123
cli/cloud/cloudcmd/create.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package cloudcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
azurecl "github.com/edgelesssys/constellation/cli/azure/client"
|
||||
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/cli/gcp"
|
||||
"github.com/edgelesssys/constellation/cli/gcp/client"
|
||||
gcpcl "github.com/edgelesssys/constellation/cli/gcp/client"
|
||||
"github.com/edgelesssys/constellation/internal/config"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
)
|
||||
|
||||
// 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 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)
|
||||
},
|
||||
newAzureClient: func(subscriptionID, tenantID, name, location string) (azureclient, error) {
|
||||
return azurecl.NewInitialized(subscriptionID, tenantID, name, location)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates the handed amount of instances and all the needed resources.
|
||||
func (c *Creator) Create(ctx context.Context, provider cloudprovider.CloudProvider, config *config.Config, name, insType string, coordCount, nodeCount int,
|
||||
) (state.ConstellationState, error) {
|
||||
switch provider {
|
||||
case cloudprovider.GCP:
|
||||
cl, err := c.newGCPClient(
|
||||
ctx,
|
||||
*config.Provider.GCP.Project,
|
||||
*config.Provider.GCP.Zone,
|
||||
*config.Provider.GCP.Region,
|
||||
name,
|
||||
)
|
||||
if err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
defer cl.Close()
|
||||
return c.createGCP(ctx, cl, config, insType, coordCount, nodeCount)
|
||||
case cloudprovider.Azure:
|
||||
cl, err := c.newAzureClient(
|
||||
*config.Provider.Azure.SubscriptionID,
|
||||
*config.Provider.Azure.TenantID,
|
||||
name,
|
||||
*config.Provider.Azure.Location,
|
||||
)
|
||||
if err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
return c.createAzure(ctx, cl, config, insType, coordCount, nodeCount)
|
||||
default:
|
||||
return state.ConstellationState{}, fmt.Errorf("unsupported cloud provider: %s", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Creator) createGCP(ctx context.Context, cl gcpclient, config *config.Config, insType string, coordCount, nodeCount int,
|
||||
) (stat state.ConstellationState, retErr error) {
|
||||
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerGCP{client: cl})
|
||||
|
||||
if err := cl.CreateVPCs(ctx, *config.Provider.GCP.VPCsInput); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
if err := cl.CreateFirewall(ctx, *config.Provider.GCP.FirewallInput); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
|
||||
createInput := client.CreateInstancesInput{
|
||||
CountCoordinators: coordCount,
|
||||
CountNodes: nodeCount,
|
||||
ImageId: *config.Provider.GCP.Image,
|
||||
InstanceType: insType,
|
||||
StateDiskSizeGB: *config.StateDiskSizeGB,
|
||||
KubeEnv: gcp.KubeEnv,
|
||||
DisableCVM: *config.Provider.GCP.DisableCVM,
|
||||
}
|
||||
if err := cl.CreateInstances(ctx, createInput); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
|
||||
return cl.GetState()
|
||||
}
|
||||
|
||||
func (c *Creator) createAzure(ctx context.Context, cl azureclient, config *config.Config, insType string, coordCount, nodeCount int,
|
||||
) (stat state.ConstellationState, retErr error) {
|
||||
defer rollbackOnError(context.Background(), c.out, &retErr, &rollbackerAzure{client: cl})
|
||||
|
||||
if err := cl.CreateResourceGroup(ctx); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
if err := cl.CreateVirtualNetwork(ctx); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
if err := cl.CreateSecurityGroup(ctx, *config.Provider.Azure.NetworkSecurityGroupInput); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
createInput := azurecl.CreateInstancesInput{
|
||||
CountCoordinators: coordCount,
|
||||
CountNodes: nodeCount,
|
||||
InstanceType: insType,
|
||||
StateDiskSizeGB: *config.StateDiskSizeGB,
|
||||
Image: *config.Provider.Azure.Image,
|
||||
UserAssingedIdentity: *config.Provider.Azure.UserAssignedIdentity,
|
||||
}
|
||||
if err := cl.CreateInstances(ctx, createInput); err != nil {
|
||||
return state.ConstellationState{}, err
|
||||
}
|
||||
|
||||
return cl.GetState()
|
||||
}
|
187
cli/cloud/cloudcmd/create_test.go
Normal file
187
cli/cloud/cloudcmd/create_test.go
Normal file
|
@ -0,0 +1,187 @@
|
|||
package cloudcmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/cli/azure"
|
||||
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/cli/gcp"
|
||||
"github.com/edgelesssys/constellation/internal/config"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCreator(t *testing.T) {
|
||||
wantGCPState := state.ConstellationState{
|
||||
CloudProvider: cloudprovider.GCP.String(),
|
||||
GCPProject: "project",
|
||||
GCPCoordinators: gcp.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"},
|
||||
},
|
||||
GCPNodes: gcp.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"},
|
||||
},
|
||||
GCPNodeInstanceGroup: "nodes-group",
|
||||
GCPCoordinatorInstanceGroup: "coordinator-group",
|
||||
GCPNodeInstanceTemplate: "node-template",
|
||||
GCPCoordinatorInstanceTemplate: "coordinator-template",
|
||||
GCPNetwork: "network",
|
||||
GCPSubnetwork: "subnetwork",
|
||||
GCPFirewalls: []string{"coordinator", "wireguard", "ssh"},
|
||||
}
|
||||
|
||||
wantAzureState := state.ConstellationState{
|
||||
CloudProvider: cloudprovider.Azure.String(),
|
||||
AzureCoordinators: azure.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"},
|
||||
},
|
||||
AzureNodes: azure.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"},
|
||||
},
|
||||
AzureResourceGroup: "resource-group",
|
||||
AzureSubnet: "subnet",
|
||||
AzureNetworkSecurityGroup: "network-security-group",
|
||||
AzureNodesScaleSet: "nodes-scale-set",
|
||||
AzureCoordinatorsScaleSet: "coordinators-scale-set",
|
||||
}
|
||||
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
gcpclient gcpclient
|
||||
newGCPClientErr error
|
||||
azureclient azureclient
|
||||
newAzureClientErr error
|
||||
provider cloudprovider.CloudProvider
|
||||
config *config.Config
|
||||
wantState state.ConstellationState
|
||||
wantErr bool
|
||||
wantRollback bool // Use only together with stubClients.
|
||||
}{
|
||||
"gcp": {
|
||||
gcpclient: &fakeGcpClient{project: "project"},
|
||||
provider: cloudprovider.GCP,
|
||||
config: config.Default(),
|
||||
wantState: wantGCPState,
|
||||
},
|
||||
"gcp newGCPClient error": {
|
||||
newGCPClientErr: 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,
|
||||
},
|
||||
"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 CreateResourceGroup error": {
|
||||
azureclient: &stubAzureClient{createResourceGroupErr: someErr},
|
||||
provider: cloudprovider.Azure,
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
wantRollback: 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": {
|
||||
provider: cloudprovider.Unknown,
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
creator := &Creator{
|
||||
out: &bytes.Buffer{},
|
||||
newGCPClient: func(ctx context.Context, project, zone, region, name string) (gcpclient, error) {
|
||||
return tc.gcpclient, tc.newGCPClientErr
|
||||
},
|
||||
newAzureClient: func(subscriptionID, tenantID, name, location string) (azureclient, error) {
|
||||
return tc.azureclient, tc.newAzureClientErr
|
||||
},
|
||||
}
|
||||
|
||||
state, err := creator.Create(context.Background(), tc.provider, tc.config, "name", "type", 2, 3)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
if tc.wantRollback {
|
||||
switch tc.provider {
|
||||
case cloudprovider.GCP:
|
||||
cl := tc.gcpclient.(*stubGcpClient)
|
||||
assert.True(cl.terminateFirewallCalled)
|
||||
assert.True(cl.terminateInstancesCalled)
|
||||
assert.True(cl.terminateVPCsCalled)
|
||||
assert.True(cl.closeCalled)
|
||||
case cloudprovider.Azure:
|
||||
cl := tc.azureclient.(*stubAzureClient)
|
||||
assert.True(cl.terminateResourceGroupCalled)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.Equal(tc.wantState, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package cmd
|
||||
package cloudcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -47,14 +47,3 @@ type rollbackerAzure struct {
|
|||
func (r *rollbackerAzure) rollback(ctx context.Context) error {
|
||||
return r.client.TerminateResourceGroup(ctx)
|
||||
}
|
||||
|
||||
type rollbackerAWS struct {
|
||||
client ec2client
|
||||
}
|
||||
|
||||
func (r *rollbackerAWS) rollback(ctx context.Context) error {
|
||||
var err error
|
||||
err = multierr.Append(err, r.client.TerminateInstances(ctx))
|
||||
err = multierr.Append(err, r.client.DeleteSecurityGroup(ctx))
|
||||
return err
|
||||
}
|
||||
|
|
104
cli/cloud/cloudcmd/serviceaccount.go
Normal file
104
cli/cloud/cloudcmd/serviceaccount.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
package cloudcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
azurecl "github.com/edgelesssys/constellation/cli/azure/client"
|
||||
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
||||
gcpcl "github.com/edgelesssys/constellation/cli/gcp/client"
|
||||
"github.com/edgelesssys/constellation/internal/config"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
)
|
||||
|
||||
// ServieAccountCreator creates service accounts.
|
||||
type ServiceAccountCreator struct {
|
||||
newGCPClient func(ctx context.Context) (gcpclient, error)
|
||||
newAzureClient func(subscriptionID, tenantID string) (azureclient, error)
|
||||
}
|
||||
|
||||
func NewServiceAccountCreator() *ServiceAccountCreator {
|
||||
return &ServiceAccountCreator{
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return gcpcl.NewFromDefault(ctx)
|
||||
},
|
||||
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
|
||||
return azurecl.NewFromDefault(subscriptionID, tenantID)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new cloud provider service account with access to the created resources.
|
||||
func (c *ServiceAccountCreator) Create(ctx context.Context, stat state.ConstellationState, config *config.Config,
|
||||
) (string, state.ConstellationState, error) {
|
||||
provider := cloudprovider.FromString(stat.CloudProvider)
|
||||
switch provider {
|
||||
case cloudprovider.GCP:
|
||||
cl, err := c.newGCPClient(ctx)
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, err
|
||||
}
|
||||
defer cl.Close()
|
||||
|
||||
serviceAccount, stat, err := c.createServiceAccountGCP(ctx, cl, stat, config)
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, err
|
||||
}
|
||||
|
||||
return serviceAccount, stat, err
|
||||
case cloudprovider.Azure:
|
||||
cl, err := c.newAzureClient(stat.AzureSubscription, stat.AzureTenant)
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, err
|
||||
}
|
||||
|
||||
serviceAccount, stat, err := c.createServiceAccountAzure(ctx, cl, stat, config)
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, err
|
||||
}
|
||||
|
||||
return serviceAccount, stat, err
|
||||
default:
|
||||
return "", state.ConstellationState{}, fmt.Errorf("unsupported provider: %s", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ServiceAccountCreator) createServiceAccountGCP(ctx context.Context, cl gcpclient,
|
||||
stat state.ConstellationState, config *config.Config,
|
||||
) (string, state.ConstellationState, error) {
|
||||
if err := cl.SetState(stat); err != nil {
|
||||
return "", state.ConstellationState{}, fmt.Errorf("failed to set state while creating service account: %w", err)
|
||||
}
|
||||
|
||||
input := gcpcl.ServiceAccountInput{
|
||||
Roles: *config.Provider.GCP.ServiceAccountRoles,
|
||||
}
|
||||
serviceAccount, err := cl.CreateServiceAccount(ctx, input)
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, fmt.Errorf("failed to create service account: %w", err)
|
||||
}
|
||||
|
||||
stat, err = cl.GetState()
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, fmt.Errorf("failed to get state after creating service account: %w", err)
|
||||
}
|
||||
return serviceAccount, stat, nil
|
||||
}
|
||||
|
||||
func (c *ServiceAccountCreator) createServiceAccountAzure(ctx context.Context, cl azureclient,
|
||||
stat state.ConstellationState, config *config.Config,
|
||||
) (string, state.ConstellationState, error) {
|
||||
if err := cl.SetState(stat); err != nil {
|
||||
return "", state.ConstellationState{}, fmt.Errorf("failed to set state while creating service account: %w", err)
|
||||
}
|
||||
serviceAccount, err := cl.CreateServicePrincipal(ctx)
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, fmt.Errorf("failed to create service account: %w", err)
|
||||
}
|
||||
|
||||
stat, err = cl.GetState()
|
||||
if err != nil {
|
||||
return "", state.ConstellationState{}, fmt.Errorf("failed to get state after creating service account: %w", err)
|
||||
}
|
||||
return serviceAccount, stat, nil
|
||||
}
|
157
cli/cloud/cloudcmd/serviceaccount_test.go
Normal file
157
cli/cloud/cloudcmd/serviceaccount_test.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package cloudcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/cli/gcp"
|
||||
"github.com/edgelesssys/constellation/internal/config"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestServiceAccountCreator(t *testing.T) {
|
||||
someGCPState := func() state.ConstellationState {
|
||||
return state.ConstellationState{
|
||||
CloudProvider: cloudprovider.GCP.String(),
|
||||
GCPProject: "project",
|
||||
GCPNodes: gcp.Instances{},
|
||||
GCPCoordinators: gcp.Instances{},
|
||||
GCPNodeInstanceGroup: "nodes-group",
|
||||
GCPCoordinatorInstanceGroup: "coord-group",
|
||||
GCPNodeInstanceTemplate: "template",
|
||||
GCPCoordinatorInstanceTemplate: "template",
|
||||
GCPNetwork: "network",
|
||||
GCPFirewalls: []string{},
|
||||
}
|
||||
}
|
||||
someAzureState := func() state.ConstellationState {
|
||||
return state.ConstellationState{
|
||||
CloudProvider: cloudprovider.Azure.String(),
|
||||
}
|
||||
}
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
newGCPClient func(ctx context.Context) (gcpclient, error)
|
||||
newAzureClient func(subscriptionID, tenantID string) (azureclient, error)
|
||||
state state.ConstellationState
|
||||
config *config.Config
|
||||
wantErr bool
|
||||
wantStateMutator func(*state.ConstellationState)
|
||||
}{
|
||||
"gcp": {
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return &fakeGcpClient{}, nil
|
||||
},
|
||||
state: someGCPState(),
|
||||
config: config.Default(),
|
||||
wantStateMutator: func(stat *state.ConstellationState) {
|
||||
stat.GCPServiceAccount = "service-account@project.iam.gserviceaccount.com"
|
||||
},
|
||||
},
|
||||
"gcp newGCPClient error": {
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return nil, someErr
|
||||
},
|
||||
state: someGCPState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp client setState error": {
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return &stubGcpClient{setStateErr: someErr}, nil
|
||||
},
|
||||
state: someGCPState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp client createServiceAccount error": {
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return &stubGcpClient{createServiceAccountErr: someErr}, nil
|
||||
},
|
||||
state: someGCPState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp client getState error": {
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return &stubGcpClient{getStateErr: someErr}, nil
|
||||
},
|
||||
state: someGCPState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure": {
|
||||
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
|
||||
return &fakeAzureClient{}, nil
|
||||
},
|
||||
state: someAzureState(),
|
||||
config: config.Default(),
|
||||
wantStateMutator: func(stat *state.ConstellationState) {
|
||||
stat.AzureADAppObjectID = "00000000-0000-0000-0000-000000000001"
|
||||
},
|
||||
},
|
||||
"azure newAzureClient error": {
|
||||
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
|
||||
return nil, someErr
|
||||
},
|
||||
state: someAzureState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure client setState error": {
|
||||
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
|
||||
return &stubAzureClient{setStateErr: someErr}, nil
|
||||
},
|
||||
state: someAzureState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure client createServiceAccount error": {
|
||||
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
|
||||
return &stubAzureClient{createServicePrincipalErr: someErr}, nil
|
||||
},
|
||||
state: someAzureState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure client getState error": {
|
||||
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
|
||||
return &stubAzureClient{getStateErr: someErr}, nil
|
||||
},
|
||||
state: someAzureState(),
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
"unknown cloud provider": {
|
||||
state: state.ConstellationState{},
|
||||
config: config.Default(),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
creator := &ServiceAccountCreator{
|
||||
newGCPClient: tc.newGCPClient,
|
||||
newAzureClient: tc.newAzureClient,
|
||||
}
|
||||
|
||||
serviceAccount, state, err := creator.Create(context.Background(), tc.state, tc.config)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
assert.NoError(err)
|
||||
assert.NotEmpty(serviceAccount)
|
||||
tc.wantStateMutator(&tc.state)
|
||||
assert.Equal(tc.state, state)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
79
cli/cloud/cloudcmd/terminate.go
Normal file
79
cli/cloud/cloudcmd/terminate.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package cloudcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
azurecl "github.com/edgelesssys/constellation/cli/azure/client"
|
||||
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
||||
gcpcl "github.com/edgelesssys/constellation/cli/gcp/client"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
)
|
||||
|
||||
// Terminator deletes cloud provider resources.
|
||||
type Terminator struct {
|
||||
newGCPClient func(ctx context.Context) (gcpclient, 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)
|
||||
},
|
||||
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
|
||||
return azurecl.NewFromDefault(subscriptionID, tenantID)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Terminate deletes the could provider resources defined in the constellation state.
|
||||
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)
|
||||
default:
|
||||
return fmt.Errorf("unsupported provider: %s", provider)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Terminator) terminateGCP(ctx context.Context, cl gcpclient, state state.ConstellationState) error {
|
||||
if err := cl.SetState(state); 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 cl.TerminateServiceAccount(ctx)
|
||||
}
|
||||
|
||||
func (t *Terminator) terminateAzure(ctx context.Context, cl azureclient, state state.ConstellationState) error {
|
||||
if err := cl.SetState(state); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := cl.TerminateServicePrincipal(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return cl.TerminateResourceGroup(ctx)
|
||||
}
|
159
cli/cloud/cloudcmd/terminate_test.go
Normal file
159
cli/cloud/cloudcmd/terminate_test.go
Normal file
|
@ -0,0 +1,159 @@
|
|||
package cloudcmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/edgelesssys/constellation/cli/azure"
|
||||
"github.com/edgelesssys/constellation/cli/cloudprovider"
|
||||
"github.com/edgelesssys/constellation/cli/gcp"
|
||||
"github.com/edgelesssys/constellation/internal/state"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTerminator(t *testing.T) {
|
||||
someGCPState := func() state.ConstellationState {
|
||||
return state.ConstellationState{
|
||||
CloudProvider: cloudprovider.GCP.String(),
|
||||
GCPProject: "project",
|
||||
GCPNodes: gcp.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"},
|
||||
},
|
||||
GCPCoordinators: gcp.Instances{
|
||||
"id-c": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
|
||||
},
|
||||
GCPNodeInstanceGroup: "nodes-group",
|
||||
GCPCoordinatorInstanceGroup: "coord-group",
|
||||
GCPNodeInstanceTemplate: "template",
|
||||
GCPCoordinatorInstanceTemplate: "template",
|
||||
GCPNetwork: "network",
|
||||
GCPFirewalls: []string{"a", "b", "c"},
|
||||
GCPServiceAccount: "service-account@project.iam.gserviceaccount.com",
|
||||
}
|
||||
}
|
||||
someAzureState := func() state.ConstellationState {
|
||||
return state.ConstellationState{
|
||||
CloudProvider: cloudprovider.Azure.String(),
|
||||
AzureNodes: azure.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"},
|
||||
},
|
||||
AzureCoordinators: azure.Instances{
|
||||
"id-c": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
|
||||
},
|
||||
AzureResourceGroup: "group",
|
||||
AzureADAppObjectID: "00000000-0000-0000-0000-000000000001",
|
||||
}
|
||||
}
|
||||
someErr := errors.New("failed")
|
||||
|
||||
testCases := map[string]struct {
|
||||
gcpclient gcpclient
|
||||
newGCPClientErr error
|
||||
azureclient azureclient
|
||||
newAzureClientErr error
|
||||
state state.ConstellationState
|
||||
wantErr bool
|
||||
}{
|
||||
"gcp": {
|
||||
gcpclient: &stubGcpClient{},
|
||||
state: someGCPState(),
|
||||
},
|
||||
"gcp newGCPClient error": {
|
||||
newGCPClientErr: someErr,
|
||||
state: someGCPState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp setState error": {
|
||||
gcpclient: &stubGcpClient{setStateErr: someErr},
|
||||
state: someGCPState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp terminateInstances error": {
|
||||
gcpclient: &stubGcpClient{terminateInstancesErr: someErr},
|
||||
state: someGCPState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp terminateFirewall error": {
|
||||
gcpclient: &stubGcpClient{terminateFirewallErr: someErr},
|
||||
state: someGCPState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp terminateVPCs error": {
|
||||
gcpclient: &stubGcpClient{terminateVPCsErr: someErr},
|
||||
state: someGCPState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"gcp terminateServiceAccount error": {
|
||||
gcpclient: &stubGcpClient{terminateServiceAccountErr: someErr},
|
||||
state: someGCPState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure": {
|
||||
azureclient: &stubAzureClient{},
|
||||
state: someAzureState(),
|
||||
},
|
||||
"azure newAzureClient error": {
|
||||
newAzureClientErr: someErr,
|
||||
state: someAzureState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure setState error": {
|
||||
azureclient: &stubAzureClient{setStateErr: someErr},
|
||||
state: someAzureState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure terminateServicePrincipal error": {
|
||||
azureclient: &stubAzureClient{terminateServicePrincipalErr: someErr},
|
||||
state: someAzureState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"azure terminateResourceGroup error": {
|
||||
azureclient: &stubAzureClient{terminateResourceGroupErr: someErr},
|
||||
state: someAzureState(),
|
||||
wantErr: true,
|
||||
},
|
||||
"unknown cloud provider": {
|
||||
state: state.ConstellationState{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
terminator := &Terminator{
|
||||
newGCPClient: func(ctx context.Context) (gcpclient, error) {
|
||||
return tc.gcpclient, tc.newGCPClientErr
|
||||
},
|
||||
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
|
||||
return tc.azureclient, tc.newAzureClientErr
|
||||
},
|
||||
}
|
||||
|
||||
err := terminator.Terminate(context.Background(), tc.state)
|
||||
|
||||
if tc.wantErr {
|
||||
assert.Error(err)
|
||||
} else {
|
||||
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.terminateServiceAccountCalled)
|
||||
assert.True(cl.closeCalled)
|
||||
case cloudprovider.Azure:
|
||||
cl := tc.azureclient.(*stubAzureClient)
|
||||
assert.True(cl.terminateResourceGroupCalled)
|
||||
assert.True(cl.terminateServicePrincipalCalled)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue