Skip to content

Commit

Permalink
Add AWSMachine fields to control vpc placement for the instance
Browse files Browse the repository at this point in the history
  • Loading branch information
cnmcavoy committed Jan 29, 2024
1 parent e10643a commit 0fff22f
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 5 deletions.
4 changes: 4 additions & 0 deletions api/v1beta1/awsmachine_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ 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.VPC = restored.Spec.VPC
dst.Spec.SecurityGroupOverrides = restored.Spec.SecurityGroupOverrides

return nil
}
Expand Down Expand Up @@ -87,6 +89,8 @@ 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.VPC = restored.Spec.Template.Spec.VPC
dst.Spec.Template.Spec.SecurityGroupOverrides = restored.Spec.Template.Spec.SecurityGroupOverrides

return nil
}
Expand Down
2 changes: 2 additions & 0 deletions api/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions api/v1beta2/awsmachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,21 @@ type AWSMachineSpec struct {
// +optional
AdditionalSecurityGroups []AWSResourceReference `json:"additionalSecurityGroups,omitempty"`

// VPC is a reference to the VPC to use when picking a subnet to use for this
// instance. Only valid if the subnet (id or filters) is also specified.
// +optional
VPC *AWSResourceReference `json:"vpc,omitempty"`

// Subnet is a reference to the subnet to use for this instance. If not specified,
// the cluster subnet will be used.
// +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"`
Expand Down
12 changes: 12 additions & 0 deletions api/v1beta2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 37 additions & 0 deletions config/crd/bases/infrastructure.cluster.x-k8s.io_awsmachines.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -905,6 +912,36 @@ spec:
built-in support for gzip-compressed user data user data stored
in aws secret manager is always gzip-compressed.
type: boolean
vpc:
description: VPC is a reference to the VPC to use when picking a subnet
to use for this instance. Only valid if the subnet (id or filters)
is also specified.
properties:
filters:
description: 'Filters is a set of key/value pairs used to identify
a resource They are applied according to the rules defined by
the AWS API: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Filtering.html'
items:
description: Filter is a filter used to identify an AWS resource.
properties:
name:
description: Name of the filter. Filter names are case-sensitive.
type: string
values:
description: Values includes one or more filter values.
Filter values are case-sensitive.
items:
type: string
type: array
required:
- name
- values
type: object
type: array
id:
description: ID of resource
type: string
type: object
required:
- instanceType
type: object
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -868,6 +876,38 @@ spec:
cloud-init has built-in support for gzip-compressed user
data user data stored in aws secret manager is always gzip-compressed.
type: boolean
vpc:
description: VPC is a reference to the VPC to use when picking
a subnet to use for this instance. Only valid if the subnet
(id or filters) is also specified.
properties:
filters:
description: 'Filters is a set of key/value pairs used
to identify a resource They are applied according to
the rules defined by the AWS API: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/Using_Filtering.html'
items:
description: Filter is a filter used to identify an
AWS resource.
properties:
name:
description: Name of the filter. Filter names are
case-sensitive.
type: string
values:
description: Values includes one or more filter
values. Filter values are case-sensitive.
items:
type: string
type: array
required:
- name
- values
type: object
type: array
id:
description: ID of resource
type: string
type: object
required:
- instanceType
type: object
Expand Down
47 changes: 42 additions & 5 deletions pkg/cloud/services/ec2/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,13 @@ import (
func (s *Service) GetRunningInstanceByTags(scope *scope.MachineScope) (*infrav1.Instance, error) {
s.scope.Debug("Looking for existing machine instance by tags")

vpcID, err := s.getVPCID(scope)
if err != nil {
return nil, err
}
input := &ec2.DescribeInstancesInput{
Filters: []*ec2.Filter{
filter.EC2.VPC(s.scope.VPC().ID),
filter.EC2.VPC(vpcID),
filter.EC2.ClusterOwned(s.scope.Name()),
filter.EC2.Name(scope.Name()),
filter.EC2.InstanceStates(ec2.InstanceStateNamePending, ec2.InstanceStateNameRunning),
Expand Down Expand Up @@ -309,7 +313,11 @@ func (s *Service) findSubnet(scope *scope.MachineScope) (string, error) {
filter.EC2.SubnetStates(ec2.SubnetStatePending, ec2.SubnetStateAvailable),
}
if !scope.IsExternallyManaged() {
criteria = append(criteria, filter.EC2.VPC(s.scope.VPC().ID))
vpcID, err := s.getVPCID(scope)
if err != nil {
return "", fmt.Errorf("failed to lookup vpc for subnets: %w", err)
}
criteria = append(criteria, filter.EC2.VPC(vpcID))
}
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})})
Expand Down Expand Up @@ -395,6 +403,30 @@ func (s *Service) findSubnet(scope *scope.MachineScope) (string, error) {
}
}

func (s *Service) getVPCID(scope *scope.MachineScope) (string, error) {
if scope.AWSMachine.Spec.VPC == nil {
return s.scope.VPC().ID, nil
}
if scope.AWSMachine.Spec.VPC.ID != nil {
return *scope.AWSMachine.Spec.VPC.ID, nil
}
filters := make([]*ec2.Filter, 0, len(scope.AWSMachine.Spec.VPC.Filters))
for _, filter := range scope.AWSMachine.Spec.VPC.Filters {
filters = append(filters, &ec2.Filter{
Name: aws.String(filter.Name),
Values: aws.StringSlice(filter.Values),
})
}
out, err := s.EC2Client.DescribeVpcsWithContext(context.TODO(), &ec2.DescribeVpcsInput{Filters: filters})
if err != nil {
return "", err
}
if out != nil && len(out.Vpcs) == 1 {
return *out.Vpcs[0].VpcId, nil
}
return "", fmt.Errorf("ambigious VPC filters, only one VPC expected, got %v", out)
}

// getFilteredSubnets fetches subnets filtered based on the criteria passed.
func (s *Service) getFilteredSubnets(criteria ...*ec2.Filter) ([]*ec2.Subnet, error) {
out, err := s.EC2Client.DescribeSubnetsWithContext(context.TODO(), &ec2.DescribeSubnetsInput{Filters: criteria})
Expand Down Expand Up @@ -440,10 +472,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
}
Expand Down
Loading

0 comments on commit 0fff22f

Please sign in to comment.