Manually manage resource group on Azure

This commit is contained in:
katexochen 2022-08-25 15:12:08 +02:00 committed by Paul Meyer
parent e6ae54a25a
commit f15605cb45
25 changed files with 403 additions and 1162 deletions

View file

@ -26,12 +26,9 @@ type azureclient interface {
GetState() state.ConstellationState
SetState(state.ConstellationState)
CreateApplicationInsight(ctx context.Context) error
CreateResourceGroup(ctx context.Context) error
CreateExternalLoadBalancer(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
CreateServicePrincipal(ctx context.Context) (string, error)
TerminateResourceGroup(ctx context.Context) error
TerminateServicePrincipal(ctx context.Context) error
TerminateResourceGroupResources(ctx context.Context) error
}

View file

@ -79,11 +79,6 @@ func (c *fakeAzureClient) CreateApplicationInsight(ctx context.Context) error {
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
@ -123,17 +118,8 @@ func (c *fakeAzureClient) CreateServicePrincipal(ctx context.Context) (string, e
}.ToCloudServiceAccountURI(), nil
}
func (c *fakeAzureClient) TerminateResourceGroup(ctx context.Context) error {
if c.resourceGroup == "" {
return nil
}
c.workers = nil
c.controlPlanes = nil
c.resourceGroup = ""
c.subnetID = ""
c.networkSecurityGroup = ""
c.workerScaleSet = ""
c.controlPlaneScaleSet = ""
func (c *fakeAzureClient) TerminateResourceGroupResources(ctx context.Context) error {
// TODO(katexochen)
return nil
}
@ -146,18 +132,17 @@ func (c *fakeAzureClient) TerminateServicePrincipal(ctx context.Context) error {
}
type stubAzureClient struct {
terminateResourceGroupCalled bool
terminateServicePrincipalCalled bool
terminateResourceGroupResourcesCalled bool
terminateServicePrincipalCalled bool
createApplicationInsightErr error
createResourceGroupErr error
createVirtualNetworkErr error
createSecurityGroupErr error
createLoadBalancerErr error
createInstancesErr error
createServicePrincipalErr error
terminateResourceGroupErr error
terminateServicePrincipalErr error
createApplicationInsightErr error
createVirtualNetworkErr error
createSecurityGroupErr error
createLoadBalancerErr error
createInstancesErr error
createServicePrincipalErr error
terminateResourceGroupResourcesErr error
terminateServicePrincipalErr error
}
func (c *stubAzureClient) GetState() state.ConstellationState {
@ -175,10 +160,6 @@ func (c *stubAzureClient) CreateApplicationInsight(ctx context.Context) error {
return c.createApplicationInsightErr
}
func (c *stubAzureClient) CreateResourceGroup(ctx context.Context) error {
return c.createResourceGroupErr
}
func (c *stubAzureClient) CreateVirtualNetwork(ctx context.Context) error {
return c.createVirtualNetworkErr
}
@ -198,9 +179,9 @@ func (c *stubAzureClient) CreateServicePrincipal(ctx context.Context) (string, e
}.ToCloudServiceAccountURI(), c.createServicePrincipalErr
}
func (c *stubAzureClient) TerminateResourceGroup(ctx context.Context) error {
c.terminateResourceGroupCalled = true
return c.terminateResourceGroupErr
func (c *stubAzureClient) TerminateResourceGroupResources(ctx context.Context) error {
c.terminateResourceGroupResourcesCalled = true
return c.terminateResourceGroupResourcesErr
}
func (c *stubAzureClient) TerminateServicePrincipal(ctx context.Context) error {

View file

@ -18,7 +18,7 @@ import (
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)
newAzureClient func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error)
}
// NewCreator creates a new creator.
@ -28,8 +28,8 @@ func NewCreator(out io.Writer) *Creator {
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)
newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) {
return azurecl.NewInitialized(subscriptionID, tenantID, name, location, resourceGroup)
},
}
}
@ -57,6 +57,7 @@ func (c *Creator) Create(ctx context.Context, provider cloudprovider.Provider, c
config.Provider.Azure.TenantID,
name,
config.Provider.Azure.Location,
config.Provider.Azure.ResourceGroup,
)
if err != nil {
return state.ConstellationState{}, err
@ -144,9 +145,6 @@ func (c *Creator) createAzure(ctx context.Context, cl azureclient, config *confi
) (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.CreateApplicationInsight(ctx); err != nil {
return state.ConstellationState{}, err
}

View file

@ -51,7 +51,6 @@ func TestCreator(t *testing.T) {
"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",
AzureWorkerScaleSet: "workers-scale-set",
@ -123,13 +122,6 @@ func TestCreator(t *testing.T) {
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,
@ -167,7 +159,7 @@ func TestCreator(t *testing.T) {
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) {
newAzureClient: func(subscriptionID, tenantID, name, location, resourceGroup string) (azureclient, error) {
return tc.azureclient, tc.newAzureClientErr
},
}
@ -186,7 +178,7 @@ func TestCreator(t *testing.T) {
assert.True(cl.closeCalled)
case cloudprovider.Azure:
cl := tc.azureclient.(*stubAzureClient)
assert.True(cl.terminateResourceGroupCalled)
assert.True(cl.terminateResourceGroupResourcesCalled)
}
}
} else {

View file

@ -46,5 +46,5 @@ type rollbackerAzure struct {
}
func (r *rollbackerAzure) rollback(ctx context.Context) error {
return r.client.TerminateResourceGroup(ctx)
return r.client.TerminateResourceGroupResources(ctx)
}

View file

@ -1,68 +0,0 @@
package cloudcmd
import (
"context"
"fmt"
azurecl "github.com/edgelesssys/constellation/cli/internal/azure/client"
gcpcl "github.com/edgelesssys/constellation/cli/internal/gcp/client"
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/internal/config"
"github.com/edgelesssys/constellation/internal/state"
)
// ServiceAccountCreator 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:
return "", state.ConstellationState{}, fmt.Errorf("creating service account not supported for GCP")
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
case cloudprovider.QEMU:
return "unsupported://qemu", stat, nil
default:
return "", state.ConstellationState{}, fmt.Errorf("unsupported provider: %s", provider)
}
}
func (c *ServiceAccountCreator) createServiceAccountAzure(ctx context.Context, cl azureclient,
stat state.ConstellationState, _ *config.Config,
) (string, state.ConstellationState, error) {
cl.SetState(stat)
serviceAccount, err := cl.CreateServicePrincipal(ctx)
if err != nil {
return "", state.ConstellationState{}, fmt.Errorf("creating service account: %w", err)
}
return serviceAccount, cl.GetState(), nil
}

View file

@ -1,89 +0,0 @@
package cloudcmd
import (
"context"
"errors"
"testing"
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
"github.com/edgelesssys/constellation/internal/config"
"github.com/edgelesssys/constellation/internal/state"
"github.com/stretchr/testify/assert"
)
func TestServiceAccountCreator(t *testing.T) {
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)
}{
"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 createServiceAccount error": {
newAzureClient: func(subscriptionID, tenantID string) (azureclient, error) {
return &stubAzureClient{createServicePrincipalErr: someErr}, nil
},
state: someAzureState(),
config: config.Default(),
wantErr: true,
},
"qemu": {
state: state.ConstellationState{CloudProvider: "qemu"},
wantStateMutator: func(cs *state.ConstellationState) {},
config: config.Default(),
},
"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)
}
})
}
}

