Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions api/v1alpha2/linodemachine_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ type LinodeMachineSpec struct {
// +optional
VPCID *int `json:"vpcID,omitempty"`

// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
// IPv6Options defines the IPv6 options for the instance.
// If not specified, IPv6 ranges won't be allocated to instance.
// +optional
IPv6Options *IPv6CreateOptions `json:"ipv6Options,omitempty"`

// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
// +optional
// NetworkHelper is an option usually enabled on account level. It helps configure networking automatically for instances.
Expand All @@ -121,6 +127,31 @@ type LinodeMachineSpec struct {
NetworkHelper *bool `json:"networkHelper,omitempty"`
}

// IPv6CreateOptions defines the IPv6 options for the instance.
type IPv6CreateOptions struct {
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
// EnableSLAAC is an option to enable SLAAC (Stateless Address Autoconfiguration) for the instance.
// This is useful for IPv6 addresses, allowing the instance to automatically configure its own IPv6 address.
// Defaults to false.
// +optional
EnableSLAAC *bool `json:"enableSLAAC,omitempty"`

// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
// EnableRanges is an option to enable IPv6 ranges for the instance.
// If set to true, the instance will have a range of IPv6 addresses.
// This is useful for instances that require multiple IPv6 addresses.
// Defaults to false.
// +optional
EnableRanges *bool `json:"enableRanges,omitempty"`

// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
// IsPublicIPv6 is an option to enable public IPv6 for the instance.
// If set to true, the instance will have a publicly routable IPv6 range.
// Defaults to false.
// +optional
IsPublicIPv6 *bool `json:"isPublicIPv6,omitempty"`
}

// InstanceDisk defines a list of disks to use for an instance
type InstanceDisk struct {
// DiskID is the linode assigned ID of the disk
Expand Down
35 changes: 35 additions & 0 deletions api/v1alpha2/zz_generated.deepcopy.go

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

Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,43 @@ spec:
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
ipv6Options:
description: |-
IPv6Options defines the IPv6 options for the instance.
If not specified, IPv6 ranges won't be allocated to instance.
properties:
enableRanges:
description: |-
EnableRanges is an option to enable IPv6 ranges for the instance.
If set to true, the instance will have a range of IPv6 addresses.
This is useful for instances that require multiple IPv6 addresses.
Defaults to false.
type: boolean
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
enableSLAAC:
description: |-
EnableSLAAC is an option to enable SLAAC (Stateless Address Autoconfiguration) for the instance.
This is useful for IPv6 addresses, allowing the instance to automatically configure its own IPv6 address.
Defaults to false.
type: boolean
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
isPublicIPv6:
description: |-
IsPublicIPv6 is an option to enable public IPv6 for the instance.
If set to true, the instance will have a publicly routable IPv6 range.
Defaults to false.
type: boolean
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
type: object
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
networkHelper:
description: |-
NetworkHelper is an option usually enabled on account level. It helps configure networking automatically for instances.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,43 @@ spec:
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
ipv6Options:
description: |-
IPv6Options defines the IPv6 options for the instance.
If not specified, IPv6 ranges won't be allocated to instance.
properties:
enableRanges:
description: |-
EnableRanges is an option to enable IPv6 ranges for the instance.
If set to true, the instance will have a range of IPv6 addresses.
This is useful for instances that require multiple IPv6 addresses.
Defaults to false.
type: boolean
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
enableSLAAC:
description: |-
EnableSLAAC is an option to enable SLAAC (Stateless Address Autoconfiguration) for the instance.
This is useful for IPv6 addresses, allowing the instance to automatically configure its own IPv6 address.
Defaults to false.
type: boolean
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
isPublicIPv6:
description: |-
IsPublicIPv6 is an option to enable public IPv6 for the instance.
If set to true, the instance will have a publicly routable IPv6 range.
Defaults to false.
type: boolean
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
type: object
x-kubernetes-validations:
- message: Value is immutable
rule: self == oldSelf
networkHelper:
description: |-
NetworkHelper is an option usually enabled on account level. It helps configure networking automatically for instances.
Expand Down
19 changes: 19 additions & 0 deletions docs/src/reference/out.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,24 @@ _Appears in:_
| `format` _object (keys:string, values:string)_ | How to format the data stored in the generated Secret.<br />It supports Go template syntax and interpolating the following values: .AccessKey, .SecretKey .BucketName .BucketEndpoint .S3Endpoint<br />If no format is supplied then a generic one is used containing the values specified. | | |


#### IPv6CreateOptions



IPv6CreateOptions defines the IPv6 options for the instance.



_Appears in:_
- [LinodeMachineSpec](#linodemachinespec)

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `enableSLAAC` _boolean_ | EnableSLAAC is an option to enable SLAAC (Stateless Address Autoconfiguration) for the instance.<br />This is useful for IPv6 addresses, allowing the instance to automatically configure its own IPv6 address.<br />Defaults to false. | | |
| `enableRanges` _boolean_ | EnableRanges is an option to enable IPv6 ranges for the instance.<br />If set to true, the instance will have a range of IPv6 addresses.<br />This is useful for instances that require multiple IPv6 addresses.<br />Defaults to false. | | |
| `isPublicIPv6` _boolean_ | IsPublicIPv6 is an option to enable public IPv6 for the instance.<br />If set to true, the instance will have a publicly routable IPv6 range.<br />Defaults to false. | | |


#### InstanceConfigInterfaceCreateOptions


Expand Down Expand Up @@ -621,6 +639,7 @@ _Appears in:_
| `firewallRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#objectreference-v1-core)_ | FirewallRef is a reference to a firewall object. This makes the linode use the specified firewall. | | |
| `vpcRef` _[ObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#objectreference-v1-core)_ | VPCRef is a reference to a LinodeVPC resource. If specified, this takes precedence over<br />the cluster-level VPC configuration for multi-region support. | | |
| `vpcID` _integer_ | VPCID is the ID of an existing VPC in Linode. This allows using a VPC that is not managed by CAPL. | | |
| `ipv6Options` _[IPv6CreateOptions](#ipv6createoptions)_ | IPv6Options defines the IPv6 options for the instance.<br />If not specified, IPv6 ranges won't be allocated to instance. | | |
| `networkHelper` _boolean_ | NetworkHelper is an option usually enabled on account level. It helps configure networking automatically for instances.<br />You can use this to enable/disable the network helper for a specific instance.<br />For more information, see https://techdocs.akamai.com/cloud-computing/docs/automatically-configure-networking<br />Defaults to true. | | |


Expand Down
89 changes: 56 additions & 33 deletions internal/controller/linodemachine_controller_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,12 +463,12 @@ func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope

subnetName := machineScope.LinodeCluster.Spec.Network.SubnetName // name of subnet to use

var ipv6RangeConfig []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range
var ipv6Config *linodego.InstanceConfigInterfaceCreateOptionsIPv6
if subnetName != "" {
for _, subnet := range linodeVPC.Spec.Subnets {
if subnet.Label == subnetName {
subnetID = subnet.SubnetID
ipv6RangeConfig = machineIPv6RangeConfig(len(subnet.IPv6))
ipv6Config = getMachineIPv6Config(machineScope, len(subnet.IPv6))
break
}
}
Expand All @@ -478,7 +478,7 @@ func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope
}
} else {
subnetID = linodeVPC.Spec.Subnets[0].SubnetID // get first subnet if nothing specified
ipv6RangeConfig = machineIPv6RangeConfig(len(linodeVPC.Spec.Subnets[0].IPv6))
ipv6Config = getMachineIPv6Config(machineScope, len(linodeVPC.Spec.Subnets[0].IPv6))
}

if subnetID == 0 {
Expand All @@ -488,10 +488,9 @@ func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope
for i, netInterface := range interfaces {
if netInterface.Purpose == linodego.InterfacePurposeVPC {
interfaces[i].SubnetID = &subnetID
if len(ipv6RangeConfig) > 0 {
interfaces[i].IPv6 = &linodego.InstanceConfigInterfaceCreateOptionsIPv6{
Ranges: ipv6RangeConfig,
}
// If IPv6 range config is not empty, add it to the interface configuration
if !isIPv6ConfigEmpty(ipv6Config) {
interfaces[i].IPv6 = ipv6Config
}
return nil, nil //nolint:nilnil // it is important we don't return an interface if a VPC interface already exists
}
Expand All @@ -506,11 +505,9 @@ func getVPCInterfaceConfig(ctx context.Context, machineScope *scope.MachineScope
},
}

// If IPv6 range config is not empty, add it to the interface configuration
if len(ipv6RangeConfig) > 0 {
vpcIntfCreateOpts.IPv6 = &linodego.InstanceConfigInterfaceCreateOptionsIPv6{
Ranges: ipv6RangeConfig,
}
// If IPv6 config is not empty, add it to the interface configuration
if !isIPv6ConfigEmpty(ipv6Config) {
vpcIntfCreateOpts.IPv6 = ipv6Config
}

return vpcIntfCreateOpts, nil
Expand Down Expand Up @@ -538,12 +535,12 @@ func getVPCInterfaceConfigFromDirectID(ctx context.Context, machineScope *scope.
}

// If subnet name specified, find matching subnet; otherwise use first subnet
var ipv6RangeConfig []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range
var ipv6Config *linodego.InstanceConfigInterfaceCreateOptionsIPv6
if subnetName != "" {
for _, subnet := range vpc.Subnets {
if subnet.Label == subnetName {
subnetID = subnet.ID
ipv6RangeConfig = machineIPv6RangeConfig(len(subnet.IPv6))
ipv6Config = getMachineIPv6Config(machineScope, len(subnet.IPv6))
break
}
}
Expand All @@ -552,17 +549,15 @@ func getVPCInterfaceConfigFromDirectID(ctx context.Context, machineScope *scope.
}
} else {
subnetID = vpc.Subnets[0].ID
ipv6RangeConfig = machineIPv6RangeConfig(len(vpc.Subnets[0].IPv6))
ipv6Config = getMachineIPv6Config(machineScope, len(vpc.Subnets[0].IPv6))
}

// Check if a VPC interface already exists
for i, netInterface := range interfaces {
if netInterface.Purpose == linodego.InterfacePurposeVPC {
interfaces[i].SubnetID = &subnetID
if len(ipv6RangeConfig) > 0 {
interfaces[i].IPv6 = &linodego.InstanceConfigInterfaceCreateOptionsIPv6{
Ranges: ipv6RangeConfig,
}
if !isIPv6ConfigEmpty(ipv6Config) {
interfaces[i].IPv6 = ipv6Config
}
return nil, nil //nolint:nilnil // it is important we don't return an interface if a VPC interface already exists
}
Expand All @@ -579,27 +574,55 @@ func getVPCInterfaceConfigFromDirectID(ctx context.Context, machineScope *scope.
}

// If IPv6 range config is not empty, add it to the interface configuration
if len(ipv6RangeConfig) > 0 {
vpcIntfCreateOpts.IPv6 = &linodego.InstanceConfigInterfaceCreateOptionsIPv6{
Ranges: ipv6RangeConfig,
}
if !isIPv6ConfigEmpty(ipv6Config) {
vpcIntfCreateOpts.IPv6 = ipv6Config
}

return vpcIntfCreateOpts, nil
}

// machineIPv6RangeConfig returns the IPv6 range configuration if subnet has IPv6 ranges.
// For now, we support only a single IPv6 range for machine per subnet.
// If this changes, we may need to adjust this logic.
func machineIPv6RangeConfig(numIPv6RangesInSubnet int) []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range {
if numIPv6RangesInSubnet == 0 {
return nil // No IPv6 ranges available in subnet, return empty slice
// isIPv6ConfigEmpty checks if the IPv6 configuration is empty.
func isIPv6ConfigEmpty(opts *linodego.InstanceConfigInterfaceCreateOptionsIPv6) bool {
return opts == nil ||
len(opts.SLAAC) == 0 &&
len(opts.Ranges) == 0 &&
(opts.IsPublic == nil)
}

// getMachineIPv6Config returns the IPv6 configuration for a LinodeMachine.
// It checks the LinodeMachine's IPv6Options for SLAAC and Ranges settings.
// If `EnableSLAAC` is set, it will enable SLAAC with the default IPv6 CIDR range.
// If `EnableRanges` is set, it will enable IPv6 ranges with the default IPv6 CIDR range.
// If `IsPublicIPv6` is set, it will be used to determine if the IPv6 range should be publicly routable or not.
func getMachineIPv6Config(machineScope *scope.MachineScope, numIPv6RangesInSubnet int) *linodego.InstanceConfigInterfaceCreateOptionsIPv6 {
intfOpts := &linodego.InstanceConfigInterfaceCreateOptionsIPv6{}

// If there are no IPv6 ranges in the subnet or if IPv6 options are not specified, return nil.
if numIPv6RangesInSubnet == 0 || machineScope.LinodeMachine.Spec.IPv6Options == nil {
return intfOpts
}
return []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range{
{
Range: ptr.To(defaultNodeIPv6CIDRRange),
},

if machineScope.LinodeMachine.Spec.IPv6Options.IsPublicIPv6 != nil {
// Set the public IPv6 flag based on the IsPublicIPv6 specification.
intfOpts.IsPublic = machineScope.LinodeMachine.Spec.IPv6Options.IsPublicIPv6
}

if machineScope.LinodeMachine.Spec.IPv6Options.EnableSLAAC != nil && *machineScope.LinodeMachine.Spec.IPv6Options.EnableSLAAC {
intfOpts.SLAAC = []linodego.InstanceConfigInterfaceCreateOptionsIPv6SLAAC{
{
Range: defaultNodeIPv6CIDRRange,
},
}
}
if machineScope.LinodeMachine.Spec.IPv6Options.EnableRanges != nil && *machineScope.LinodeMachine.Spec.IPv6Options.EnableRanges {
intfOpts.Ranges = []linodego.InstanceConfigInterfaceCreateOptionsIPv6Range{
{
Range: ptr.To(defaultNodeIPv6CIDRRange),
},
}
}

return intfOpts
}

func linodeMachineSpecToInstanceCreateConfig(machineSpec infrav1alpha2.LinodeMachineSpec, machineTags []string) *linodego.InstanceCreateOptions {
Expand Down
Loading
Loading