diff --git a/api/v1beta1/awsmachine_conversion.go b/api/v1beta1/awsmachine_conversion.go index 92a22d5a59..55856591a9 100644 --- a/api/v1beta1/awsmachine_conversion.go +++ b/api/v1beta1/awsmachine_conversion.go @@ -39,6 +39,7 @@ func (src *AWSMachine) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.InstanceMetadataOptions = restored.Spec.InstanceMetadataOptions dst.Spec.PlacementGroupName = restored.Spec.PlacementGroupName dst.Spec.PrivateDNSName = restored.Spec.PrivateDNSName + dst.Spec.SecurityGroupOverrides = restored.Spec.SecurityGroupOverrides return nil } @@ -87,6 +88,7 @@ func (r *AWSMachineTemplate) ConvertTo(dstRaw conversion.Hub) error { dst.Spec.Template.Spec.InstanceMetadataOptions = restored.Spec.Template.Spec.InstanceMetadataOptions dst.Spec.Template.Spec.PlacementGroupName = restored.Spec.Template.Spec.PlacementGroupName dst.Spec.Template.Spec.PrivateDNSName = restored.Spec.Template.Spec.PrivateDNSName + dst.Spec.Template.Spec.SecurityGroupOverrides = restored.Spec.Template.Spec.SecurityGroupOverrides return nil } diff --git a/api/v1beta1/zz_generated.conversion.go b/api/v1beta1/zz_generated.conversion.go index 030941fb4f..ec3859c7f1 100644 --- a/api/v1beta1/zz_generated.conversion.go +++ b/api/v1beta1/zz_generated.conversion.go @@ -1399,6 +1399,7 @@ func autoConvert_v1beta2_AWSMachineSpec_To_v1beta1_AWSMachineSpec(in *v1beta2.AW } else { out.Subnet = nil } + // WARNING: in.SecurityGroupOverrides requires manual conversion: does not exist in peer-type out.SSHKeyName = (*string)(unsafe.Pointer(in.SSHKeyName)) out.RootVolume = (*Volume)(unsafe.Pointer(in.RootVolume)) out.NonRootVolumes = *(*[]Volume)(unsafe.Pointer(&in.NonRootVolumes)) diff --git a/api/v1beta2/awsmachine_types.go b/api/v1beta2/awsmachine_types.go index 1929c79e4d..dad3bb0575 100644 --- a/api/v1beta2/awsmachine_types.go +++ b/api/v1beta2/awsmachine_types.go @@ -114,6 +114,11 @@ type AWSMachineSpec struct { // +optional Subnet *AWSResourceReference `json:"subnet,omitempty"` + // SecurityGroupOverrides is an optional set of security groups to use for the node. + // This is optional - if not provided security groups from the cluster will be used. + // +optional + SecurityGroupOverrides map[SecurityGroupRole]string `json:"securityGroupOverrides,omitempty"` + // SSHKeyName is the name of the ssh key to attach to the instance. Valid values are empty string (do not use SSH keys), a valid SSH key name, or omitted (use the default SSH key name) // +optional SSHKeyName *string `json:"sshKeyName,omitempty"` diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go index 60ce90eeb7..b3cd28d608 100644 --- a/api/v1beta2/zz_generated.deepcopy.go +++ b/api/v1beta2/zz_generated.deepcopy.go @@ -700,6 +700,13 @@ func (in *AWSMachineSpec) DeepCopyInto(out *AWSMachineSpec) { *out = new(AWSResourceReference) (*in).DeepCopyInto(*out) } + if in.SecurityGroupOverrides != nil { + in, out := &in.SecurityGroupOverrides, &out.SecurityGroupOverrides + *out = make(map[SecurityGroupRole]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.SSHKeyName != nil { in, out := &in.SSHKeyName, &out.SSHKeyName *out = new(string) diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml index fca3f66245..f8e31edb56 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml @@ -848,6 +848,13 @@ spec: required: - size type: object + securityGroupOverrides: + additionalProperties: + type: string + description: SecurityGroupOverrides is an optional set of security + groups to use for the node. This is optional - if not provided security + groups from the cluster will be used. + type: object spotMarketOptions: description: SpotMarketOptions allows users to configure instances to be run using AWS Spot instances. diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml index ebdee36d0b..5ae1afd3a6 100644 --- a/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml +++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachinetemplates.yaml @@ -807,6 +807,14 @@ spec: required: - size type: object + securityGroupOverrides: + additionalProperties: + type: string + description: SecurityGroupOverrides is an optional set of + security groups to use for the node. This is optional - + if not provided security groups from the cluster will be + used. + type: object spotMarketOptions: description: SpotMarketOptions allows users to configure instances to be run using AWS Spot instances. diff --git a/controllers/awsmachine_controller_test.go b/controllers/awsmachine_controller_test.go index 6e4b26ffda..01122cad0e 100644 --- a/controllers/awsmachine_controller_test.go +++ b/controllers/awsmachine_controller_test.go @@ -530,10 +530,6 @@ func mockedCreateSecretCall(s *mock_services.MockSecretInterfaceMockRecorder) { func mockedCreateInstanceCalls(m *mocks.MockEC2APIMockRecorder) { m.DescribeInstancesWithContext(context.TODO(), gomock.Eq(&ec2.DescribeInstancesInput{ Filters: []*ec2.Filter{ - { - Name: aws.String("vpc-id"), - Values: aws.StringSlice([]string{""}), - }, { Name: aws.String("tag:sigs.k8s.io/cluster-api-provider-aws/cluster/test-cluster"), Values: aws.StringSlice([]string{"owned"}), @@ -642,10 +638,6 @@ func mockedCreateInstanceCalls(m *mocks.MockEC2APIMockRecorder) { Name: aws.String("state"), Values: aws.StringSlice([]string{"pending", "available"}), }, - { - Name: aws.String("vpc-id"), - Values: aws.StringSlice([]string{""}), - }, { Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"subnet-1"}), diff --git a/docs/book/src/topics/bring-your-own-aws-infrastructure.md b/docs/book/src/topics/bring-your-own-aws-infrastructure.md index 4841425158..bd157fa28f 100644 --- a/docs/book/src/topics/bring-your-own-aws-infrastructure.md +++ b/docs/book/src/topics/bring-your-own-aws-infrastructure.md @@ -120,6 +120,30 @@ spec: Users may either specify `failureDomain` on the Machine or MachineDeployment objects, _or_ users may explicitly specify subnet IDs on the AWSMachine or AWSMachineTemplate objects. If both are specified, the subnet ID is used and the `failureDomain` is ignored. +### Placing EC2 Instances in Specific External VPCs + +CAPA clusters are deployed within a single VPC, but it's possible to place machines that live in external VPCs. For this kind of configuration, we assume that all the VPCs have the ability to communicate, either through external peering, a transit gateway, or some other mechanism already established outside of CAPA. CAPA will not create a tunnel or manage the network configuration for any secondary VPCs. + +The AWSMachineTemplate `subnet` field allows specifying filters or specific subnet ids for worker machine placement. If the filters or subnet id is specified in a secondary VPC, CAPA will place the machine in that VPC and subnet. + +```yaml +spec: + template: + spec: + subnet: + filters: + name: "vpc-id" + values: + - "secondary-vpc-id" + securityGroupOverrides: + node: sg-04e870a3507a5ad2c5c8c2 + node-eks-additional: sg-04e870a3507a5ad2c5c8c1 +``` + +#### Caveats/Notes + +CAPA helpfully creates security groups for various roles in the cluster and automatically attaches them to workers. However, security groups are tied to a specific VPC, so workers placed in a VPC outside of the cluster will need to have these security groups created by some external process first and set in the `securityGroupOverrides` field, otherwise the ec2 creation will fail. + ### Security Groups To use existing security groups for instances for a cluster, add this to the AWSCluster specification: @@ -147,6 +171,15 @@ spec: - ... ``` +It's also possible to override the cluster security groups for an individual AWSMachine or AWSMachineTemplate: + +```yaml +spec: + SecurityGroupOverrides: + node: sg-04e870a3507a5ad2c5c8c2 + node-eks-additional: sg-04e870a3507a5ad2c5c8c1 +``` + ### Control Plane Load Balancer The cluster control plane is accessed through a Classic ELB. By default, Cluster API creates the Classic ELB. To use an existing Classic ELB, add its name to the AWSCluster specification: diff --git a/pkg/cloud/services/ec2/instances.go b/pkg/cloud/services/ec2/instances.go index 9392736f99..ad9a746a8d 100644 --- a/pkg/cloud/services/ec2/instances.go +++ b/pkg/cloud/services/ec2/instances.go @@ -45,7 +45,6 @@ func (s *Service) GetRunningInstanceByTags(scope *scope.MachineScope) (*infrav1. input := &ec2.DescribeInstancesInput{ Filters: []*ec2.Filter{ - filter.EC2.VPC(s.scope.VPC().ID), filter.EC2.ClusterOwned(s.scope.Name()), filter.EC2.Name(scope.Name()), filter.EC2.InstanceStates(ec2.InstanceStateNamePending, ec2.InstanceStateNameRunning), @@ -308,9 +307,6 @@ func (s *Service) findSubnet(scope *scope.MachineScope) (string, error) { criteria := []*ec2.Filter{ filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), } - if !scope.IsExternallyManaged() { - criteria = append(criteria, filter.EC2.VPC(s.scope.VPC().ID)) - } if scope.AWSMachine.Spec.Subnet.ID != nil { criteria = append(criteria, &ec2.Filter{Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{*scope.AWSMachine.Spec.Subnet.ID})}) } @@ -345,6 +341,11 @@ func (s *Service) findSubnet(scope *scope.MachineScope) (string, error) { } filtered = append(filtered, subnet) } + // prefer a subnet in the cluster VPC if multiple match + clusterVPC := s.scope.VPC().ID + sort.SliceStable(filtered, func(i, j int) bool { + return strings.Compare(*filtered[i].VpcId, clusterVPC) > strings.Compare(*filtered[j].VpcId, clusterVPC) + }) if len(filtered) == 0 { errMessage = fmt.Sprintf("failed to run machine %q, found %d subnets matching criteria but post-filtering failed.", scope.Name(), len(subnets)) + errMessage @@ -440,10 +441,15 @@ func (s *Service) GetCoreSecurityGroups(scope *scope.MachineScope) ([]string, er } ids := make([]string, 0, len(sgRoles)) for _, sg := range sgRoles { - if _, ok := s.scope.SecurityGroups()[sg]; !ok { - return nil, awserrors.NewFailedDependency(fmt.Sprintf("%s security group not available", sg)) + if _, ok := scope.AWSMachine.Spec.SecurityGroupOverrides[sg]; ok { + ids = append(ids, scope.AWSMachine.Spec.SecurityGroupOverrides[sg]) + continue } - ids = append(ids, s.scope.SecurityGroups()[sg].ID) + if _, ok := s.scope.SecurityGroups()[sg]; ok { + ids = append(ids, s.scope.SecurityGroups()[sg].ID) + continue + } + return nil, awserrors.NewFailedDependency(fmt.Sprintf("%s security group not available", sg)) } return ids, nil } diff --git a/pkg/cloud/services/ec2/instances_test.go b/pkg/cloud/services/ec2/instances_test.go index a90d5de5be..f68e4a5f5e 100644 --- a/pkg/cloud/services/ec2/instances_test.go +++ b/pkg/cloud/services/ec2/instances_test.go @@ -548,6 +548,401 @@ func TestCreateInstance(t *testing.T) { } }, }, + { + name: "when multiple subnets match filters, subnets in the cluster vpc are preferred", + machine: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"set": "node"}, + }, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + DataSecretName: aws.String("bootstrap-data"), + }, + FailureDomain: aws.String("us-east-1c"), + }, + }, + machineConfig: &infrav1.AWSMachineSpec{ + AMI: infrav1.AMIReference{ + ID: aws.String("abc"), + }, + InstanceType: "m5.2xlarge", + Subnet: &infrav1.AWSResourceReference{ + Filters: []infrav1.Filter{ + { + Name: "availability-zone", + Values: []string{"us-east-1c"}, + }, + }, + }, + UncompressedUserData: &isUncompressedFalse, + }, + awsCluster: &infrav1.AWSCluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: infrav1.AWSClusterSpec{ + NetworkSpec: infrav1.NetworkSpec{ + VPC: infrav1.VPCSpec{ + ID: "vpc-foo", + }, + Subnets: infrav1.Subnets{ + infrav1.SubnetSpec{ + ID: "subnet-1", + AvailabilityZone: "us-east-1a", + IsPublic: false, + }, + infrav1.SubnetSpec{ + ID: "subnet-2", + AvailabilityZone: "us-east-1b", + IsPublic: false, + }, + infrav1.SubnetSpec{ + ID: "subnet-3", + AvailabilityZone: "us-east-1c", + IsPublic: false, + }, + }, + }, + }, + Status: infrav1.AWSClusterStatus{ + Network: infrav1.NetworkStatus{ + SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ + infrav1.SecurityGroupControlPlane: { + ID: "1", + }, + infrav1.SecurityGroupNode: { + ID: "2", + }, + infrav1.SecurityGroupLB: { + ID: "3", + }, + }, + APIServerELB: infrav1.LoadBalancer{ + DNSName: "test-apiserver.us-east-1.aws", + }, + }, + }, + }, + expect: func(m *mocks.MockEC2APIMockRecorder) { + m. + DescribeInstanceTypesWithContext(context.TODO(), gomock.Eq(&ec2.DescribeInstanceTypesInput{ + InstanceTypes: []*string{ + aws.String("m5.2xlarge"), + }, + })). + Return(&ec2.DescribeInstanceTypesOutput{ + InstanceTypes: []*ec2.InstanceTypeInfo{ + { + ProcessorInfo: &ec2.ProcessorInfo{ + SupportedArchitectures: []*string{ + aws.String("x86_64"), + }, + }, + }, + }, + }, nil) + m.DescribeSubnetsWithContext(context.TODO(), gomock.Eq(&ec2.DescribeSubnetsInput{ + Filters: []*ec2.Filter{ + filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), + { + Name: aws.String("availability-zone"), + Values: aws.StringSlice([]string{"us-east-1c"}), + }, + }})).Return(&ec2.DescribeSubnetsOutput{ + Subnets: []*ec2.Subnet{ + { + VpcId: aws.String("vpc-bar"), + SubnetId: aws.String("subnet-4"), + AvailabilityZone: aws.String("us-east-1c"), + CidrBlock: aws.String("10.0.10.0/24"), + MapPublicIpOnLaunch: aws.Bool(false), + }, + { + VpcId: aws.String("vpc-foo"), + SubnetId: aws.String("subnet-3"), + AvailabilityZone: aws.String("us-east-1c"), + CidrBlock: aws.String("10.0.11.0/24"), + }, + }, + }, nil) + m. + RunInstancesWithContext(context.TODO(), &ec2.RunInstancesInput{ + ImageId: aws.String("abc"), + InstanceType: aws.String("m5.2xlarge"), + KeyName: aws.String("default"), + SecurityGroupIds: aws.StringSlice([]string{"2", "3"}), + SubnetId: aws.String("subnet-3"), + TagSpecifications: []*ec2.TagSpecification{ + { + ResourceType: aws.String("instance"), + Tags: []*ec2.Tag{ + { + Key: aws.String("MachineName"), + Value: aws.String("/"), + }, + { + Key: aws.String("Name"), + Value: aws.String("aws-test1"), + }, + { + Key: aws.String("kubernetes.io/cluster/test1"), + Value: aws.String("owned"), + }, + { + Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test1"), + Value: aws.String("owned"), + }, + { + Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), + Value: aws.String("node"), + }, + }, + }, + }, + UserData: aws.String(base64.StdEncoding.EncodeToString(userDataCompressed)), + MaxCount: aws.Int64(1), + MinCount: aws.Int64(1), + }).Return(&ec2.Reservation{ + Instances: []*ec2.Instance{ + { + State: &ec2.InstanceState{ + Name: aws.String(ec2.InstanceStateNamePending), + }, + IamInstanceProfile: &ec2.IamInstanceProfile{ + Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), + }, + InstanceId: aws.String("two"), + InstanceType: aws.String("m5.large"), + SubnetId: aws.String("subnet-3"), + ImageId: aws.String("ami-1"), + RootDeviceName: aws.String("device-1"), + BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ + { + DeviceName: aws.String("device-1"), + Ebs: &ec2.EbsInstanceBlockDevice{ + VolumeId: aws.String("volume-1"), + }, + }, + }, + Placement: &ec2.Placement{ + AvailabilityZone: &az, + }, + }, + }, + }, nil) + m. + DescribeNetworkInterfacesWithContext(context.TODO(), gomock.Any()). + Return(&ec2.DescribeNetworkInterfacesOutput{ + NetworkInterfaces: []*ec2.NetworkInterface{}, + NextToken: nil, + }, nil) + }, + check: func(instance *infrav1.Instance, err error) { + if err != nil { + t.Fatalf("did not expect error: %v", err) + } + + if instance.SubnetID != "subnet-3" { + t.Fatalf("expected subnet-3 from availability zone us-east-1c, got %q", instance.SubnetID) + } + }, + }, + { + name: "with a subnet outside the cluster vpc", + machine: &clusterv1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{"set": "node"}, + }, + Spec: clusterv1.MachineSpec{ + Bootstrap: clusterv1.Bootstrap{ + DataSecretName: aws.String("bootstrap-data"), + }, + FailureDomain: aws.String("us-east-1c"), + }, + }, + machineConfig: &infrav1.AWSMachineSpec{ + AMI: infrav1.AMIReference{ + ID: aws.String("abc"), + }, + InstanceType: "m5.2xlarge", + Subnet: &infrav1.AWSResourceReference{ + Filters: []infrav1.Filter{ + { + Name: "vpc-id", + Values: []string{"vpc-bar"}, + }, + { + Name: "availability-zone", + Values: []string{"us-east-1c"}, + }, + }, + }, + SecurityGroupOverrides: map[infrav1.SecurityGroupRole]string{ + infrav1.SecurityGroupNode: "4", + }, + UncompressedUserData: &isUncompressedFalse, + }, + awsCluster: &infrav1.AWSCluster{ + ObjectMeta: metav1.ObjectMeta{Name: "test"}, + Spec: infrav1.AWSClusterSpec{ + NetworkSpec: infrav1.NetworkSpec{ + VPC: infrav1.VPCSpec{ + ID: "vpc-foo", + }, + Subnets: infrav1.Subnets{ + infrav1.SubnetSpec{ + ID: "subnet-1", + AvailabilityZone: "us-east-1a", + IsPublic: false, + }, + infrav1.SubnetSpec{ + ID: "subnet-2", + AvailabilityZone: "us-east-1b", + IsPublic: false, + }, + infrav1.SubnetSpec{ + ID: "subnet-3", + AvailabilityZone: "us-east-1c", + IsPublic: false, + }, + }, + }, + }, + Status: infrav1.AWSClusterStatus{ + Network: infrav1.NetworkStatus{ + SecurityGroups: map[infrav1.SecurityGroupRole]infrav1.SecurityGroup{ + infrav1.SecurityGroupControlPlane: { + ID: "1", + }, + infrav1.SecurityGroupNode: { + ID: "2", + }, + infrav1.SecurityGroupLB: { + ID: "3", + }, + }, + APIServerELB: infrav1.LoadBalancer{ + DNSName: "test-apiserver.us-east-1.aws", + }, + }, + }, + }, + expect: func(m *mocks.MockEC2APIMockRecorder) { + m. + DescribeInstanceTypesWithContext(context.TODO(), gomock.Eq(&ec2.DescribeInstanceTypesInput{ + InstanceTypes: []*string{ + aws.String("m5.2xlarge"), + }, + })). + Return(&ec2.DescribeInstanceTypesOutput{ + InstanceTypes: []*ec2.InstanceTypeInfo{ + { + ProcessorInfo: &ec2.ProcessorInfo{ + SupportedArchitectures: []*string{ + aws.String("x86_64"), + }, + }, + }, + }, + }, nil) + m.DescribeSubnetsWithContext(context.TODO(), gomock.Eq(&ec2.DescribeSubnetsInput{ + Filters: []*ec2.Filter{ + filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), + filter.EC2.VPC("vpc-bar"), + { + Name: aws.String("availability-zone"), + Values: aws.StringSlice([]string{"us-east-1c"}), + }, + }})).Return(&ec2.DescribeSubnetsOutput{ + Subnets: []*ec2.Subnet{ + { + VpcId: aws.String("vpc-bar"), + SubnetId: aws.String("subnet-5"), + AvailabilityZone: aws.String("us-east-1c"), + CidrBlock: aws.String("10.0.11.0/24"), + }, + }, + }, nil) + m. + RunInstancesWithContext(context.TODO(), &ec2.RunInstancesInput{ + ImageId: aws.String("abc"), + InstanceType: aws.String("m5.2xlarge"), + KeyName: aws.String("default"), + SecurityGroupIds: aws.StringSlice([]string{"4", "3"}), + SubnetId: aws.String("subnet-5"), + TagSpecifications: []*ec2.TagSpecification{ + { + ResourceType: aws.String("instance"), + Tags: []*ec2.Tag{ + { + Key: aws.String("MachineName"), + Value: aws.String("/"), + }, + { + Key: aws.String("Name"), + Value: aws.String("aws-test1"), + }, + { + Key: aws.String("kubernetes.io/cluster/test1"), + Value: aws.String("owned"), + }, + { + Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/cluster/test1"), + Value: aws.String("owned"), + }, + { + Key: aws.String("sigs.k8s.io/cluster-api-provider-aws/role"), + Value: aws.String("node"), + }, + }, + }, + }, + UserData: aws.String(base64.StdEncoding.EncodeToString(userDataCompressed)), + MaxCount: aws.Int64(1), + MinCount: aws.Int64(1), + }).Return(&ec2.Reservation{ + Instances: []*ec2.Instance{ + { + State: &ec2.InstanceState{ + Name: aws.String(ec2.InstanceStateNamePending), + }, + IamInstanceProfile: &ec2.IamInstanceProfile{ + Arn: aws.String("arn:aws:iam::123456789012:instance-profile/foo"), + }, + InstanceId: aws.String("two"), + InstanceType: aws.String("m5.large"), + SubnetId: aws.String("subnet-5"), + ImageId: aws.String("ami-1"), + RootDeviceName: aws.String("device-1"), + BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{ + { + DeviceName: aws.String("device-1"), + Ebs: &ec2.EbsInstanceBlockDevice{ + VolumeId: aws.String("volume-1"), + }, + }, + }, + Placement: &ec2.Placement{ + AvailabilityZone: &az, + }, + }, + }, + }, nil) + m. + DescribeNetworkInterfacesWithContext(context.TODO(), gomock.Any()). + Return(&ec2.DescribeNetworkInterfacesOutput{ + NetworkInterfaces: []*ec2.NetworkInterface{}, + NextToken: nil, + }, nil) + }, + check: func(instance *infrav1.Instance, err error) { + if err != nil { + t.Fatalf("did not expect error: %v", err) + } + + if instance.SubnetID != "subnet-5" { + t.Fatalf("expected subnet-5 from availability zone us-east-1c, got %q", instance.SubnetID) + } + }, + }, { name: "with ImageLookupOrg specified at the machine level", machine: &clusterv1.Machine{ @@ -1074,7 +1469,6 @@ func TestCreateInstance(t *testing.T) { DescribeSubnetsWithContext(context.TODO(), &ec2.DescribeSubnetsInput{ Filters: []*ec2.Filter{ filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), - filter.EC2.VPC("vpc-id"), {Name: aws.String("tag:some-tag"), Values: aws.StringSlice([]string{"some-value"})}, }, }). @@ -1184,7 +1578,6 @@ func TestCreateInstance(t *testing.T) { DescribeSubnetsWithContext(context.TODO(), &ec2.DescribeSubnetsInput{ Filters: []*ec2.Filter{ filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), - filter.EC2.VPC("vpc-id"), {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"matching-subnet"})}, }, }). @@ -1328,7 +1721,6 @@ func TestCreateInstance(t *testing.T) { DescribeSubnetsWithContext(context.TODO(), &ec2.DescribeSubnetsInput{ Filters: []*ec2.Filter{ filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), - filter.EC2.VPC("vpc-id"), {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"non-matching-subnet"})}, }, }). @@ -1451,7 +1843,6 @@ func TestCreateInstance(t *testing.T) { DescribeSubnetsWithContext(context.TODO(), &ec2.DescribeSubnetsInput{ Filters: []*ec2.Filter{ filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), - filter.EC2.VPC("vpc-id"), {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"matching-subnet"})}, }, }). @@ -1532,7 +1923,6 @@ func TestCreateInstance(t *testing.T) { DescribeSubnetsWithContext(context.TODO(), &ec2.DescribeSubnetsInput{ Filters: []*ec2.Filter{ filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), - filter.EC2.VPC("vpc-id"), {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"subnet-1"})}, }, }). @@ -1713,7 +2103,6 @@ func TestCreateInstance(t *testing.T) { DescribeSubnetsWithContext(context.TODO(), &ec2.DescribeSubnetsInput{ Filters: []*ec2.Filter{ filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), - filter.EC2.VPC("vpc-id"), {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"public-subnet-1"})}, }, }). @@ -1843,7 +2232,6 @@ func TestCreateInstance(t *testing.T) { DescribeSubnetsWithContext(context.TODO(), &ec2.DescribeSubnetsInput{ Filters: []*ec2.Filter{ filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), - filter.EC2.VPC("vpc-id"), {Name: aws.String("subnet-id"), Values: aws.StringSlice([]string{"private-subnet-1"})}, }, }). @@ -1968,7 +2356,6 @@ func TestCreateInstance(t *testing.T) { DescribeSubnetsWithContext(context.TODO(), &ec2.DescribeSubnetsInput{ Filters: []*ec2.Filter{ filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable), - filter.EC2.VPC("vpc-id"), {Name: aws.String("tag:some-tag"), Values: aws.StringSlice([]string{"some-value"})}, }, }).