mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-08-06 05:54:28 -04:00
Remove cli/ec2
This commit is contained in:
parent
064151a956
commit
6a9419e89c
19 changed files with 6 additions and 1804 deletions
|
@ -397,8 +397,6 @@ func readOrGeneratedMasterSecret(w io.Writer, fileHandler file.Handler, filename
|
||||||
|
|
||||||
func getScalingGroupsFromConfig(stat state.ConstellationState, config *config.Config) (coordinators, nodes ScalingGroup, err error) {
|
func getScalingGroupsFromConfig(stat state.ConstellationState, config *config.Config) (coordinators, nodes ScalingGroup, err error) {
|
||||||
switch {
|
switch {
|
||||||
case len(stat.EC2Instances) != 0:
|
|
||||||
return getAWSInstances(stat)
|
|
||||||
case len(stat.GCPCoordinators) != 0:
|
case len(stat.GCPCoordinators) != 0:
|
||||||
return getGCPInstances(stat, config)
|
return getGCPInstances(stat, config)
|
||||||
case len(stat.AzureCoordinators) != 0:
|
case len(stat.AzureCoordinators) != 0:
|
||||||
|
@ -410,39 +408,6 @@ func getScalingGroupsFromConfig(stat state.ConstellationState, config *config.Co
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAWSInstances(stat state.ConstellationState) (coordinators, nodes ScalingGroup, err error) {
|
|
||||||
coordinatorID, _, err := stat.EC2Instances.GetOne()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
coordinatorMap := stat.EC2Instances
|
|
||||||
var coordinatorInstances Instances
|
|
||||||
for _, node := range coordinatorMap {
|
|
||||||
coordinatorInstances = append(coordinatorInstances, Instance(node))
|
|
||||||
}
|
|
||||||
// GroupID of coordinators is empty, since they currently do not scale.
|
|
||||||
coordinators = ScalingGroup{
|
|
||||||
Instances: coordinatorInstances,
|
|
||||||
GroupID: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeMap := stat.EC2Instances.GetOthers(coordinatorID)
|
|
||||||
if len(nodeMap) == 0 {
|
|
||||||
return ScalingGroup{}, ScalingGroup{}, errors.New("no worker nodes available, can't create Constellation cluster with one instance")
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeInstances Instances
|
|
||||||
for _, node := range nodeMap {
|
|
||||||
nodeInstances = append(nodeInstances, Instance(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make min / max configurable and abstract autoscaling for different cloud providers
|
|
||||||
// TODO: GroupID of workers is empty, since they currently do not scale.
|
|
||||||
nodes = ScalingGroup{Instances: nodeInstances, GroupID: ""}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGCPInstances(stat state.ConstellationState, config *config.Config) (coordinators, nodes ScalingGroup, err error) {
|
func getGCPInstances(stat state.ConstellationState, config *config.Config) (coordinators, nodes ScalingGroup, err error) {
|
||||||
coordinatorMap := stat.GCPCoordinators
|
coordinatorMap := stat.GCPCoordinators
|
||||||
if len(coordinatorMap) == 0 {
|
if len(coordinatorMap) == 0 {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/cli/cloud/cloudtypes"
|
"github.com/edgelesssys/constellation/cli/cloud/cloudtypes"
|
||||||
"github.com/edgelesssys/constellation/cli/ec2"
|
|
||||||
"github.com/edgelesssys/constellation/internal/constants"
|
"github.com/edgelesssys/constellation/internal/constants"
|
||||||
"github.com/edgelesssys/constellation/internal/file"
|
"github.com/edgelesssys/constellation/internal/file"
|
||||||
"github.com/edgelesssys/constellation/internal/state"
|
"github.com/edgelesssys/constellation/internal/state"
|
||||||
|
@ -167,15 +166,12 @@ func TestInitialize(t *testing.T) {
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
"no instances to pick one": {
|
"no instances to pick one": {
|
||||||
existingState: state.ConstellationState{
|
existingState: state.ConstellationState{GCPNodes: cloudtypes.Instances{}},
|
||||||
EC2Instances: ec2.Instances{},
|
client: &stubProtoClient{},
|
||||||
EC2SecurityGroup: "sg-test",
|
waiter: &stubStatusWaiter{},
|
||||||
},
|
privKey: testKey,
|
||||||
client: &stubProtoClient{},
|
vpnHandler: &stubVPNHandler{},
|
||||||
waiter: &stubStatusWaiter{},
|
wantErr: true,
|
||||||
privKey: testKey,
|
|
||||||
vpnHandler: &stubVPNHandler{},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
},
|
||||||
"public key to short": {
|
"public key to short": {
|
||||||
existingState: testGcpState,
|
existingState: testGcpState,
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// api collects used functions of AWS' ec2.Client as interfaces to enable testing.
|
|
||||||
type api interface {
|
|
||||||
ec2.DescribeInstancesAPIClient
|
|
||||||
|
|
||||||
// Instances
|
|
||||||
RunInstances(ctx context.Context,
|
|
||||||
params *ec2.RunInstancesInput,
|
|
||||||
optFns ...func(*ec2.Options)) (*ec2.RunInstancesOutput, error)
|
|
||||||
|
|
||||||
TerminateInstances(ctx context.Context,
|
|
||||||
params *ec2.TerminateInstancesInput,
|
|
||||||
optFns ...func(*ec2.Options)) (*ec2.TerminateInstancesOutput, error)
|
|
||||||
|
|
||||||
CreateTags(ctx context.Context,
|
|
||||||
params *ec2.CreateTagsInput,
|
|
||||||
optFns ...func(*ec2.Options)) (*ec2.CreateTagsOutput, error)
|
|
||||||
|
|
||||||
// SecurityGroup
|
|
||||||
CreateSecurityGroup(ctx context.Context,
|
|
||||||
params *ec2.CreateSecurityGroupInput,
|
|
||||||
optFns ...func(*ec2.Options)) (*ec2.CreateSecurityGroupOutput, error)
|
|
||||||
|
|
||||||
DeleteSecurityGroup(ctx context.Context,
|
|
||||||
params *ec2.DeleteSecurityGroupInput,
|
|
||||||
optFns ...func(*ec2.Options)) (*ec2.DeleteSecurityGroupOutput, error)
|
|
||||||
|
|
||||||
AuthorizeSecurityGroupIngress(ctx context.Context,
|
|
||||||
params *ec2.AuthorizeSecurityGroupIngressInput,
|
|
||||||
optFns ...func(*ec2.Options)) (*ec2.AuthorizeSecurityGroupIngressOutput, error)
|
|
||||||
|
|
||||||
AuthorizeSecurityGroupEgress(ctx context.Context,
|
|
||||||
params *ec2.AuthorizeSecurityGroupEgressInput,
|
|
||||||
optFns ...func(*ec2.Options)) (*ec2.AuthorizeSecurityGroupEgressOutput, error)
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/ec2"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
|
||||||
"github.com/aws/smithy-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// stubAPI is a stub ec2 api for testing.
|
|
||||||
type stubAPI struct {
|
|
||||||
instances []types.Instance
|
|
||||||
securityGroup types.SecurityGroup
|
|
||||||
|
|
||||||
describeInstancesErr error
|
|
||||||
runInstancesErr error
|
|
||||||
runInstancesDryRunErr *error
|
|
||||||
terminateInstancesErr error
|
|
||||||
terminateInstancesDryRunErr *error
|
|
||||||
createTagsErr error
|
|
||||||
createSecurityGroupErr error
|
|
||||||
createSecurityGroupDryRunErr *error
|
|
||||||
deleteSecurityGroupErr error
|
|
||||||
deleteSecurityGroupDryRunErr *error
|
|
||||||
authorizeSecurityGroupIngressErr error
|
|
||||||
authorizeSecurityGroupIngressDryRunErr *error
|
|
||||||
authorizeSecurityGroupEgressErr error
|
|
||||||
authorizeSecurityGroupEgressDryRunErr *error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a stubAPI) DescribeInstances(ctx context.Context,
|
|
||||||
params *ec2.DescribeInstancesInput,
|
|
||||||
optFns ...func(*ec2.Options),
|
|
||||||
) (*ec2.DescribeInstancesOutput, error) {
|
|
||||||
return &ec2.DescribeInstancesOutput{
|
|
||||||
Reservations: []types.Reservation{
|
|
||||||
{Instances: a.instances},
|
|
||||||
},
|
|
||||||
}, a.describeInstancesErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a stubAPI) RunInstances(ctx context.Context,
|
|
||||||
params *ec2.RunInstancesInput,
|
|
||||||
optFns ...func(*ec2.Options),
|
|
||||||
) (*ec2.RunInstancesOutput, error) {
|
|
||||||
if err := getDryRunErr(params.DryRun, a.runInstancesDryRunErr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ec2.RunInstancesOutput{Instances: a.instances}, a.runInstancesErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a stubAPI) CreateTags(ctx context.Context,
|
|
||||||
params *ec2.CreateTagsInput,
|
|
||||||
optFns ...func(*ec2.Options),
|
|
||||||
) (*ec2.CreateTagsOutput, error) {
|
|
||||||
return nil, a.createTagsErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a stubAPI) TerminateInstances(ctx context.Context,
|
|
||||||
params *ec2.TerminateInstancesInput,
|
|
||||||
optFns ...func(*ec2.Options),
|
|
||||||
) (*ec2.TerminateInstancesOutput, error) {
|
|
||||||
if err := getDryRunErr(params.DryRun, a.terminateInstancesDryRunErr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, a.terminateInstancesErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a stubAPI) CreateSecurityGroup(ctx context.Context,
|
|
||||||
params *ec2.CreateSecurityGroupInput,
|
|
||||||
optFns ...func(*ec2.Options),
|
|
||||||
) (*ec2.CreateSecurityGroupOutput, error) {
|
|
||||||
if err := getDryRunErr(params.DryRun, a.createSecurityGroupDryRunErr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ec2.CreateSecurityGroupOutput{
|
|
||||||
GroupId: a.securityGroup.GroupId,
|
|
||||||
}, a.createSecurityGroupErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a stubAPI) DeleteSecurityGroup(ctx context.Context,
|
|
||||||
params *ec2.DeleteSecurityGroupInput,
|
|
||||||
optFns ...func(*ec2.Options),
|
|
||||||
) (*ec2.DeleteSecurityGroupOutput, error) {
|
|
||||||
if err := getDryRunErr(params.DryRun, a.deleteSecurityGroupDryRunErr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, a.deleteSecurityGroupErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a stubAPI) AuthorizeSecurityGroupIngress(ctx context.Context,
|
|
||||||
params *ec2.AuthorizeSecurityGroupIngressInput,
|
|
||||||
optFns ...func(*ec2.Options),
|
|
||||||
) (*ec2.AuthorizeSecurityGroupIngressOutput, error) {
|
|
||||||
if err := getDryRunErr(params.DryRun, a.authorizeSecurityGroupIngressDryRunErr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, a.authorizeSecurityGroupIngressErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a stubAPI) AuthorizeSecurityGroupEgress(ctx context.Context,
|
|
||||||
params *ec2.AuthorizeSecurityGroupEgressInput,
|
|
||||||
optFns ...func(*ec2.Options),
|
|
||||||
) (*ec2.AuthorizeSecurityGroupEgressOutput, error) {
|
|
||||||
if err := getDryRunErr(params.DryRun, a.authorizeSecurityGroupEgressDryRunErr); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, a.authorizeSecurityGroupEgressErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDryRunErr(dryRun *bool, stubErr *error) error {
|
|
||||||
if dryRun == nil || !*dryRun {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if stubErr != nil {
|
|
||||||
return *stubErr
|
|
||||||
}
|
|
||||||
return &smithy.GenericAPIError{Code: "DryRunOperation"}
|
|
||||||
}
|
|
||||||
|
|
||||||
var stateRunning = types.InstanceState{
|
|
||||||
Code: aws.Int32(int32(16)),
|
|
||||||
Name: types.InstanceStateNameRunning,
|
|
||||||
}
|
|
||||||
|
|
||||||
var stateTerminated = types.InstanceState{
|
|
||||||
Code: aws.Int32(48),
|
|
||||||
Name: types.InstanceStateNameTerminated,
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
awsconfig "github.com/aws/aws-sdk-go-v2/config"
|
|
||||||
awsec2 "github.com/aws/aws-sdk-go-v2/service/ec2"
|
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/cli/ec2"
|
|
||||||
"github.com/edgelesssys/constellation/internal/cloud/cloudprovider"
|
|
||||||
"github.com/edgelesssys/constellation/internal/state"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Client for the AWS EC2 API.
|
|
||||||
type Client struct {
|
|
||||||
api api
|
|
||||||
instances ec2.Instances
|
|
||||||
securityGroup string
|
|
||||||
timeout time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func newClient(api api) (*Client, error) {
|
|
||||||
return &Client{
|
|
||||||
api: api,
|
|
||||||
instances: make(map[string]ec2.Instance),
|
|
||||||
timeout: 2 * time.Minute,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFromDefault creates a Client from the default config.
|
|
||||||
func NewFromDefault(ctx context.Context) (*Client, error) {
|
|
||||||
cfg, err := awsconfig.LoadDefaultConfig(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newClient(awsec2.NewFromConfig(cfg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetState returns the current configuration of the Constellation,
|
|
||||||
// which can be stored and used through later CLI commands.
|
|
||||||
func (c *Client) GetState() (state.ConstellationState, error) {
|
|
||||||
if len(c.instances) == 0 {
|
|
||||||
return state.ConstellationState{}, errors.New("client has no instances")
|
|
||||||
}
|
|
||||||
if c.securityGroup == "" {
|
|
||||||
return state.ConstellationState{}, errors.New("client has no security group")
|
|
||||||
}
|
|
||||||
return state.ConstellationState{
|
|
||||||
CloudProvider: cloudprovider.AWS.String(),
|
|
||||||
EC2Instances: c.instances,
|
|
||||||
EC2SecurityGroup: c.securityGroup,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetState sets a Client to an existing configuration.
|
|
||||||
func (c *Client) SetState(stat state.ConstellationState) error {
|
|
||||||
if stat.CloudProvider != cloudprovider.AWS.String() {
|
|
||||||
return errors.New("state is not aws state")
|
|
||||||
}
|
|
||||||
if len(stat.EC2Instances) == 0 {
|
|
||||||
return errors.New("state has no instances")
|
|
||||||
}
|
|
||||||
if stat.EC2SecurityGroup == "" {
|
|
||||||
return errors.New("state has no security group")
|
|
||||||
}
|
|
||||||
c.instances = stat.EC2Instances
|
|
||||||
c.securityGroup = stat.EC2SecurityGroup
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/cli/ec2"
|
|
||||||
"github.com/edgelesssys/constellation/internal/state"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetState(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
client Client
|
|
||||||
wantState state.ConstellationState
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"successful get": {
|
|
||||||
client: Client{
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
securityGroup: "sg",
|
|
||||||
},
|
|
||||||
wantState: state.ConstellationState{
|
|
||||||
CloudProvider: "AWS",
|
|
||||||
EC2Instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
EC2SecurityGroup: "sg",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"client without security group": {
|
|
||||||
client: Client{
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"client without instances": {
|
|
||||||
client: Client{
|
|
||||||
securityGroup: "sg",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
stat, err := tc.client.GetState()
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(tc.wantState, stat)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSetState(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
state state.ConstellationState
|
|
||||||
wantInstances ec2.Instances
|
|
||||||
wantSecurityGroup string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"successful set": {
|
|
||||||
state: state.ConstellationState{
|
|
||||||
CloudProvider: "AWS",
|
|
||||||
EC2SecurityGroup: "sg-test",
|
|
||||||
EC2Instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
},
|
|
||||||
wantInstances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantSecurityGroup: "sg-test",
|
|
||||||
},
|
|
||||||
"state without cloudprovider": {
|
|
||||||
state: state.ConstellationState{
|
|
||||||
EC2SecurityGroup: "sg-test",
|
|
||||||
EC2Instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"state with incorrect cloudprovider": {
|
|
||||||
state: state.ConstellationState{
|
|
||||||
CloudProvider: "incorrect",
|
|
||||||
EC2SecurityGroup: "sg-test",
|
|
||||||
EC2Instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"state without instances": {
|
|
||||||
state: state.ConstellationState{
|
|
||||||
CloudProvider: "AWS",
|
|
||||||
EC2SecurityGroup: "sg-test",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"state without security group": {
|
|
||||||
state: state.ConstellationState{
|
|
||||||
CloudProvider: "AWS",
|
|
||||||
EC2Instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
client := &Client{}
|
|
||||||
|
|
||||||
err := client.SetState(tc.state)
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(tc.wantInstances, client.instances)
|
|
||||||
assert.Equal(tc.wantSecurityGroup, client.securityGroup)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,199 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
|
||||||
awsec2 "github.com/aws/aws-sdk-go-v2/service/ec2"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
|
||||||
"github.com/edgelesssys/constellation/cli/ec2"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateInstances creates the instances defined in input.
|
|
||||||
//
|
|
||||||
// An existing security group is needed to create instances.
|
|
||||||
func (c *Client) CreateInstances(ctx context.Context, input CreateInput) error {
|
|
||||||
if c.securityGroup == "" {
|
|
||||||
return errors.New("no security group set")
|
|
||||||
}
|
|
||||||
input.securityGroupIds = []string{c.securityGroup}
|
|
||||||
|
|
||||||
if err := c.createDryRun(ctx, input); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.api.RunInstances(ctx, input.AWS())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create instances: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, instance := range resp.Instances {
|
|
||||||
id := instance.InstanceId
|
|
||||||
if id == nil {
|
|
||||||
return errors.New("instanceId is nil pointer")
|
|
||||||
}
|
|
||||||
c.instances[*id] = ec2.Instance{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.waitStateRunning(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.tagInstances(ctx, input.Tags); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.getInstanceIPs(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TerminateInstances terminates all instances of a Client.
|
|
||||||
func (c *Client) TerminateInstances(ctx context.Context) error {
|
|
||||||
if len(c.instances) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
input := &awsec2.TerminateInstancesInput{
|
|
||||||
InstanceIds: c.instances.IDs(),
|
|
||||||
}
|
|
||||||
if err := c.terminateDryRun(ctx, *input); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := c.api.TerminateInstances(ctx, input); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.waitStateTerminated(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.instances = ec2.Instances{}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitStateRunning waits until all the client's instances reached the running state.
|
|
||||||
//
|
|
||||||
// A set of instances is also considered to be running if at least one of the
|
|
||||||
// instances' state is 'running' and the other instances have a nil state.
|
|
||||||
func (c *Client) waitStateRunning(ctx context.Context) error {
|
|
||||||
if len(c.instances) == 0 {
|
|
||||||
return errors.New("client has no instances")
|
|
||||||
}
|
|
||||||
describeInput := &awsec2.DescribeInstancesInput{
|
|
||||||
InstanceIds: c.instances.IDs(),
|
|
||||||
}
|
|
||||||
waiter := awsec2.NewInstanceRunningWaiter(c.api)
|
|
||||||
return waiter.Wait(ctx, describeInput, c.timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitStateTerminated waits until all the client's instances reached the terminated state.
|
|
||||||
//
|
|
||||||
// A set of instances is also considered to be terminated if at least one of the
|
|
||||||
// instances' state is 'terminated' and the other instances have a nil state.
|
|
||||||
func (c *Client) waitStateTerminated(ctx context.Context) error {
|
|
||||||
if len(c.instances) == 0 {
|
|
||||||
return errors.New("client has no instances")
|
|
||||||
}
|
|
||||||
|
|
||||||
describeInput := &awsec2.DescribeInstancesInput{
|
|
||||||
InstanceIds: c.instances.IDs(),
|
|
||||||
}
|
|
||||||
waiter := awsec2.NewInstanceTerminatedWaiter(c.api)
|
|
||||||
return waiter.Wait(ctx, describeInput, c.timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tagInstances tags all instances of a client with a given set of tags.
|
|
||||||
func (c *Client) tagInstances(ctx context.Context, tags ec2.Tags) error {
|
|
||||||
if len(c.instances) == 0 {
|
|
||||||
return errors.New("client has no instances")
|
|
||||||
}
|
|
||||||
|
|
||||||
tagInput := &awsec2.CreateTagsInput{
|
|
||||||
Resources: c.instances.IDs(),
|
|
||||||
Tags: tags.AWS(),
|
|
||||||
}
|
|
||||||
if _, err := c.api.CreateTags(ctx, tagInput); err != nil {
|
|
||||||
return fmt.Errorf("failed to tag instances: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createDryRun checks if user has the privilege to create the instances
|
|
||||||
// which were defined in input.
|
|
||||||
func (c *Client) createDryRun(ctx context.Context, input CreateInput) error {
|
|
||||||
runInput := input.AWS()
|
|
||||||
runInput.DryRun = aws.Bool(true)
|
|
||||||
_, err := c.api.RunInstances(ctx, runInput)
|
|
||||||
return checkDryRunError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// terminateDryRun checks if user has the privilege to terminate the instances
|
|
||||||
// which were defined in input.
|
|
||||||
func (c *Client) terminateDryRun(ctx context.Context, input awsec2.TerminateInstancesInput) error {
|
|
||||||
input.DryRun = aws.Bool(true)
|
|
||||||
_, err := c.api.TerminateInstances(ctx, &input)
|
|
||||||
return checkDryRunError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getInstanceIPs queries the private and public IP addresses
|
|
||||||
// and adds the information to each instance.
|
|
||||||
//
|
|
||||||
// The instances must be in 'running' state.
|
|
||||||
func (c *Client) getInstanceIPs(ctx context.Context) error {
|
|
||||||
describeInput := &awsec2.DescribeInstancesInput{
|
|
||||||
InstanceIds: c.instances.IDs(),
|
|
||||||
}
|
|
||||||
paginator := awsec2.NewDescribeInstancesPaginator(c.api, describeInput)
|
|
||||||
for paginator.HasMorePages() {
|
|
||||||
output, err := paginator.NextPage(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, reservation := range output.Reservations {
|
|
||||||
for _, instanceDescription := range reservation.Instances {
|
|
||||||
if instanceDescription.InstanceId == nil {
|
|
||||||
return errors.New("instanceId is nil pointer")
|
|
||||||
}
|
|
||||||
if instanceDescription.PublicIpAddress == nil {
|
|
||||||
return errors.New("publicIpAddress is nil pointer")
|
|
||||||
}
|
|
||||||
if instanceDescription.PrivateIpAddress == nil {
|
|
||||||
return errors.New("privateIpAddress is nil pointer")
|
|
||||||
}
|
|
||||||
instance, ok := c.instances[*instanceDescription.InstanceId]
|
|
||||||
if !ok {
|
|
||||||
return errors.New("got an instance description to an unknown instanceId")
|
|
||||||
}
|
|
||||||
instance.PublicIP = *instanceDescription.PublicIpAddress
|
|
||||||
instance.PrivateIP = *instanceDescription.PrivateIpAddress
|
|
||||||
c.instances[*instanceDescription.InstanceId] = instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateInput defines the propertis of the instances to create.
|
|
||||||
type CreateInput struct {
|
|
||||||
ImageId string
|
|
||||||
InstanceType string
|
|
||||||
Count int
|
|
||||||
Tags ec2.Tags
|
|
||||||
securityGroupIds []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// AWS creates a AWS ec2.RunInstancesInput from an CreateInput.
|
|
||||||
func (ci *CreateInput) AWS() *awsec2.RunInstancesInput {
|
|
||||||
return &awsec2.RunInstancesInput{
|
|
||||||
ImageId: aws.String(ci.ImageId),
|
|
||||||
InstanceType: ec2.InstanceTypes[ci.InstanceType],
|
|
||||||
MaxCount: aws.Int32(int32(ci.Count)),
|
|
||||||
MinCount: aws.Int32(int32(ci.Count)),
|
|
||||||
EnclaveOptions: &types.EnclaveOptionsRequest{Enabled: aws.Bool(true)},
|
|
||||||
SecurityGroupIds: ci.securityGroupIds,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,493 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
|
||||||
awsec2 "github.com/aws/aws-sdk-go-v2/service/ec2"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
|
||||||
"github.com/edgelesssys/constellation/cli/ec2"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCreateInstances(t *testing.T) {
|
|
||||||
testInstances := []types.Instance{
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-1"),
|
|
||||||
PublicIpAddress: aws.String("192.0.2.1"),
|
|
||||||
PrivateIpAddress: aws.String("192.0.2.2"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-2"),
|
|
||||||
PublicIpAddress: aws.String("192.0.2.3"),
|
|
||||||
PrivateIpAddress: aws.String("192.0.2.4"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-3"),
|
|
||||||
PublicIpAddress: aws.String("192.0.2.5"),
|
|
||||||
PrivateIpAddress: aws.String("192.0.2.6"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
someErr := errors.New("failed")
|
|
||||||
var noErr error
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
api stubAPI
|
|
||||||
instances ec2.Instances
|
|
||||||
securityGroup string
|
|
||||||
wantErr bool
|
|
||||||
wantInstances ec2.Instances
|
|
||||||
}{
|
|
||||||
"create": {
|
|
||||||
api: stubAPI{instances: testInstances},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
wantInstances: ec2.Instances{
|
|
||||||
"id-1": {PublicIP: "192.0.2.1", PrivateIP: "192.0.2.2"},
|
|
||||||
"id-2": {PublicIP: "192.0.2.3", PrivateIP: "192.0.2.4"},
|
|
||||||
"id-3": {PublicIP: "192.0.2.5", PrivateIP: "192.0.2.6"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"client already has instances": {
|
|
||||||
api: stubAPI{instances: testInstances},
|
|
||||||
instances: ec2.Instances{"id-4": {}, "id-5": {}},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
wantInstances: ec2.Instances{
|
|
||||||
"id-1": {PublicIP: "192.0.2.1", PrivateIP: "192.0.2.2"},
|
|
||||||
"id-2": {PublicIP: "192.0.2.3", PrivateIP: "192.0.2.4"},
|
|
||||||
"id-3": {PublicIP: "192.0.2.5", PrivateIP: "192.0.2.6"},
|
|
||||||
"id-4": {},
|
|
||||||
"id-5": {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"client already has same instance id": {
|
|
||||||
api: stubAPI{instances: testInstances},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-4": {}, "id-5": {}},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
wantErr: false,
|
|
||||||
wantInstances: ec2.Instances{
|
|
||||||
"id-1": {PublicIP: "192.0.2.1", PrivateIP: "192.0.2.2"},
|
|
||||||
"id-2": {PublicIP: "192.0.2.3", PrivateIP: "192.0.2.4"},
|
|
||||||
"id-3": {PublicIP: "192.0.2.5", PrivateIP: "192.0.2.6"},
|
|
||||||
"id-4": {},
|
|
||||||
"id-5": {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"client has no security group": {
|
|
||||||
api: stubAPI{},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"run API error": {
|
|
||||||
api: stubAPI{runInstancesErr: someErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"runDryRun API error": {
|
|
||||||
api: stubAPI{runInstancesDryRunErr: &someErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"runDryRun missing expected API error": {
|
|
||||||
api: stubAPI{runInstancesDryRunErr: &noErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
client := &Client{
|
|
||||||
api: tc.api,
|
|
||||||
instances: tc.instances,
|
|
||||||
timeout: time.Millisecond,
|
|
||||||
securityGroup: tc.securityGroup,
|
|
||||||
}
|
|
||||||
if client.instances == nil {
|
|
||||||
client.instances = make(map[string]ec2.Instance)
|
|
||||||
}
|
|
||||||
input := CreateInput{
|
|
||||||
ImageId: "test-image",
|
|
||||||
InstanceType: "",
|
|
||||||
Count: 13,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.CreateInstances(context.Background(), input)
|
|
||||||
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.ElementsMatch(tc.wantInstances.IDs(), client.instances.IDs())
|
|
||||||
assert.ElementsMatch(tc.wantInstances.PublicIPs(), client.instances.PublicIPs())
|
|
||||||
assert.ElementsMatch(tc.wantInstances.PrivateIPs(), client.instances.PrivateIPs())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTerminateInstances(t *testing.T) {
|
|
||||||
testAWSInstances := []types.Instance{
|
|
||||||
{InstanceId: aws.String("id-1"), State: &stateTerminated},
|
|
||||||
{InstanceId: aws.String("id-2"), State: &stateTerminated},
|
|
||||||
{InstanceId: aws.String("id-3"), State: &stateTerminated},
|
|
||||||
}
|
|
||||||
someErr := errors.New("failed")
|
|
||||||
var noErr error
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
api stubAPI
|
|
||||||
instances ec2.Instances
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"client with instances": {
|
|
||||||
api: stubAPI{instances: testAWSInstances},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
"client no instances set": {
|
|
||||||
api: stubAPI{},
|
|
||||||
},
|
|
||||||
"terminate API error": {
|
|
||||||
api: stubAPI{terminateInstancesErr: someErr},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"terminateDryRun API error": {
|
|
||||||
api: stubAPI{terminateInstancesDryRunErr: &someErr},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"terminateDryRun miss expected API error": {
|
|
||||||
api: stubAPI{terminateInstancesDryRunErr: &noErr},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
client := &Client{
|
|
||||||
api: tc.api,
|
|
||||||
instances: tc.instances,
|
|
||||||
timeout: time.Millisecond,
|
|
||||||
}
|
|
||||||
if client.instances == nil {
|
|
||||||
client.instances = make(map[string]ec2.Instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.TerminateInstances(context.Background())
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Empty(client.instances)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWaitStateRunning(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
api api
|
|
||||||
instances ec2.Instances
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"instances are running": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-1"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-2"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-3"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
"one instance running, rest nil": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-1"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
{InstanceId: aws.String("id-2")},
|
|
||||||
{InstanceId: aws.String("id-3")},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
"one instance terminated, rest nil": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-1"),
|
|
||||||
State: &stateTerminated,
|
|
||||||
},
|
|
||||||
{InstanceId: aws.String("id-2")},
|
|
||||||
{InstanceId: aws.String("id-3")},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"instances with different state": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-1"),
|
|
||||||
State: &stateTerminated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-2"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
{InstanceId: aws.String("id-3")},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"all instances have nil state": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{InstanceId: aws.String("id-1")},
|
|
||||||
{InstanceId: aws.String("id-2")},
|
|
||||||
{InstanceId: aws.String("id-3")},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"client has no instances": {
|
|
||||||
api: &stubAPI{},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
client := &Client{
|
|
||||||
api: tc.api,
|
|
||||||
instances: tc.instances,
|
|
||||||
timeout: time.Millisecond,
|
|
||||||
}
|
|
||||||
if client.instances == nil {
|
|
||||||
client.instances = make(map[string]ec2.Instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.waitStateRunning(context.Background())
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWaitStateTerminated(t *testing.T) {
|
|
||||||
testCases := map[string]struct {
|
|
||||||
api api
|
|
||||||
instances ec2.Instances
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"instances are terminated": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-1"),
|
|
||||||
State: &stateTerminated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-2"),
|
|
||||||
State: &stateTerminated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-3"),
|
|
||||||
State: &stateTerminated,
|
|
||||||
},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
"one instance terminated, rest nil": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-1"),
|
|
||||||
State: &stateTerminated,
|
|
||||||
},
|
|
||||||
{InstanceId: aws.String("id-2")},
|
|
||||||
{InstanceId: aws.String("id-3")},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
"one instance running, rest nil": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-1"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
{InstanceId: aws.String("id-2")},
|
|
||||||
{InstanceId: aws.String("id-3")},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"instances with different state": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-1"),
|
|
||||||
State: &stateTerminated,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
InstanceId: aws.String("id-2"),
|
|
||||||
State: &stateRunning,
|
|
||||||
},
|
|
||||||
{InstanceId: aws.String("id-3")},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"all instances have nil state": {
|
|
||||||
api: stubAPI{instances: []types.Instance{
|
|
||||||
{InstanceId: aws.String("id-1")},
|
|
||||||
{InstanceId: aws.String("id-2")},
|
|
||||||
{InstanceId: aws.String("id-3")},
|
|
||||||
}},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"client has no instances": {
|
|
||||||
api: &stubAPI{},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
client := &Client{
|
|
||||||
api: tc.api,
|
|
||||||
instances: tc.instances,
|
|
||||||
timeout: time.Millisecond,
|
|
||||||
}
|
|
||||||
if client.instances == nil {
|
|
||||||
client.instances = make(map[string]ec2.Instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.waitStateTerminated(context.Background())
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTagInstances(t *testing.T) {
|
|
||||||
testTags := ec2.Tags{
|
|
||||||
{Key: "Name", Value: "Test"},
|
|
||||||
{Key: "Foo", Value: "Bar"},
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
api stubAPI
|
|
||||||
instances ec2.Instances
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"tag": {
|
|
||||||
api: stubAPI{},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
"client without instances": {
|
|
||||||
api: stubAPI{createTagsErr: errors.New("failed")},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"tag API error": {
|
|
||||||
api: stubAPI{createTagsErr: errors.New("failed")},
|
|
||||||
instances: ec2.Instances{"id-1": {}, "id-2": {}, "id-3": {}},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
client := &Client{
|
|
||||||
api: tc.api,
|
|
||||||
instances: tc.instances,
|
|
||||||
timeout: time.Millisecond,
|
|
||||||
}
|
|
||||||
if client.instances == nil {
|
|
||||||
client.instances = make(map[string]ec2.Instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.tagInstances(context.Background(), testTags)
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEc2RunInstanceInput(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
in CreateInput
|
|
||||||
wantOutput awsec2.RunInstancesInput
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
in: CreateInput{
|
|
||||||
ImageId: "test-image",
|
|
||||||
InstanceType: "4xlarge",
|
|
||||||
Count: 13,
|
|
||||||
securityGroupIds: []string{"test-sec-group"},
|
|
||||||
},
|
|
||||||
wantOutput: awsec2.RunInstancesInput{
|
|
||||||
ImageId: aws.String("test-image"),
|
|
||||||
InstanceType: types.InstanceTypeC5a4xlarge,
|
|
||||||
MinCount: aws.Int32(int32(13)),
|
|
||||||
MaxCount: aws.Int32(int32(13)),
|
|
||||||
EnclaveOptions: &types.EnclaveOptionsRequest{Enabled: aws.Bool(true)},
|
|
||||||
SecurityGroupIds: []string{"test-sec-group"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
in: CreateInput{
|
|
||||||
ImageId: "test-image-2",
|
|
||||||
InstanceType: "12xlarge",
|
|
||||||
Count: 2,
|
|
||||||
securityGroupIds: []string{"test-sec-group-2"},
|
|
||||||
},
|
|
||||||
wantOutput: awsec2.RunInstancesInput{
|
|
||||||
ImageId: aws.String("test-image-2"),
|
|
||||||
InstanceType: types.InstanceTypeC5a12xlarge,
|
|
||||||
MinCount: aws.Int32(int32(2)),
|
|
||||||
MaxCount: aws.Int32(int32(2)),
|
|
||||||
EnclaveOptions: &types.EnclaveOptionsRequest{Enabled: aws.Bool(true)},
|
|
||||||
SecurityGroupIds: []string{"test-sec-group-2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
out := tc.in.AWS()
|
|
||||||
assert.Equal(tc.wantOutput, *out)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
|
||||||
awsec2 "github.com/aws/aws-sdk-go-v2/service/ec2"
|
|
||||||
"github.com/edgelesssys/constellation/cli/cloud/cloudtypes"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateSecurityGroup creates a AWS security group with the handed properties.
|
|
||||||
func (c *Client) CreateSecurityGroup(ctx context.Context, input SecurityGroupInput) error {
|
|
||||||
if c.securityGroup != "" {
|
|
||||||
return errors.New("client already has a security group")
|
|
||||||
}
|
|
||||||
|
|
||||||
id := uuid.New()
|
|
||||||
createInput := &awsec2.CreateSecurityGroupInput{
|
|
||||||
Description: aws.String("Security group of Constellation cluster. This group was generated through the Constellation CLI."),
|
|
||||||
GroupName: aws.String("Constellation-" + id.String()),
|
|
||||||
DryRun: aws.Bool(true),
|
|
||||||
}
|
|
||||||
|
|
||||||
// DryRun
|
|
||||||
_, err := c.api.CreateSecurityGroup(ctx, createInput)
|
|
||||||
if err := checkDryRunError(err); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
createInput.DryRun = aws.Bool(false)
|
|
||||||
|
|
||||||
// Create
|
|
||||||
out, err := c.api.CreateSecurityGroup(ctx, createInput)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if out.GroupId == nil {
|
|
||||||
return errors.New("security group creation didn't return an id")
|
|
||||||
}
|
|
||||||
c.securityGroup = *out.GroupId
|
|
||||||
|
|
||||||
// Authorize.
|
|
||||||
return c.authorizeSecurityGroup(ctx, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteSecurityGroup deletes the security group of the client.
|
|
||||||
// The deletion is idempotent, no error is returned if the client has
|
|
||||||
// an empty securityGroupID.
|
|
||||||
func (c *Client) DeleteSecurityGroup(ctx context.Context) error {
|
|
||||||
if c.securityGroup == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
input := &awsec2.DeleteSecurityGroupInput{
|
|
||||||
GroupId: aws.String(c.securityGroup),
|
|
||||||
DryRun: aws.Bool(true),
|
|
||||||
}
|
|
||||||
|
|
||||||
// DryRun
|
|
||||||
_, err := c.api.DeleteSecurityGroup(ctx, input)
|
|
||||||
if err := checkDryRunError(err); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
input.DryRun = aws.Bool(false)
|
|
||||||
|
|
||||||
// Delete
|
|
||||||
if _, err := c.api.DeleteSecurityGroup(ctx, input); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.securityGroup = ""
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) authorizeSecurityGroup(ctx context.Context, input SecurityGroupInput) error {
|
|
||||||
if c.securityGroup == "" {
|
|
||||||
return errors.New("client hasn't got a security group id")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.authorizeSecurityGroupIngress(ctx, input.Inbound); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.authorizeSecurityGroupEgress(ctx, input.Outbound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) authorizeSecurityGroupIngress(ctx context.Context, perms cloudtypes.Firewall) error {
|
|
||||||
if len(perms) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
authInput := &awsec2.AuthorizeSecurityGroupIngressInput{
|
|
||||||
GroupId: aws.String(c.securityGroup),
|
|
||||||
IpPermissions: perms.AWS(),
|
|
||||||
DryRun: aws.Bool(true),
|
|
||||||
}
|
|
||||||
|
|
||||||
// DryRun
|
|
||||||
_, err := c.api.AuthorizeSecurityGroupIngress(ctx, authInput)
|
|
||||||
if err := checkDryRunError(err); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
authInput.DryRun = aws.Bool(false)
|
|
||||||
|
|
||||||
// Authorize
|
|
||||||
_, err = c.api.AuthorizeSecurityGroupIngress(ctx, authInput)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) authorizeSecurityGroupEgress(ctx context.Context, perms cloudtypes.Firewall) error {
|
|
||||||
if len(perms) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
authInput := &awsec2.AuthorizeSecurityGroupEgressInput{
|
|
||||||
GroupId: aws.String(c.securityGroup),
|
|
||||||
IpPermissions: perms.AWS(),
|
|
||||||
DryRun: aws.Bool(true),
|
|
||||||
}
|
|
||||||
|
|
||||||
// DryRun
|
|
||||||
_, err := c.api.AuthorizeSecurityGroupEgress(ctx, authInput)
|
|
||||||
if err := checkDryRunError(err); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
authInput.DryRun = aws.Bool(false)
|
|
||||||
|
|
||||||
// Authorize
|
|
||||||
_, err = c.api.AuthorizeSecurityGroupEgress(ctx, authInput)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
type SecurityGroupInput struct {
|
|
||||||
Inbound cloudtypes.Firewall
|
|
||||||
Outbound cloudtypes.Firewall
|
|
||||||
}
|
|
|
@ -1,269 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
|
||||||
"github.com/edgelesssys/constellation/cli/cloud/cloudtypes"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCreateSecurityGroup(t *testing.T) {
|
|
||||||
testInput := SecurityGroupInput{
|
|
||||||
Inbound: cloudtypes.Firewall{
|
|
||||||
{
|
|
||||||
Description: "perm1",
|
|
||||||
Protocol: "TCP",
|
|
||||||
IPRange: "192.0.2.0/24",
|
|
||||||
FromPort: 22,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Description: "perm2",
|
|
||||||
Protocol: "UDP",
|
|
||||||
IPRange: "192.0.2.0/24",
|
|
||||||
FromPort: 4433,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Outbound: cloudtypes.Firewall{
|
|
||||||
{
|
|
||||||
Description: "perm3",
|
|
||||||
Protocol: "TCP",
|
|
||||||
IPRange: "192.0.2.0/24",
|
|
||||||
FromPort: 4040,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
someErr := errors.New("failed")
|
|
||||||
var noErr error
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
api stubAPI
|
|
||||||
securityGroup string
|
|
||||||
input SecurityGroupInput
|
|
||||||
wantErr bool
|
|
||||||
wantSecurityGroup string
|
|
||||||
}{
|
|
||||||
"create security group": {
|
|
||||||
api: stubAPI{securityGroup: types.SecurityGroup{GroupId: aws.String("sg-test")}},
|
|
||||||
input: testInput,
|
|
||||||
wantSecurityGroup: "sg-test",
|
|
||||||
},
|
|
||||||
"create security group without permissions": {
|
|
||||||
api: stubAPI{securityGroup: types.SecurityGroup{GroupId: aws.String("sg-test")}},
|
|
||||||
input: SecurityGroupInput{},
|
|
||||||
wantSecurityGroup: "sg-test",
|
|
||||||
},
|
|
||||||
"client already has security group": {
|
|
||||||
api: stubAPI{},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"create returns nil security group ID": {
|
|
||||||
api: stubAPI{securityGroup: types.SecurityGroup{GroupId: nil}},
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"create API error": {
|
|
||||||
api: stubAPI{createSecurityGroupErr: someErr},
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"create DryRun API error": {
|
|
||||||
api: stubAPI{createSecurityGroupDryRunErr: &someErr},
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"create DryRun missing expected error": {
|
|
||||||
api: stubAPI{createSecurityGroupDryRunErr: &noErr},
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"authorize error": {
|
|
||||||
api: stubAPI{
|
|
||||||
securityGroup: types.SecurityGroup{GroupId: aws.String("sg-test")},
|
|
||||||
authorizeSecurityGroupIngressErr: someErr,
|
|
||||||
},
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
client, err := newClient(tc.api)
|
|
||||||
require.NoError(err)
|
|
||||||
client.securityGroup = tc.securityGroup
|
|
||||||
|
|
||||||
err = client.CreateSecurityGroup(context.Background(), tc.input)
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Equal(tc.wantSecurityGroup, client.securityGroup)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDeleteSecurityGroup(t *testing.T) {
|
|
||||||
someErr := errors.New("failed")
|
|
||||||
var noErr error
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
api stubAPI
|
|
||||||
securityGroup string
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"delete security group": {
|
|
||||||
api: stubAPI{},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
},
|
|
||||||
"client without security group": {
|
|
||||||
api: stubAPI{},
|
|
||||||
},
|
|
||||||
"delete API error": {
|
|
||||||
api: stubAPI{deleteSecurityGroupErr: someErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"delete DryRun API error": {
|
|
||||||
api: stubAPI{deleteSecurityGroupDryRunErr: &someErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"delete DryRun missing expected error": {
|
|
||||||
api: stubAPI{deleteSecurityGroupDryRunErr: &noErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
client, err := newClient(tc.api)
|
|
||||||
require.NoError(err)
|
|
||||||
client.securityGroup = tc.securityGroup
|
|
||||||
|
|
||||||
err = client.DeleteSecurityGroup(context.Background())
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Empty(client.securityGroup)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAuthorizeSecurityGroup(t *testing.T) {
|
|
||||||
testInput := SecurityGroupInput{
|
|
||||||
Inbound: cloudtypes.Firewall{
|
|
||||||
{
|
|
||||||
Description: "perm1",
|
|
||||||
Protocol: "TCP",
|
|
||||||
IPRange: " 192.0.2.0/24",
|
|
||||||
FromPort: 22,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Description: "perm2",
|
|
||||||
Protocol: "UDP",
|
|
||||||
IPRange: "192.0.2.0/24",
|
|
||||||
FromPort: 4433,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Outbound: cloudtypes.Firewall{
|
|
||||||
{
|
|
||||||
Description: "perm3",
|
|
||||||
Protocol: "TCP",
|
|
||||||
IPRange: "192.0.2.0/24",
|
|
||||||
FromPort: 4040,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
someErr := errors.New("failed")
|
|
||||||
var noErr error
|
|
||||||
|
|
||||||
testCases := map[string]struct {
|
|
||||||
api stubAPI
|
|
||||||
securityGroup string
|
|
||||||
input SecurityGroupInput
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
"authorize": {
|
|
||||||
api: stubAPI{},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
input: testInput,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
"client without security group": {
|
|
||||||
api: stubAPI{},
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"authorizeIngress API error": {
|
|
||||||
api: stubAPI{authorizeSecurityGroupIngressErr: someErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"authorizeIngress DryRun API error": {
|
|
||||||
api: stubAPI{authorizeSecurityGroupIngressDryRunErr: &someErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"authorizeIngress DryRun missing expected error": {
|
|
||||||
api: stubAPI{authorizeSecurityGroupIngressDryRunErr: &noErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"authorizeEgress API error": {
|
|
||||||
api: stubAPI{authorizeSecurityGroupEgressErr: someErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"authorizeEgress DryRun API error": {
|
|
||||||
api: stubAPI{authorizeSecurityGroupEgressDryRunErr: &someErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
"authorizeEgress DryRun missing expected error": {
|
|
||||||
api: stubAPI{authorizeSecurityGroupEgressDryRunErr: &noErr},
|
|
||||||
securityGroup: "sg-test",
|
|
||||||
input: testInput,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, tc := range testCases {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
require := require.New(t)
|
|
||||||
|
|
||||||
client, err := newClient(tc.api)
|
|
||||||
require.NoError(err)
|
|
||||||
client.securityGroup = tc.securityGroup
|
|
||||||
|
|
||||||
err = client.authorizeSecurityGroup(context.Background(), tc.input)
|
|
||||||
if tc.wantErr {
|
|
||||||
assert.Error(err)
|
|
||||||
} else {
|
|
||||||
assert.NoError(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/aws/smithy-go"
|
|
||||||
)
|
|
||||||
|
|
||||||
// checkDryRunError error checks if an error is a DryRun error.
|
|
||||||
// If the error is nil, an error is returned, since a DryRun error
|
|
||||||
// is the expected result of a DryRun operation.
|
|
||||||
func checkDryRunError(err error) error {
|
|
||||||
var apiErr smithy.APIError
|
|
||||||
if errors.As(err, &apiErr) && apiErr.ErrorCode() == "DryRunOperation" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return errors.New("expected APIError: DryRunOperation, but got no error at all")
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/aws/smithy-go"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCheckDryRunError(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
someErr := errors.New("failed")
|
|
||||||
assert.ErrorIs(checkDryRunError(someErr), someErr)
|
|
||||||
|
|
||||||
dryRunErr := smithy.GenericAPIError{Code: "DryRunOperation"}
|
|
||||||
assert.NoError(checkDryRunError(&dryRunErr))
|
|
||||||
|
|
||||||
var nilErr error
|
|
||||||
assert.Error(checkDryRunError(nilErr))
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
package ec2
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
// Instance is an ec2 instance.
|
|
||||||
type Instance struct {
|
|
||||||
PublicIP string
|
|
||||||
PrivateIP string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instances is a map of ec2 Instances. The ID of an instance is used as key.
|
|
||||||
type Instances map[string]Instance
|
|
||||||
|
|
||||||
// IDs returns the IDs of all instances of the Constellation.
|
|
||||||
func (i Instances) IDs() []string {
|
|
||||||
var ids []string
|
|
||||||
for id := range i {
|
|
||||||
ids = append(ids, id)
|
|
||||||
}
|
|
||||||
return ids
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicIPs returns the public IPs of all the instances of the Constellation.
|
|
||||||
func (i Instances) PublicIPs() []string {
|
|
||||||
var ips []string
|
|
||||||
for _, instance := range i {
|
|
||||||
ips = append(ips, instance.PublicIP)
|
|
||||||
}
|
|
||||||
return ips
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivateIPs returns the private IPs of all the instances of the Constellation.
|
|
||||||
func (i Instances) PrivateIPs() []string {
|
|
||||||
var ips []string
|
|
||||||
for _, instance := range i {
|
|
||||||
ips = append(ips, instance.PrivateIP)
|
|
||||||
}
|
|
||||||
return ips
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOne return anyone instance out of the instances and its ID.
|
|
||||||
func (i Instances) GetOne() (string, Instance, error) {
|
|
||||||
for id, instance := range i {
|
|
||||||
return id, instance, nil
|
|
||||||
}
|
|
||||||
return "", Instance{}, errors.New("map is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOthers returns all instances but the one with the handed ID.
|
|
||||||
func (i Instances) GetOthers(id string) Instances {
|
|
||||||
others := make(Instances)
|
|
||||||
for key, instance := range i {
|
|
||||||
if key != id {
|
|
||||||
others[key] = instance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return others
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package ec2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIDs(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
testState := testInstances()
|
|
||||||
wantIDs := []string{"id-9", "id-10", "id-11", "id-12"}
|
|
||||||
assert.ElementsMatch(wantIDs, testState.IDs())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPublicIPs(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
testState := testInstances()
|
|
||||||
wantIPs := []string{"192.0.2.1", "192.0.2.3", "192.0.2.5", "192.0.2.7"}
|
|
||||||
assert.ElementsMatch(wantIPs, testState.PublicIPs())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrivateIPs(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
testState := testInstances()
|
|
||||||
wantIPs := []string{"192.0.2.2", "192.0.2.4", "192.0.2.6", "192.0.2.8"}
|
|
||||||
assert.ElementsMatch(wantIPs, testState.PrivateIPs())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetOne(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
testState := testInstances()
|
|
||||||
id, instance, err := testState.GetOne()
|
|
||||||
assert.NoError(err)
|
|
||||||
assert.Contains(testState, id)
|
|
||||||
assert.Equal(testState[id], instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetOthers(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
testCases := testInstances().IDs()
|
|
||||||
|
|
||||||
for _, id := range testCases {
|
|
||||||
others := testInstances().GetOthers(id)
|
|
||||||
assert.NotContains(others, id)
|
|
||||||
wantInstances := testInstances()
|
|
||||||
delete(wantInstances, id)
|
|
||||||
assert.ElementsMatch(others.IDs(), wantInstances.IDs())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testInstances() Instances {
|
|
||||||
return Instances{
|
|
||||||
"id-9": {
|
|
||||||
PublicIP: "192.0.2.1",
|
|
||||||
PrivateIP: "192.0.2.2",
|
|
||||||
},
|
|
||||||
"id-10": {
|
|
||||||
PublicIP: "192.0.2.3",
|
|
||||||
PrivateIP: "192.0.2.4",
|
|
||||||
},
|
|
||||||
"id-11": {
|
|
||||||
PublicIP: "192.0.2.5",
|
|
||||||
PrivateIP: "192.0.2.6",
|
|
||||||
},
|
|
||||||
"id-12": {
|
|
||||||
PublicIP: "192.0.2.7",
|
|
||||||
PrivateIP: "192.0.2.8",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package ec2
|
|
||||||
|
|
||||||
import "github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
|
||||||
|
|
||||||
// InstanceTypes defines possible values for the SIZE positional argument.
|
|
||||||
var InstanceTypes = map[string]types.InstanceType{
|
|
||||||
"4xlarge": types.InstanceTypeC5a4xlarge,
|
|
||||||
"8xlarge": types.InstanceTypeC5a8xlarge,
|
|
||||||
"12xlarge": types.InstanceTypeC5a12xlarge,
|
|
||||||
"16xlarge": types.InstanceTypeC5a16xlarge,
|
|
||||||
"24xlarge": types.InstanceTypeC5a24xlarge,
|
|
||||||
// shorthands
|
|
||||||
"4xl": types.InstanceTypeC5a4xlarge,
|
|
||||||
"8xl": types.InstanceTypeC5a8xlarge,
|
|
||||||
"12xl": types.InstanceTypeC5a12xlarge,
|
|
||||||
"16xl": types.InstanceTypeC5a16xlarge,
|
|
||||||
"24xl": types.InstanceTypeC5a24xlarge,
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package ec2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Tag is a ec2 tag. It consits of a key and a value.
|
|
||||||
type Tag struct {
|
|
||||||
Key string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tags is a set of Tags.
|
|
||||||
type Tags []Tag
|
|
||||||
|
|
||||||
// AWS returns a AWS representation of tags.
|
|
||||||
func (t Tags) AWS() []types.Tag {
|
|
||||||
var awsTags []types.Tag
|
|
||||||
for _, tag := range t {
|
|
||||||
awsTags = append(awsTags, types.Tag{
|
|
||||||
Key: aws.String(tag.Key),
|
|
||||||
Value: aws.String(tag.Value),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return awsTags
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package ec2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
|
||||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTagsAws(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
|
|
||||||
testTags := Tags{
|
|
||||||
{
|
|
||||||
Key: "Name",
|
|
||||||
Value: "Test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: "Foo",
|
|
||||||
Value: "Bar",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
wantTags := []types.Tag{
|
|
||||||
{
|
|
||||||
Key: aws.String("Name"),
|
|
||||||
Value: aws.String("Test"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Key: aws.String("Foo"),
|
|
||||||
Value: aws.String("Bar"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
awsTags := testTags.AWS()
|
|
||||||
assert.Equal(wantTags, awsTags)
|
|
||||||
}
|
|
|
@ -13,8 +13,6 @@ import (
|
||||||
|
|
||||||
func GetScalingGroupsFromConfig(stat state.ConstellationState, config *configc.Config) (coordinators, nodes cmdc.ScalingGroup, err error) {
|
func GetScalingGroupsFromConfig(stat state.ConstellationState, config *configc.Config) (coordinators, nodes cmdc.ScalingGroup, err error) {
|
||||||
switch {
|
switch {
|
||||||
case len(stat.EC2Instances) != 0:
|
|
||||||
return getAWSInstances(stat, config)
|
|
||||||
case len(stat.GCPCoordinators) != 0:
|
case len(stat.GCPCoordinators) != 0:
|
||||||
return getGCPInstances(stat, config)
|
return getGCPInstances(stat, config)
|
||||||
case len(stat.AzureCoordinators) != 0:
|
case len(stat.AzureCoordinators) != 0:
|
||||||
|
@ -26,38 +24,6 @@ func GetScalingGroupsFromConfig(stat state.ConstellationState, config *configc.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAWSInstances(stat state.ConstellationState, _ *configc.Config) (coordinators, nodes cmdc.ScalingGroup, err error) {
|
|
||||||
coordinatorID, _, err := stat.EC2Instances.GetOne()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
coordinatorMap := stat.EC2Instances
|
|
||||||
var coordinatorInstances cmdc.Instances
|
|
||||||
for _, node := range coordinatorMap {
|
|
||||||
coordinatorInstances = append(coordinatorInstances, cmdc.Instance(node))
|
|
||||||
}
|
|
||||||
// GroupID of coordinators is empty, since they currently do not scale.
|
|
||||||
coordinators = cmdc.ScalingGroup{
|
|
||||||
Instances: coordinatorInstances,
|
|
||||||
GroupID: "",
|
|
||||||
}
|
|
||||||
nodeMap := stat.EC2Instances.GetOthers(coordinatorID)
|
|
||||||
if len(nodeMap) == 0 {
|
|
||||||
return cmdc.ScalingGroup{}, cmdc.ScalingGroup{}, errors.New("no nodes available, can't create Constellation with one instance")
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodeInstances cmdc.Instances
|
|
||||||
for _, node := range nodeMap {
|
|
||||||
nodeInstances = append(nodeInstances, cmdc.Instance(node))
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make min / max configurable and abstract autoscaling for different cloud providers
|
|
||||||
// TODO: GroupID of nodes is empty, since they currently do not scale.
|
|
||||||
nodes = cmdc.ScalingGroup{Instances: nodeInstances}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGCPInstances(stat state.ConstellationState, config *configc.Config) (coordinators, nodes cmdc.ScalingGroup, err error) {
|
func getGCPInstances(stat state.ConstellationState, config *configc.Config) (coordinators, nodes cmdc.ScalingGroup, err error) {
|
||||||
coordinatorMap := stat.GCPCoordinators
|
coordinatorMap := stat.GCPCoordinators
|
||||||
if len(coordinatorMap) == 0 {
|
if len(coordinatorMap) == 0 {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/edgelesssys/constellation/cli/cloud/cloudtypes"
|
"github.com/edgelesssys/constellation/cli/cloud/cloudtypes"
|
||||||
"github.com/edgelesssys/constellation/cli/ec2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConstellationState is the state of a Constellation.
|
// ConstellationState is the state of a Constellation.
|
||||||
|
@ -11,9 +10,6 @@ type ConstellationState struct {
|
||||||
UID string `json:"uid,omitempty"`
|
UID string `json:"uid,omitempty"`
|
||||||
CloudProvider string `json:"cloudprovider,omitempty"`
|
CloudProvider string `json:"cloudprovider,omitempty"`
|
||||||
|
|
||||||
EC2Instances ec2.Instances `json:"ec2instances,omitempty"`
|
|
||||||
EC2SecurityGroup string `json:"ec2securitygroup,omitempty"`
|
|
||||||
|
|
||||||
GCPNodes cloudtypes.Instances `json:"gcpnodes,omitempty"`
|
GCPNodes cloudtypes.Instances `json:"gcpnodes,omitempty"`
|
||||||
GCPCoordinators cloudtypes.Instances `json:"gcpcoordinators,omitempty"`
|
GCPCoordinators cloudtypes.Instances `json:"gcpcoordinators,omitempty"`
|
||||||
GCPNodeInstanceGroup string `json:"gcpnodeinstancegroup,omitempty"`
|
GCPNodeInstanceGroup string `json:"gcpnodeinstancegroup,omitempty"`
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue