diff --git a/cloud/scope/machine.go b/cloud/scope/machine.go index ef2f9f8fe..81cb31239 100644 --- a/cloud/scope/machine.go +++ b/cloud/scope/machine.go @@ -1,9 +1,14 @@ package scope import ( + "context" + b64 "encoding/base64" "errors" "fmt" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + infrav1 "github.com/linode/cluster-api-provider-linode/api/v1alpha1" "github.com/linode/linodego" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" @@ -69,3 +74,35 @@ func NewMachineScope(apiKey string, params MachineScopeParams) (*MachineScope, e LinodeMachine: params.LinodeMachine, }, nil } + +// GetBootstrapData returns the bootstrap data from the secret in the Machine's bootstrap.dataSecretName. +func (m *MachineScope) GetBootstrapData(ctx context.Context) (string, error) { + if m.Machine.Spec.Bootstrap.DataSecretName == nil { + return "", fmt.Errorf( + "bootstrap data secret is nil for LinodeMachine %s/%s", + m.LinodeMachine.Namespace, + m.LinodeMachine.Name, + ) + } + + secret := &corev1.Secret{} + key := types.NamespacedName{Namespace: m.LinodeMachine.Namespace, Name: *m.Machine.Spec.Bootstrap.DataSecretName} + if err := m.client.Get(ctx, key, secret); err != nil { + return "", fmt.Errorf( + "failed to retrieve bootstrap data secret for LinodeMachine %s/%s", + m.LinodeMachine.Namespace, + m.LinodeMachine.Name, + ) + } + + value, ok := secret.Data["value"] + if !ok { + return "", fmt.Errorf( + "bootstrap data secret value key is missing for LinodeMachine %s/%s", + m.LinodeMachine.Namespace, + m.LinodeMachine.Name, + ) + } + + return b64.StdEncoding.EncodeToString(value), nil +} diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 5f7cf5498..e55764d7d 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -10,8 +10,19 @@ rules: - events verbs: - create + - get + - list - patch - update + - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list + - watch - apiGroups: - cluster.x-k8s.io resources: diff --git a/controller/linodemachine_controller.go b/controller/linodemachine_controller.go index 5a05f0b5b..9e75f37ba 100644 --- a/controller/linodemachine_controller.go +++ b/controller/linodemachine_controller.go @@ -81,13 +81,14 @@ type LinodeMachineReconciler struct { ReconcileTimeout time.Duration } -//+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=linodemachines,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=linodemachines/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=linodemachines/finalizers,verbs=update +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=linodemachines,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=linodemachines/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=infrastructure.cluster.x-k8s.io,resources=linodemachines/finalizers,verbs=update -//+kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=get;watch;list -//+kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machines,verbs=get;watch;list -//+kubebuilder:rbac:groups="",resources=events,verbs=create;update;patch +// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=clusters,verbs=get;watch;list +// +kubebuilder:rbac:groups=cluster.x-k8s.io,resources=machines,verbs=get;watch;list +// +kubebuilder:rbac:groups="",resources=events,verbs=get;list;watch;create;update;patch +// +kubebuilder:rbac:groups="",resources=secrets;,verbs=get;list;watch // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -118,7 +119,6 @@ func (r *LinodeMachineReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, nil case skippedMachinePhases[machine.Status.Phase]: - log.Info("Machine phase is not the one we are looking for, skipping reconciliation", "phase", machine.Status.Phase) return ctrl.Result{}, nil default: @@ -209,6 +209,9 @@ func (r *LinodeMachineReconciler) reconcile( } }() + // Add the finalizer if not already there + controllerutil.AddFinalizer(machineScope.LinodeMachine, infrav1.GroupVersion.String()) + // Delete if !machineScope.LinodeMachine.ObjectMeta.DeletionTimestamp.IsZero() { failureReason = cerrs.DeleteMachineError @@ -218,8 +221,6 @@ func (r *LinodeMachineReconciler) reconcile( return } - controllerutil.AddFinalizer(machineScope.LinodeMachine, infrav1.GroupVersion.String()) - var linodeInstance *linodego.Instance defer func() { machineScope.LinodeMachine.Status.InstanceState = util.Pointer(linodego.InstanceOffline) @@ -241,7 +242,12 @@ func (r *LinodeMachineReconciler) reconcile( // Create failureReason = cerrs.CreateMachineError + // Make sure bootstrap data is available and populated. + if machineScope.Machine.Spec.Bootstrap.DataSecretName == nil { + logger.Info("Bootstrap data secret is not yet available") + return ctrl.Result{RequeueAfter: reconciler.DefaultMachineControllerWaitForBootstrapDelay}, nil + } linodeInstance, err = r.reconcileCreate(ctx, machineScope, logger) return @@ -281,6 +287,17 @@ func (*LinodeMachineReconciler) reconcileCreate(ctx context.Context, machineScop } createConfig.Tags = tags + // get the bootstrap data for the Linode instance and set it for create config + bootstrapData, err := machineScope.GetBootstrapData(ctx) + if err != nil { + logger.Info("Failed to get bootstrap data", "error", err.Error()) + + return nil, err + } + createConfig.Metadata = &linodego.InstanceMetadataOptions{ + UserData: bootstrapData, + } + if linodeInstance, err = machineScope.LinodeClient.CreateInstance(ctx, *createConfig); err != nil { logger.Info("Failed to create Linode machine instance", "error", err.Error()) diff --git a/util/reconciler/defaults.go b/util/reconciler/defaults.go index 0c789e20a..dd8d3a7a5 100644 --- a/util/reconciler/defaults.go +++ b/util/reconciler/defaults.go @@ -26,6 +26,8 @@ const ( // DefaultMappingTimeout is the default timeout for a controller request mapping func. DefaultMappingTimeout = 60 * time.Second + // DefaultMachineControllerWaitForBootstrapDelay is the default requeue delay if bootstrap data is not ready. + DefaultMachineControllerWaitForBootstrapDelay = 5 * time.Second // DefaultMachineControllerWaitForRunningDelay is the default requeue delay if instance is not running. DefaultMachineControllerWaitForRunningDelay = 5 * time.Second // DefaultMachineControllerWaitForRunningTimeout is the default timeout if instance is not running.