@@ -27,6 +27,7 @@ import (
2727 apierrors "k8s.io/apimachinery/pkg/api/errors"
2828 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30+ "k8s.io/apimachinery/pkg/types"
3031 kerrors "k8s.io/apimachinery/pkg/util/errors"
3132 "k8s.io/apiserver/pkg/storage/names"
3233 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
@@ -36,10 +37,12 @@ import (
3637 "sigs.k8s.io/cluster-api/util/conditions"
3738 "sigs.k8s.io/cluster-api/util/patch"
3839 ctrl "sigs.k8s.io/controller-runtime"
40+ "sigs.k8s.io/controller-runtime/pkg/client"
3941
4042 bootstrapv1 "github.com/k3s-io/cluster-api-k3s/bootstrap/api/v1beta2"
4143 controlplanev1 "github.com/k3s-io/cluster-api-k3s/controlplane/api/v1beta2"
4244 k3s "github.com/k3s-io/cluster-api-k3s/pkg/k3s"
45+ "github.com/k3s-io/cluster-api-k3s/pkg/util/ssa"
4346)
4447
4548var ErrPreConditionFailed = errors .New ("precondition check failed" )
@@ -253,6 +256,12 @@ func selectMachineForScaleDown(ctx context.Context, controlPlane *k3s.ControlPla
253256func (r * KThreesControlPlaneReconciler ) cloneConfigsAndGenerateMachine (ctx context.Context , cluster * clusterv1.Cluster , kcp * controlplanev1.KThreesControlPlane , bootstrapSpec * bootstrapv1.KThreesConfigSpec , failureDomain * string ) error {
254257 var errs []error
255258
259+ // Compute desired Machine
260+ machine , err := r .computeDesiredMachine (kcp , cluster , failureDomain , nil )
261+ if err != nil {
262+ return errors .Wrap (err , "failed to create Machine: failed to compute desired Machine" )
263+ }
264+
256265 // Since the cloned resource should eventually have a controller ref for the Machine, we create an
257266 // OwnerReference here without the Controller field set
258267 infraCloneOwner := & metav1.OwnerReference {
@@ -275,6 +284,7 @@ func (r *KThreesControlPlaneReconciler) cloneConfigsAndGenerateMachine(ctx conte
275284 // Safe to return early here since no resources have been created yet.
276285 return fmt .Errorf ("failed to clone infrastructure template: %w" , err )
277286 }
287+ machine .Spec .InfrastructureRef = * infraRef
278288
279289 // Clone the bootstrap configuration
280290 bootstrapRef , err := r .generateKThreesConfig (ctx , kcp , cluster , bootstrapSpec )
@@ -284,8 +294,9 @@ func (r *KThreesControlPlaneReconciler) cloneConfigsAndGenerateMachine(ctx conte
284294
285295 // Only proceed to generating the Machine if we haven't encountered an error
286296 if len (errs ) == 0 {
287- if err := r .generateMachine (ctx , kcp , cluster , infraRef , bootstrapRef , failureDomain ); err != nil {
288- errs = append (errs , fmt .Errorf ("failed to create Machine: %w" , err ))
297+ machine .Spec .Bootstrap .ConfigRef = bootstrapRef
298+ if err := r .createMachine (ctx , kcp , machine ); err != nil {
299+ errs = append (errs , errors .Wrap (err , "failed to create Machine" ))
289300 }
290301 }
291302
@@ -355,55 +366,135 @@ func (r *KThreesControlPlaneReconciler) generateKThreesConfig(ctx context.Contex
355366 return bootstrapRef , nil
356367}
357368
358- func (r * KThreesControlPlaneReconciler ) generateMachine (ctx context.Context , kcp * controlplanev1.KThreesControlPlane , cluster * clusterv1.Cluster , infraRef , bootstrapRef * corev1.ObjectReference , failureDomain * string ) error {
359- machine := & clusterv1.Machine {
369+ // updateExternalObject updates the external object with the labels and annotations from KCP.
370+ func (r * KThreesControlPlaneReconciler ) updateExternalObject (ctx context.Context , obj client.Object , kcp * controlplanev1.KThreesControlPlane , cluster * clusterv1.Cluster ) error {
371+ updatedObject := & unstructured.Unstructured {}
372+ updatedObject .SetGroupVersionKind (obj .GetObjectKind ().GroupVersionKind ())
373+ updatedObject .SetNamespace (obj .GetNamespace ())
374+ updatedObject .SetName (obj .GetName ())
375+ // Set the UID to ensure that Server-Side-Apply only performs an update
376+ // and does not perform an accidental create.
377+ updatedObject .SetUID (obj .GetUID ())
378+
379+ // Update labels
380+ updatedObject .SetLabels (k3s .ControlPlaneLabelsForCluster (cluster .Name , kcp .Spec .MachineTemplate ))
381+ // Update annotations
382+ updatedObject .SetAnnotations (kcp .Spec .MachineTemplate .ObjectMeta .Annotations )
383+
384+ if err := ssa .Patch (ctx , r .Client , kcpManagerName , updatedObject , ssa.WithCachingProxy {Cache : r .ssaCache , Original : obj }); err != nil {
385+ return errors .Wrapf (err , "failed to update %s" , obj .GetObjectKind ().GroupVersionKind ().Kind )
386+ }
387+ return nil
388+ }
389+
390+ func (r * KThreesControlPlaneReconciler ) createMachine (ctx context.Context , kcp * controlplanev1.KThreesControlPlane , machine * clusterv1.Machine ) error {
391+ if err := ssa .Patch (ctx , r .Client , kcpManagerName , machine ); err != nil {
392+ return errors .Wrap (err , "failed to create Machine" )
393+ }
394+ // Remove the annotation tracking that a remediation is in progress (the remediation completed when
395+ // the replacement machine has been created above).
396+ delete (kcp .Annotations , controlplanev1 .RemediationInProgressAnnotation )
397+ return nil
398+ }
399+
400+ func (r * KThreesControlPlaneReconciler ) updateMachine (ctx context.Context , machine * clusterv1.Machine , kcp * controlplanev1.KThreesControlPlane , cluster * clusterv1.Cluster ) (* clusterv1.Machine , error ) {
401+ updatedMachine , err := r .computeDesiredMachine (kcp , cluster , machine .Spec .FailureDomain , machine )
402+ if err != nil {
403+ return nil , errors .Wrap (err , "failed to update Machine: failed to compute desired Machine" )
404+ }
405+
406+ err = ssa .Patch (ctx , r .Client , kcpManagerName , updatedMachine , ssa.WithCachingProxy {Cache : r .ssaCache , Original : machine })
407+ if err != nil {
408+ return nil , errors .Wrap (err , "failed to update Machine" )
409+ }
410+ return updatedMachine , nil
411+ }
412+
413+ // computeDesiredMachine computes the desired Machine.
414+ // This Machine will be used during reconciliation to:
415+ // * create a new Machine
416+ // * update an existing Machine
417+ // Because we are using Server-Side-Apply we always have to calculate the full object.
418+ // There are small differences in how we calculate the Machine depending on if it
419+ // is a create or update. Example: for a new Machine we have to calculate a new name,
420+ // while for an existing Machine we have to use the name of the existing Machine.
421+ // Also, for an existing Machine, we will not copy its labels, as they are not managed by the KThreesControlPlane controller.
422+ func (r * KThreesControlPlaneReconciler ) computeDesiredMachine (kcp * controlplanev1.KThreesControlPlane , cluster * clusterv1.Cluster , failureDomain * string , existingMachine * clusterv1.Machine ) (* clusterv1.Machine , error ) {
423+ var machineName string
424+ var machineUID types.UID
425+ var version * string
426+ annotations := map [string ]string {}
427+ if existingMachine == nil {
428+ // Creating a new machine
429+ machineName = names .SimpleNameGenerator .GenerateName (kcp .Name + "-" )
430+ version = & kcp .Spec .Version
431+
432+ // Machine's bootstrap config may be missing ClusterConfiguration if it is not the first machine in the control plane.
433+ // We store ClusterConfiguration as annotation here to detect any changes in KCP ClusterConfiguration and rollout the machine if any.
434+ serverConfig , err := json .Marshal (kcp .Spec .KThreesConfigSpec .ServerConfig )
435+ if err != nil {
436+ return nil , errors .Wrap (err , "failed to marshal cluster configuration" )
437+ }
438+ annotations [controlplanev1 .KThreesServerConfigurationAnnotation ] = string (serverConfig )
439+
440+ // In case this machine is being created as a consequence of a remediation, then add an annotation
441+ // tracking remediating data.
442+ // NOTE: This is required in order to track remediation retries.
443+ if remediationData , ok := kcp .Annotations [controlplanev1 .RemediationInProgressAnnotation ]; ok {
444+ annotations [controlplanev1 .RemediationForAnnotation ] = remediationData
445+ }
446+ } else {
447+ // Updating an existing machine
448+ machineName = existingMachine .Name
449+ machineUID = existingMachine .UID
450+ version = existingMachine .Spec .Version
451+
452+ // For existing machine only set the ClusterConfiguration annotation if the machine already has it.
453+ // We should not add the annotation if it was missing in the first place because we do not have enough
454+ // information.
455+ if serverConfig , ok := existingMachine .Annotations [controlplanev1 .KThreesServerConfigurationAnnotation ]; ok {
456+ annotations [controlplanev1 .KThreesServerConfigurationAnnotation ] = serverConfig
457+ }
458+
459+ // If the machine already has remediation data then preserve it.
460+ // NOTE: This is required in order to track remediation retries.
461+ if remediationData , ok := existingMachine .Annotations [controlplanev1 .RemediationForAnnotation ]; ok {
462+ annotations [controlplanev1 .RemediationForAnnotation ] = remediationData
463+ }
464+ }
465+
466+ // Construct the basic Machine.
467+ desiredMachine := & clusterv1.Machine {
360468 ObjectMeta : metav1.ObjectMeta {
361- Name : names . SimpleNameGenerator . GenerateName ( kcp . Name + "-" ) ,
469+ Name : machineName ,
362470 Namespace : kcp .Namespace ,
471+ UID : machineUID ,
363472 Labels : k3s .ControlPlaneLabelsForCluster (cluster .Name , kcp .Spec .MachineTemplate ),
364473 OwnerReferences : []metav1.OwnerReference {
365474 * metav1 .NewControllerRef (kcp , controlplanev1 .GroupVersion .WithKind ("KThreesControlPlane" )),
366475 },
367476 },
368477 Spec : clusterv1.MachineSpec {
369- ClusterName : cluster .Name ,
370- Version : & kcp .Spec .Version ,
371- InfrastructureRef : * infraRef ,
372- Bootstrap : clusterv1.Bootstrap {
373- ConfigRef : bootstrapRef ,
374- },
478+ ClusterName : cluster .Name ,
479+ Version : version ,
375480 FailureDomain : failureDomain ,
376481 NodeDrainTimeout : kcp .Spec .MachineTemplate .NodeDrainTimeout ,
377482 NodeVolumeDetachTimeout : kcp .Spec .MachineTemplate .NodeVolumeDetachTimeout ,
378483 NodeDeletionTimeout : kcp .Spec .MachineTemplate .NodeDeletionTimeout ,
379484 },
380485 }
381486
382- annotations := map [string ]string {}
383-
384- // Machine's bootstrap config may be missing ClusterConfiguration if it is not the first machine in the control plane.
385- // We store ClusterConfiguration as annotation here to detect any changes in KCP ClusterConfiguration and rollout the machine if any.
386- serverConfig , err := json .Marshal (kcp .Spec .KThreesConfigSpec .ServerConfig )
387- if err != nil {
388- return fmt .Errorf ("failed to marshal cluster configuration: %w" , err )
389- }
390- annotations [controlplanev1 .KThreesServerConfigurationAnnotation ] = string (serverConfig )
391-
392- // In case this machine is being created as a consequence of a remediation, then add an annotation
393- // tracking remediating data.
394- // NOTE: This is required in order to track remediation retries.
395- if remediationData , ok := kcp .Annotations [controlplanev1 .RemediationInProgressAnnotation ]; ok {
396- annotations [controlplanev1 .RemediationForAnnotation ] = remediationData
487+ // Set annotations
488+ for k , v := range kcp .Spec .MachineTemplate .ObjectMeta .Annotations {
489+ annotations [k ] = v
397490 }
398491
399- machine .SetAnnotations (annotations )
492+ desiredMachine .SetAnnotations (annotations )
400493
401- if err := r .Client .Create (ctx , machine ); err != nil {
402- return fmt .Errorf ("failed to create machine: %w" , err )
494+ if existingMachine != nil {
495+ desiredMachine .Spec .InfrastructureRef = existingMachine .Spec .InfrastructureRef
496+ desiredMachine .Spec .Bootstrap .ConfigRef = existingMachine .Spec .Bootstrap .ConfigRef
403497 }
404498
405- // Remove the annotation tracking that a remediation is in progress (the remediation completed when
406- // the replacement machine has been created above).
407- delete (kcp .Annotations , controlplanev1 .RemediationInProgressAnnotation )
408- return nil
499+ return desiredMachine , nil
409500}
0 commit comments