diff --git a/controller/linodemachine_controller_helpers.go b/controller/linodemachine_controller_helpers.go index cbc7299c..44a17431 100644 --- a/controller/linodemachine_controller_helpers.go +++ b/controller/linodemachine_controller_helpers.go @@ -50,11 +50,10 @@ import ( "github.com/linode/cluster-api-provider-linode/util/reconciler" ) -// Size limit in bytes on the decoded metadata.user_data for cloud-init -// The decoded user_data must not exceed 16384 bytes per the Linode API const ( - maxBootstrapDataBytes = 16384 - vlanIPFormat = "%s/11" + maxBootstrapDataBytesCloudInit = 16384 + maxBootstrapDataBytesStackscript = 65535 + vlanIPFormat = "%s/11" ) var ( @@ -473,21 +472,29 @@ func setUserData(ctx context.Context, machineScope *scope.MachineScope, createCo return err } - if len(bootstrapData) > maxBootstrapDataBytes { - err = errors.New("bootstrap data too large") - logger.Error(err, "decoded bootstrap data exceeds size limit", - "limit", maxBootstrapDataBytes, - ) - - return err - } if machineScope.LinodeMachine.Status.CloudinitMetadataSupport { + if len(bootstrapData) > maxBootstrapDataBytesCloudInit { + err = errors.New("bootstrap data too large") + logger.Error(err, "decoded bootstrap data exceeds size limit", + "limit", maxBootstrapDataBytesCloudInit, + ) + + return err + } createConfig.Metadata = &linodego.InstanceMetadataOptions{ UserData: b64.StdEncoding.EncodeToString(bootstrapData), } } else { logger.Info("using StackScripts for bootstrapping") + if len(bootstrapData) > maxBootstrapDataBytesStackscript { + err = errors.New("bootstrap data too large") + logger.Error(err, "decoded bootstrap data exceeds size limit", + "limit", maxBootstrapDataBytesStackscript, + ) + + return err + } capiStackScriptID, err := services.EnsureStackscript(ctx, machineScope) if err != nil { return fmt.Errorf("ensure stackscript: %w", err) diff --git a/controller/linodemachine_controller_helpers_test.go b/controller/linodemachine_controller_helpers_test.go index 88c38a55..789a4d96 100644 --- a/controller/linodemachine_controller_helpers_test.go +++ b/controller/linodemachine_controller_helpers_test.go @@ -161,7 +161,7 @@ func TestSetUserData(t *testing.T) { }, }, { - name: "Error - SetUserData large bootstrap data", + name: "Error - SetUserData large bootstrap data for cloud-init", machineScope: &scope.MachineScope{Machine: &v1beta1.Machine{ Spec: v1beta1.MachineSpec{ ClusterName: "", @@ -176,7 +176,40 @@ func TestSetUserData(t *testing.T) { Namespace: "default", }, Spec: infrav1alpha2.LinodeMachineSpec{Region: "us-ord", Image: "linode/ubuntu22.04"}, - Status: infrav1alpha2.LinodeMachineStatus{}, + Status: infrav1alpha2.LinodeMachineStatus{CloudinitMetadataSupport: true}, + }}, + createConfig: &linodego.InstanceCreateOptions{}, + wantConfig: &linodego.InstanceCreateOptions{}, + expects: func(mockClient *mock.MockLinodeClient, kMock *mock.MockK8sClient) { + kMock.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj *corev1.Secret, opts ...client.GetOption) error { + cred := corev1.Secret{ + Data: map[string][]byte{ + "value": make([]byte, maxBootstrapDataBytesCloudInit+1), + }, + } + *obj = cred + return nil + }) + }, + expectedError: fmt.Errorf("bootstrap data too large"), + }, + { + name: "Error - SetUserData large bootstrap data for stackscript", + machineScope: &scope.MachineScope{Machine: &v1beta1.Machine{ + Spec: v1beta1.MachineSpec{ + ClusterName: "", + Bootstrap: v1beta1.Bootstrap{ + DataSecretName: ptr.To("test-data"), + }, + InfrastructureRef: corev1.ObjectReference{}, + }, + }, LinodeMachine: &infrav1alpha2.LinodeMachine{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "default", + }, + Spec: infrav1alpha2.LinodeMachineSpec{Region: "us-ord", Image: "linode/ubuntu22.04"}, + Status: infrav1alpha2.LinodeMachineStatus{CloudinitMetadataSupport: false}, }}, createConfig: &linodego.InstanceCreateOptions{}, wantConfig: &linodego.InstanceCreateOptions{}, @@ -184,7 +217,7 @@ func TestSetUserData(t *testing.T) { kMock.EXPECT().Get(gomock.Any(), gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, key types.NamespacedName, obj *corev1.Secret, opts ...client.GetOption) error { cred := corev1.Secret{ Data: map[string][]byte{ - "value": make([]byte, maxBootstrapDataBytes+1), + "value": make([]byte, maxBootstrapDataBytesStackscript+1), }, } *obj = cred