View file

@ -72,8 +72,5 @@ func (t *Terminator) terminateGCP(ctx context.Context, cl gcpclient, state state
func (t *Terminator) terminateAzure(ctx context.Context, cl azureclient, state state.ConstellationState) error {
cl.SetState(state)
if err := cl.TerminateServicePrincipal(ctx); err != nil {
return err
}
return cl.TerminateResourceGroup(ctx)
return cl.TerminateResourceGroupResources(ctx)
}

View file

@ -41,7 +41,6 @@ func TestTerminator(t *testing.T) {
AzureControlPlaneInstances: cloudtypes.Instances{
"id-c": {PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1"},
},
AzureResourceGroup: "group",
AzureADAppObjectID: "00000000-0000-0000-0000-000000000001",
}
}
@ -88,13 +87,8 @@ func TestTerminator(t *testing.T) {
state: someAzureState(),
wantErr: true,
},
"azure terminateServicePrincipal error": {
azureclient: &stubAzureClient{terminateServicePrincipalErr: someErr},
state: someAzureState(),
wantErr: true,
},
"azure terminateResourceGroup error": {
azureclient: &stubAzureClient{terminateResourceGroupErr: someErr},
"azure terminateResourceGroupResources error": {
azureclient: &stubAzureClient{terminateResourceGroupResourcesErr: someErr},
state: someAzureState(),
wantErr: true,
},
@ -132,8 +126,7 @@ func TestTerminator(t *testing.T) {
assert.True(cl.closeCalled)
case cloudprovider.Azure:
cl := tc.azureclient.(*stubAzureClient)
assert.True(cl.terminateResourceGroupCalled)
assert.True(cl.terminateServicePrincipalCalled)
assert.True(cl.terminateResourceGroupResourcesCalled)
}
}
})