diff --git a/internal/controllers/capiprovider_controller.go b/internal/controllers/capiprovider_controller.go index 0b3e9c7f0..ac51b593e 100644 --- a/internal/controllers/capiprovider_controller.go +++ b/internal/controllers/capiprovider_controller.go @@ -30,8 +30,6 @@ import ( operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2" - operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2" - turtlesv1 "github.com/rancher-sandbox/rancher-turtles/api/v1alpha1" "github.com/rancher-sandbox/rancher-turtles/internal/sync" ) @@ -71,7 +69,10 @@ func (r *CAPIProviderReconciler) reconcileNormal(ctx context.Context, capiProvid } func (r *CAPIProviderReconciler) sync(ctx context.Context, capiProvider *turtlesv1.CAPIProvider) (_ ctrl.Result, err error) { - s := sync.List{} + s := sync.List{ + sync.NewProviderSync(r.Client, capiProvider), + sync.NewSecretSync(r.Client, capiProvider), + } if err := s.Sync(ctx); client.IgnoreNotFound(err) != nil { return ctrl.Result{}, err diff --git a/internal/sync/provider_sync.go b/internal/sync/provider_sync.go new file mode 100644 index 000000000..c6ecd4114 --- /dev/null +++ b/internal/sync/provider_sync.go @@ -0,0 +1,97 @@ +package sync + +import ( + "context" + + "sigs.k8s.io/cluster-api/util/conditions" + "sigs.k8s.io/controller-runtime/pkg/client" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + operatorv1 "sigs.k8s.io/cluster-api-operator/api/v1alpha2" + + turtlesv1 "github.com/rancher-sandbox/rancher-turtles/api/v1alpha1" +) + +// ProviderSync is a structure mirroring state of the CAPI Operator Provider object. +type ProviderSync struct { + *DefaultSynchronizer +} + +// NewProviderSync creates a new mirror object. +func NewProviderSync(cl client.Client, capiProvider *turtlesv1.CAPIProvider) Sync { + return &ProviderSync{ + DefaultSynchronizer: NewDefaultSynchronizer(cl, capiProvider, ProviderSync{}.Template(capiProvider)), + } +} + +// Template returning the mirrored CAPI Operator manifest template. +func (ProviderSync) Template(capiProvider *turtlesv1.CAPIProvider) client.Object { + meta := metav1.ObjectMeta{ + Name: string(capiProvider.Spec.Name), + Namespace: capiProvider.GetNamespace(), + } + + switch capiProvider.Spec.Type { + case turtlesv1.Infrastructure: + return &operatorv1.InfrastructureProvider{ObjectMeta: meta} + case turtlesv1.Core: + return &operatorv1.CoreProvider{ObjectMeta: meta} + case turtlesv1.ControlPlane: + return &operatorv1.ControlPlaneProvider{ObjectMeta: meta} + case turtlesv1.Bootstrap: + return &operatorv1.BootstrapProvider{ObjectMeta: meta} + case turtlesv1.Addon: + return &operatorv1.AddonProvider{ObjectMeta: meta} + default: + } + + return nil +} + +// Sync updates the mirror object state from the upstream source object +// Direction of updates: +// Spec -> down +// up <- Status. +func (s *ProviderSync) Sync(_ context.Context) error { + s.SyncObjects() + + return nil +} + +// SyncObjects updates the Source CAPIProvider object and the destination provider object states. +// Direction of updates: +// Spec -> Provider +// CAPIProvider <- Status. +func (s *ProviderSync) SyncObjects() { + switch mirror := s.Destination.(type) { + case *operatorv1.InfrastructureProvider: + s.Source.Spec.ProviderSpec.DeepCopyInto(&mirror.Spec.ProviderSpec) + mirror.Status.ProviderStatus.DeepCopyInto(&s.Source.Status.ProviderStatus) + case *operatorv1.CoreProvider: + s.Source.Spec.ProviderSpec.DeepCopyInto(&mirror.Spec.ProviderSpec) + mirror.Status.ProviderStatus.DeepCopyInto(&s.Source.Status.ProviderStatus) + case *operatorv1.ControlPlaneProvider: + s.Source.Spec.ProviderSpec.DeepCopyInto(&mirror.Spec.ProviderSpec) + mirror.Status.ProviderStatus.DeepCopyInto(&s.Source.Status.ProviderStatus) + case *operatorv1.BootstrapProvider: + s.Source.Spec.ProviderSpec.DeepCopyInto(&mirror.Spec.ProviderSpec) + mirror.Status.ProviderStatus.DeepCopyInto(&s.Source.Status.ProviderStatus) + case *operatorv1.AddonProvider: + s.Source.Spec.ProviderSpec.DeepCopyInto(&mirror.Spec.ProviderSpec) + mirror.Status.ProviderStatus.DeepCopyInto(&s.Source.Status.ProviderStatus) + default: + } + + s.syncStatus() +} + +func (s *ProviderSync) syncStatus() { + switch { + case conditions.IsTrue(s.Source, operatorv1.ProviderInstalledCondition): + s.Source.Status.Phase = turtlesv1.Ready + case conditions.IsFalse(s.Source, operatorv1.PreflightCheckCondition): + s.Source.Status.Phase = turtlesv1.Failed + default: + s.Source.Status.Phase = turtlesv1.Provisioning + } +} diff --git a/internal/sync/secret_sync.go b/internal/sync/secret_sync.go new file mode 100644 index 000000000..b86578968 --- /dev/null +++ b/internal/sync/secret_sync.go @@ -0,0 +1,91 @@ +package sync + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + turtlesv1 "github.com/rancher-sandbox/rancher-turtles/api/v1alpha1" +) + +// SecretSync is a structure mirroring variable secret state of the CAPI Operator Provider object. +type SecretSync struct { + *DefaultSynchronizer + + Secret *corev1.Secret +} + +// NewSecretSync creates a new secret object sync. +func NewSecretSync(cl client.Client, capiProvider *turtlesv1.CAPIProvider) Sync { + secret := SecretSync{}.GetSecret(capiProvider) + + return &SecretSync{ + DefaultSynchronizer: NewDefaultSynchronizer(cl, capiProvider, secret), + Secret: secret, + } +} + +// GetSecret returning the mirrored secret resource template. +func (SecretSync) GetSecret(capiProvider *turtlesv1.CAPIProvider) *corev1.Secret { + meta := metav1.ObjectMeta{ + Name: capiProvider.Name, + Namespace: capiProvider.Namespace, + } + + if capiProvider.Spec.ConfigSecret != nil { + meta.Name = capiProvider.Spec.ConfigSecret.Name + } + + return &corev1.Secret{ObjectMeta: meta} +} + +// Template returning the mirrored secret resource template. +func (SecretSync) Template(capiProvider *turtlesv1.CAPIProvider) client.Object { + return SecretSync{}.GetSecret(capiProvider) +} + +// Sync updates the mirror object state from the upstream source object +// Direction of updates: +// Spec -> down +// up <- Status. +func (s *SecretSync) Sync(_ context.Context) error { + s.SyncObjects() + + return nil +} + +// SyncObjects updates the Source CAPIProvider object and the environment secret state. +// Direction of updates: +// Spec.Features + Spec.Variables -> Status.Variables -> Secret. +func (s *SecretSync) SyncObjects() { + setVariables(s.DefaultSynchronizer.Source) + setFeatures(s.DefaultSynchronizer.Source) + + s.Secret.StringData = s.DefaultSynchronizer.Source.Status.Variables +} + +func setVariables(capiProvider *turtlesv1.CAPIProvider) { + if capiProvider.Spec.Variables != nil { + capiProvider.Status.Variables = capiProvider.Spec.Variables + } +} + +func setFeatures(capiProvider *turtlesv1.CAPIProvider) { + value := "true" + features := capiProvider.Spec.Features + variables := capiProvider.Status.Variables + + if features != nil { + if features.ClusterResourceSet { + variables["EXP_CLUSTER_RESOURCE_SET"] = value + } + if features.ClusterTopology { + variables["CLUSTER_TOPOLOGY"] = value + } + if features.MachinePool { + variables["EXP_MACHINE_POOL"] = value + } + } +}