diff --git a/api/v1alpha2/linodemachine_types.go b/api/v1alpha2/linodemachine_types.go
index 20821eeae..e0cf6ad27 100644
--- a/api/v1alpha2/linodemachine_types.go
+++ b/api/v1alpha2/linodemachine_types.go
@@ -111,6 +111,14 @@ type LinodeMachineSpec struct {
// VPCID is the ID of an existing VPC in Linode. This allows using a VPC that is not managed by CAPL.
// +optional
VPCID *int `json:"vpcID,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.
+ // You can use this to enable/disable the network helper for a specific instance.
+ // For more information, see https://techdocs.akamai.com/cloud-computing/docs/automatically-configure-networking
+ // Defaults to true.
+ NetworkHelper *bool `json:"networkHelper,omitempty"`
}
// InstanceDisk defines a list of disks to use for an instance
diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go
index a5abe3618..50b06c517 100644
--- a/api/v1alpha2/zz_generated.deepcopy.go
+++ b/api/v1alpha2/zz_generated.deepcopy.go
@@ -878,6 +878,11 @@ func (in *LinodeMachineSpec) DeepCopyInto(out *LinodeMachineSpec) {
*out = new(int)
**out = **in
}
+ if in.NetworkHelper != nil {
+ in, out := &in.NetworkHelper, &out.NetworkHelper
+ *out = new(bool)
+ **out = **in
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LinodeMachineSpec.
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml
index 0b0bb5d64..d668d998b 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachines.yaml
@@ -264,6 +264,16 @@ spec:
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.
+ You can use this to enable/disable the network helper for a specific instance.
+ For more information, see https://techdocs.akamai.com/cloud-computing/docs/automatically-configure-networking
+ Defaults to true.
+ type: boolean
+ x-kubernetes-validations:
+ - message: Value is immutable
+ rule: self == oldSelf
osDisk:
description: |-
OSDisk is configuration for the root disk that includes the OS,
diff --git a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml
index 4b0577d11..0ccaf93ba 100644
--- a/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml
+++ b/config/crd/bases/infrastructure.cluster.x-k8s.io_linodemachinetemplates.yaml
@@ -256,6 +256,16 @@ spec:
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.
+ You can use this to enable/disable the network helper for a specific instance.
+ For more information, see https://techdocs.akamai.com/cloud-computing/docs/automatically-configure-networking
+ Defaults to true.
+ type: boolean
+ x-kubernetes-validations:
+ - message: Value is immutable
+ rule: self == oldSelf
osDisk:
description: |-
OSDisk is configuration for the root disk that includes the OS,
diff --git a/docs/src/reference/out.md b/docs/src/reference/out.md
index c1043e715..500ef39fb 100644
--- a/docs/src/reference/out.md
+++ b/docs/src/reference/out.md
@@ -621,6 +621,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
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. | | |
+| `networkHelper` _boolean_ | NetworkHelper is an option usually enabled on account level. It helps configure networking automatically for instances.
You can use this to enable/disable the network helper for a specific instance.
For more information, see https://techdocs.akamai.com/cloud-computing/docs/automatically-configure-networking
Defaults to true. | | |
#### LinodeMachineStatus
diff --git a/internal/controller/linodemachine_controller.go b/internal/controller/linodemachine_controller.go
index 740afe252..6405f3099 100644
--- a/internal/controller/linodemachine_controller.go
+++ b/internal/controller/linodemachine_controller.go
@@ -618,18 +618,35 @@ func (r *LinodeMachineReconciler) reconcilePreflightConfigure(ctx context.Contex
})
return ctrl.Result{RequeueAfter: reconciler.DefaultMachineControllerWaitForRunningDelay}, nil
}
+
+ configData := linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{
+ Network: true,
+ },
+ }
+
if machineScope.LinodeMachine.Spec.Configuration != nil && machineScope.LinodeMachine.Spec.Configuration.Kernel != "" {
- instanceConfig, err := getDefaultInstanceConfig(ctx, machineScope, instanceID)
- if err != nil {
- logger.Error(err, "Failed to get default instance configuration")
- return retryIfTransient(err, logger)
- }
+ configData.Kernel = machineScope.LinodeMachine.Spec.Configuration.Kernel
+ }
- if _, err := machineScope.LinodeClient.UpdateInstanceConfig(ctx, instanceID, instanceConfig.ID, linodego.InstanceConfigUpdateOptions{Kernel: machineScope.LinodeMachine.Spec.Configuration.Kernel}); err != nil {
- logger.Error(err, "Failed to update default instance configuration")
- return retryIfTransient(err, logger)
+ // For cases where the network helper is not enabled on account level, we can enable it per instance level
+ // Default is true, so we only need to update if it's explicitly set to false
+ if machineScope.LinodeMachine.Spec.NetworkHelper != nil {
+ configData.Helpers = &linodego.InstanceConfigHelpers{
+ Network: *machineScope.LinodeMachine.Spec.NetworkHelper,
}
}
+
+ instanceConfig, err := getDefaultInstanceConfig(ctx, machineScope, instanceID)
+ if err != nil {
+ logger.Error(err, "Failed to get default instance configuration")
+ return retryIfTransient(err, logger)
+ }
+ if _, err := machineScope.LinodeClient.UpdateInstanceConfig(ctx, instanceID, instanceConfig.ID, configData); err != nil {
+ logger.Error(err, "Failed to update default instance configuration")
+ return retryIfTransient(err, logger)
+ }
+
conditions.Set(machineScope.LinodeMachine, metav1.Condition{
Type: ConditionPreflightConfigured,
Status: metav1.ConditionTrue,
diff --git a/internal/controller/linodemachine_controller_test.go b/internal/controller/linodemachine_controller_test.go
index ccb94805c..f01fbcfe0 100644
--- a/internal/controller/linodemachine_controller_test.go
+++ b/internal/controller/linodemachine_controller_test.go
@@ -229,6 +229,17 @@ var _ = Describe("create", Label("machine", "create"), func() {
createInst := mockLinodeClient.EXPECT().
OnAfterResponse(gomock.Any()).
Return()
+ listInstConfs := mockLinodeClient.EXPECT().
+ ListInstanceConfigs(ctx, 123, gomock.Any()).
+ After(createInst).
+ Return([]linodego.InstanceConfig{{
+ ID: 1,
+ }}, nil)
+ mockLinodeClient.EXPECT().UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{Network: true},
+ }).
+ After(listInstConfs).
+ Return(nil, nil)
bootInst := mockLinodeClient.EXPECT().
BootInstance(ctx, 123, 0).
After(createInst).
@@ -374,6 +385,17 @@ var _ = Describe("create", Label("machine", "create"), func() {
createInst := mockLinodeClient.EXPECT().
OnAfterResponse(gomock.Any()).
Return()
+ listInstConfs := mockLinodeClient.EXPECT().
+ ListInstanceConfigs(ctx, 123, gomock.Any()).
+ After(createInst).
+ Return([]linodego.InstanceConfig{{
+ ID: 1,
+ }}, nil)
+ mockLinodeClient.EXPECT().UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{Network: true},
+ }).
+ After(listInstConfs).
+ Return(nil, nil)
bootInst := mockLinodeClient.EXPECT().
BootInstance(ctx, 123, 0).
After(createInst).
@@ -441,6 +463,17 @@ var _ = Describe("create", Label("machine", "create"), func() {
mockLinodeClient.EXPECT().
OnAfterResponse(gomock.Any()).
Return()
+ listInstConfs := mockLinodeClient.EXPECT().
+ ListInstanceConfigs(ctx, 123, gomock.Any()).
+ After(createInst).
+ Return([]linodego.InstanceConfig{{
+ ID: 1,
+ }}, nil)
+ mockLinodeClient.EXPECT().UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{Network: true},
+ }).
+ After(listInstConfs).
+ Return(nil, nil)
bootInst := mockLinodeClient.EXPECT().
BootInstance(ctx, 123, 0).
After(createInst).
@@ -527,6 +560,17 @@ var _ = Describe("create", Label("machine", "create"), func() {
mockLinodeClient.EXPECT().
OnAfterResponse(gomock.Any()).
Return()
+ listInstConfs := mockLinodeClient.EXPECT().
+ ListInstanceConfigs(ctx, 123, gomock.Any()).
+ After(createInst).
+ Return([]linodego.InstanceConfig{{
+ ID: 1,
+ }}, nil)
+ mockLinodeClient.EXPECT().UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{Network: true},
+ }).
+ After(listInstConfs).
+ Return(nil, nil)
bootInst := mockLinodeClient.EXPECT().
BootInstance(ctx, 123, 0).
After(listInst).
@@ -727,7 +771,7 @@ var _ = Describe("create", Label("machine", "create"), func() {
GetImage(ctx, gomock.Any()).
After(getRegion).
Return(&linodego.Image{Capabilities: []string{"cloud-init"}}, nil)
- createInst := mockLinodeClient.EXPECT().
+ mockLinodeClient.EXPECT().
CreateInstance(ctx, gomock.Any()).
After(getImage).
Return(&linodego.Instance{
@@ -739,17 +783,19 @@ var _ = Describe("create", Label("machine", "create"), func() {
mockLinodeClient.EXPECT().
OnAfterResponse(gomock.Any()).
Return()
- listInstConfs := mockLinodeClient.EXPECT().
+ mockLinodeClient.EXPECT().
ListInstanceConfigs(ctx, 123, gomock.Any()).
- After(createInst).
Return([]linodego.InstanceConfig{{
+ ID: 1,
Devices: &linodego.InstanceConfigDeviceMap{
SDA: &linodego.InstanceConfigDevice{DiskID: 100},
},
- }}, nil).MaxTimes(2)
+ }}, nil).MaxTimes(3)
+ mockLinodeClient.EXPECT().UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{Network: true},
+ }).Return(nil, nil)
getInstDisk := mockLinodeClient.EXPECT().
GetInstanceDisk(ctx, 123, 100).
- After(listInstConfs).
Return(&linodego.InstanceDisk{ID: 100, Size: 15000}, nil)
resizeInstDisk := mockLinodeClient.EXPECT().
ResizeInstanceDisk(ctx, 123, 100, 4262).
@@ -767,12 +813,13 @@ var _ = Describe("create", Label("machine", "create"), func() {
ListInstanceConfigs(ctx, 123, gomock.Any()).
After(createEtcdDisk).
Return([]linodego.InstanceConfig{{
+ ID: 1,
Devices: &linodego.InstanceConfigDeviceMap{
SDA: &linodego.InstanceConfigDevice{DiskID: 100},
},
- }}, nil).MaxTimes(2)
+ }}, nil).MaxTimes(3)
createInstanceProfile := mockLinodeClient.EXPECT().
- UpdateInstanceConfig(ctx, 123, 0, linodego.InstanceConfigUpdateOptions{
+ UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
Devices: &linodego.InstanceConfigDeviceMap{
SDA: &linodego.InstanceConfigDevice{DiskID: 100},
SDB: &linodego.InstanceConfigDevice{DiskID: 101},
@@ -890,10 +937,16 @@ var _ = Describe("create", Label("machine", "create"), func() {
ListInstanceConfigs(ctx, 123, gomock.Any()).
After(createInst).
Return([]linodego.InstanceConfig{{
+ ID: 1,
Devices: &linodego.InstanceConfigDeviceMap{
SDA: &linodego.InstanceConfigDevice{DiskID: 100},
},
}}, nil)
+ mockLinodeClient.EXPECT().UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{Network: true},
+ }).
+ After(listInstConfs).
+ Return(nil, nil)
getInstDisk := mockLinodeClient.EXPECT().
GetInstanceDisk(ctx, 123, 100).
After(listInstConfs).
@@ -933,6 +986,17 @@ var _ = Describe("create", Label("machine", "create"), func() {
Expect(rutil.ConditionTrue(&linodeMachine, ConditionPreflightConfigured)).To(BeFalse())
Expect(rutil.ConditionTrue(&linodeMachine, ConditionPreflightAdditionalDisksCreated)).To(BeFalse())
+ mockLinodeClient.EXPECT().
+ ListInstanceConfigs(ctx, 123, gomock.Any()).
+ Return([]linodego.InstanceConfig{{
+ ID: 1,
+ Devices: &linodego.InstanceConfigDeviceMap{
+ SDA: &linodego.InstanceConfigDevice{DiskID: 100},
+ },
+ }}, nil).AnyTimes()
+ mockLinodeClient.EXPECT().UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{Network: true},
+ }).Return(nil, nil).AnyTimes()
getInst := mockLinodeClient.EXPECT().
GetInstance(ctx, 123).
After(createFailedEtcdDisk).
@@ -954,12 +1018,13 @@ var _ = Describe("create", Label("machine", "create"), func() {
ListInstanceConfigs(ctx, 123, gomock.Any()).
After(createEtcdDisk).
Return([]linodego.InstanceConfig{{
+ ID: 1,
Devices: &linodego.InstanceConfigDeviceMap{
SDA: &linodego.InstanceConfigDevice{DiskID: 100},
},
- }}, nil)
+ }}, nil).AnyTimes()
createInstanceProfile := mockLinodeClient.EXPECT().
- UpdateInstanceConfig(ctx, 123, 0, linodego.InstanceConfigUpdateOptions{
+ UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
Devices: &linodego.InstanceConfigDeviceMap{
SDA: &linodego.InstanceConfigDevice{DiskID: 100},
SDB: &linodego.InstanceConfigDevice{DiskID: 101},
@@ -1135,6 +1200,17 @@ var _ = Describe("createDNS", Label("machine", "createDNS"), func() {
mockLinodeClient.EXPECT().
OnAfterResponse(gomock.Any()).
Return()
+ listInstConfs := mockLinodeClient.EXPECT().
+ ListInstanceConfigs(ctx, 123, gomock.Any()).
+ After(createInst).
+ Return([]linodego.InstanceConfig{{
+ ID: 1,
+ }}, nil)
+ mockLinodeClient.EXPECT().UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{Network: true},
+ }).
+ After(listInstConfs).
+ Return(nil, nil)
bootInst := mockLinodeClient.EXPECT().
BootInstance(ctx, 123, 0).
After(createInst).
@@ -2435,6 +2511,17 @@ var _ = Describe("machine in vlan", Label("machine", "vlan"), Ordered, func() {
mockLinodeClient.EXPECT().
OnAfterResponse(gomock.Any()).
Return()
+ listInstConfs := mockLinodeClient.EXPECT().
+ ListInstanceConfigs(ctx, 123, gomock.Any()).
+ After(createInst).
+ Return([]linodego.InstanceConfig{{
+ ID: 1,
+ }}, nil)
+ mockLinodeClient.EXPECT().UpdateInstanceConfig(ctx, 123, 1, linodego.InstanceConfigUpdateOptions{
+ Helpers: &linodego.InstanceConfigHelpers{Network: true},
+ }).
+ After(listInstConfs).
+ Return(nil, nil)
bootInst := mockLinodeClient.EXPECT().
BootInstance(ctx, 123, 0).
After(createInst).
@@ -2601,6 +2688,10 @@ var _ = Describe("create machine with direct VPCID", Label("machine", "VPCID"),
},
}, nil).
AnyTimes()
+ mockLinodeClient.EXPECT().
+ UpdateInstanceConfig(gomock.Any(), 12345, 1, gomock.Any()).
+ Return(nil, nil).
+ AnyTimes()
mockLinodeClient.EXPECT().
GetInstanceIPAddresses(gomock.Any(), gomock.Any()).
Return(&linodego.InstanceIPAddressResponse{
diff --git a/templates/flavors/kubeadm/flatcar/patch-flatcar.yaml b/templates/flavors/kubeadm/flatcar/patch-flatcar.yaml
index f34c5131d..680612758 100644
--- a/templates/flavors/kubeadm/flatcar/patch-flatcar.yaml
+++ b/templates/flavors/kubeadm/flatcar/patch-flatcar.yaml
@@ -117,6 +117,7 @@ spec:
configuration:
kernel: linode/direct-disk
diskEncryption: disabled
+ networkHelper: false
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha2
kind: LinodeMachineTemplate
@@ -129,3 +130,4 @@ spec:
configuration:
kernel: linode/direct-disk
diskEncryption: disabled
+ networkHelper: false