diff --git a/api/v1alpha1/clustertemplate_types.go b/api/v1alpha1/clustertemplate_types.go index d63bf9c5f..9018a01e9 100644 --- a/api/v1alpha1/clustertemplate_types.go +++ b/api/v1alpha1/clustertemplate_types.go @@ -40,9 +40,8 @@ type ClusterTemplateSpec struct { ProviderContracts CompatibilityContracts `json:"providerContracts,omitempty"` // Kubernetes exact version in the SemVer format provided by this ClusterTemplate. KubernetesVersion string `json:"k8sVersion,omitempty"` - // Providers represent required CAPI providers with supported contract versions. + // Providers represent required CAPI providers. // Should be set if not present in the Helm chart metadata. - // Compatibility attributes are optional to be defined. Providers Providers `json:"providers,omitempty"` } @@ -57,8 +56,7 @@ type ClusterTemplateStatus struct { ProviderContracts CompatibilityContracts `json:"providerContracts,omitempty"` // Kubernetes exact version in the SemVer format provided by this ClusterTemplate. KubernetesVersion string `json:"k8sVersion,omitempty"` - // Providers represent required CAPI providers with supported contract versions - // if the latter has been given. + // Providers represent required CAPI providers. Providers Providers `json:"providers,omitempty"` TemplateStatusCommon `json:",inline"` diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go index ddf27cd83..16f3ef3e4 100644 --- a/api/v1alpha1/common.go +++ b/api/v1alpha1/common.go @@ -14,13 +14,6 @@ package v1alpha1 -import ( - "context" - - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" -) - const ( // SucceededReason indicates a condition or event observed a success, for example when declared desired state // matches actual state, or a performed action succeeded. @@ -57,144 +50,5 @@ const ( // Provider K0smotron ProviderK0smotronName = "k0smotron" // Provider Sveltos - ProviderSveltosName = "projectsveltos" - ProviderSveltosTargetNamespace = "projectsveltos" - ProviderSveltosCreateNamespace = true + ProviderSveltosName = "projectsveltos" ) - -func SetupIndexers(ctx context.Context, mgr ctrl.Manager) error { - if err := SetupManagedClusterIndexer(ctx, mgr); err != nil { - return err - } - - if err := SetupReleaseVersionIndexer(ctx, mgr); err != nil { - return err - } - - if err := SetupReleaseTemplatesIndexer(ctx, mgr); err != nil { - return err - } - - if err := SetupManagedClusterServicesIndexer(ctx, mgr); err != nil { - return err - } - - if err := SetupMultiClusterServiceServicesIndexer(ctx, mgr); err != nil { - return err - } - - if err := SetupClusterTemplateChainIndexer(ctx, mgr); err != nil { - return err - } - - return SetupServiceTemplateChainIndexer(ctx, mgr) -} - -const TemplateKey = ".spec.template" - -func SetupManagedClusterIndexer(ctx context.Context, mgr ctrl.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &ManagedCluster{}, TemplateKey, ExtractTemplateName) -} - -func ExtractTemplateName(rawObj client.Object) []string { - cluster, ok := rawObj.(*ManagedCluster) - if !ok { - return nil - } - return []string{cluster.Spec.Template} -} - -const ReleaseVersionKey = ".spec.version" - -func SetupReleaseVersionIndexer(ctx context.Context, mgr ctrl.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &Release{}, ReleaseVersionKey, ExtractReleaseVersion) -} - -func ExtractReleaseVersion(rawObj client.Object) []string { - release, ok := rawObj.(*Release) - if !ok { - return nil - } - return []string{release.Spec.Version} -} - -const ReleaseTemplatesKey = "releaseTemplates" - -func SetupReleaseTemplatesIndexer(ctx context.Context, mgr ctrl.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &Release{}, ReleaseTemplatesKey, ExtractReleaseTemplates) -} - -func ExtractReleaseTemplates(rawObj client.Object) []string { - release, ok := rawObj.(*Release) - if !ok { - return nil - } - return release.Templates() -} - -const ServicesTemplateKey = ".spec.services[].Template" - -func SetupManagedClusterServicesIndexer(ctx context.Context, mgr ctrl.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &ManagedCluster{}, ServicesTemplateKey, ExtractServiceTemplateFromManagedCluster) -} - -func ExtractServiceTemplateFromManagedCluster(rawObj client.Object) []string { - cluster, ok := rawObj.(*ManagedCluster) - if !ok { - return nil - } - - templates := []string{} - for _, s := range cluster.Spec.Services { - templates = append(templates, s.Template) - } - - return templates -} - -func SetupMultiClusterServiceServicesIndexer(ctx context.Context, mgr ctrl.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &MultiClusterService{}, ServicesTemplateKey, ExtractServiceTemplateFromMultiClusterService) -} - -func ExtractServiceTemplateFromMultiClusterService(rawObj client.Object) []string { - cluster, ok := rawObj.(*MultiClusterService) - if !ok { - return nil - } - - templates := []string{} - for _, s := range cluster.Spec.Services { - templates = append(templates, s.Template) - } - - return templates -} - -const SupportedTemplateKey = ".spec.supportedTemplates[].Name" - -func SetupClusterTemplateChainIndexer(ctx context.Context, mgr ctrl.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &ClusterTemplateChain{}, SupportedTemplateKey, ExtractSupportedTemplatesNames) -} - -func SetupServiceTemplateChainIndexer(ctx context.Context, mgr ctrl.Manager) error { - return mgr.GetFieldIndexer().IndexField(ctx, &ServiceTemplateChain{}, SupportedTemplateKey, ExtractSupportedTemplatesNames) -} - -func ExtractSupportedTemplatesNames(rawObj client.Object) []string { - chainSpec := TemplateChainSpec{} - switch chain := rawObj.(type) { - case *ClusterTemplateChain: - chainSpec = chain.Spec - case *ServiceTemplateChain: - chainSpec = chain.Spec - default: - return nil - } - - supportedTemplates := make([]string, 0, len(chainSpec.SupportedTemplates)) - for _, t := range chainSpec.SupportedTemplates { - supportedTemplates = append(supportedTemplates, t.Name) - } - - return supportedTemplates -} diff --git a/api/v1alpha1/indexers.go b/api/v1alpha1/indexers.go new file mode 100644 index 000000000..0229fcecb --- /dev/null +++ b/api/v1alpha1/indexers.go @@ -0,0 +1,193 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1alpha1 + +import ( + "context" + "errors" + + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func SetupIndexers(ctx context.Context, mgr ctrl.Manager) error { + var merr error + for _, f := range []func(context.Context, ctrl.Manager) error{ + setupManagedClusterIndexer, + setupManagedClusterServicesIndexer, + setupReleaseVersionIndexer, + setupReleaseTemplatesIndexer, + setupClusterTemplateChainIndexer, + setupServiceTemplateChainIndexer, + setupClusterTemplateProvidersIndexer, + setupMultiClusterServiceServicesIndexer, + } { + merr = errors.Join(merr, f(ctx, mgr)) + } + + return merr +} + +// managed cluster + +// ManagedClusterTemplateIndexKey indexer field name to extract ClusterTemplate name reference from a ManagedCluster object. +const ManagedClusterTemplateIndexKey = ".spec.template" + +func setupManagedClusterIndexer(ctx context.Context, mgr ctrl.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &ManagedCluster{}, ManagedClusterTemplateIndexKey, ExtractTemplateNameFromManagedCluster) +} + +// ExtractTemplateNameFromManagedCluster returns referenced ClusterTemplate name +// declared in a ManagedCluster object. +func ExtractTemplateNameFromManagedCluster(rawObj client.Object) []string { + cluster, ok := rawObj.(*ManagedCluster) + if !ok { + return nil + } + + return []string{cluster.Spec.Template} +} + +// ManagedClusterServiceTemplatesIndexKey indexer field name to extract service templates names from a ManagedCluster object. +const ManagedClusterServiceTemplatesIndexKey = ".spec.services[].Template" + +func setupManagedClusterServicesIndexer(ctx context.Context, mgr ctrl.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &ManagedCluster{}, ManagedClusterServiceTemplatesIndexKey, ExtractServiceTemplateNamesFromManagedCluster) +} + +// ExtractServiceTemplateNamesFromManagedCluster returns a list of service templates names +// declared in a ManagedCluster object. +func ExtractServiceTemplateNamesFromManagedCluster(rawObj client.Object) []string { + cluster, ok := rawObj.(*ManagedCluster) + if !ok { + return nil + } + + templates := []string{} + for _, s := range cluster.Spec.Services { + templates = append(templates, s.Template) + } + + return templates +} + +// release + +// ReleaseVersionIndexKey indexer field name to extract release version from a Release object. +const ReleaseVersionIndexKey = ".spec.version" + +func setupReleaseVersionIndexer(ctx context.Context, mgr ctrl.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &Release{}, ReleaseVersionIndexKey, extractReleaseVersion) +} + +func extractReleaseVersion(rawObj client.Object) []string { + release, ok := rawObj.(*Release) + if !ok { + return nil + } + return []string{release.Spec.Version} +} + +// ReleaseTemplatesIndexKey indexer field name to extract component template names from a Release object. +const ReleaseTemplatesIndexKey = "releaseTemplates" + +func setupReleaseTemplatesIndexer(ctx context.Context, mgr ctrl.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &Release{}, ReleaseTemplatesIndexKey, extractReleaseTemplates) +} + +func extractReleaseTemplates(rawObj client.Object) []string { + release, ok := rawObj.(*Release) + if !ok { + return nil + } + + return release.Templates() +} + +// template chains + +// TemplateChainSupportedTemplatesIndexKey indexer field name to extract supported template names from an according TemplateChain object. +const TemplateChainSupportedTemplatesIndexKey = ".spec.supportedTemplates[].Name" + +func setupClusterTemplateChainIndexer(ctx context.Context, mgr ctrl.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &ClusterTemplateChain{}, TemplateChainSupportedTemplatesIndexKey, extractSupportedTemplatesNames) +} + +func setupServiceTemplateChainIndexer(ctx context.Context, mgr ctrl.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &ServiceTemplateChain{}, TemplateChainSupportedTemplatesIndexKey, extractSupportedTemplatesNames) +} + +func extractSupportedTemplatesNames(rawObj client.Object) []string { + chainSpec := TemplateChainSpec{} + switch chain := rawObj.(type) { + case *ClusterTemplateChain: + chainSpec = chain.Spec + case *ServiceTemplateChain: + chainSpec = chain.Spec + default: + return nil + } + + supportedTemplates := make([]string, 0, len(chainSpec.SupportedTemplates)) + for _, t := range chainSpec.SupportedTemplates { + supportedTemplates = append(supportedTemplates, t.Name) + } + + return supportedTemplates +} + +// cluster template + +// ClusterTemplateProvidersIndexKey indexer field name to extract provider names from a ClusterTemplate object. +const ClusterTemplateProvidersIndexKey = "clusterTemplateProviders" + +func setupClusterTemplateProvidersIndexer(ctx context.Context, mgr ctrl.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &ClusterTemplate{}, ClusterTemplateProvidersIndexKey, ExtractProvidersFromClusterTemplate) +} + +// ExtractProvidersFromClusterTemplate returns provider names from a ClusterTemplate object. +func ExtractProvidersFromClusterTemplate(o client.Object) []string { + ct, ok := o.(*ClusterTemplate) + if !ok { + return nil + } + + return ct.Status.Providers +} + +// multicluster service + +// MultiClusterServiceTemplatesIndexKey indexer field name to extract service templates names from a MultiClusterService object. +const MultiClusterServiceTemplatesIndexKey = "serviceTemplates" + +func setupMultiClusterServiceServicesIndexer(ctx context.Context, mgr ctrl.Manager) error { + return mgr.GetFieldIndexer().IndexField(ctx, &MultiClusterService{}, MultiClusterServiceTemplatesIndexKey, ExtractServiceTemplateNamesFromMultiClusterService) +} + +// ExtractServiceTemplateNamesFromMultiClusterService returns a list of service templates names +// declared in a MultiClusterService object. +func ExtractServiceTemplateNamesFromMultiClusterService(rawObj client.Object) []string { + mcs, ok := rawObj.(*MultiClusterService) + if !ok { + return nil + } + + templates := make([]string, len(mcs.Spec.Services)) + for i, s := range mcs.Spec.Services { + templates[i] = s.Template + } + + return templates +} diff --git a/api/v1alpha1/management_types.go b/api/v1alpha1/management_types.go index 90b801606..40844a3ed 100644 --- a/api/v1alpha1/management_types.go +++ b/api/v1alpha1/management_types.go @@ -98,8 +98,7 @@ type ManagementStatus struct { Components map[string]ComponentStatus `json:"components,omitempty"` // Release indicates the current Release object. Release string `json:"release,omitempty"` - // AvailableProviders holds all CAPI providers available along with - // their supported contract versions, if specified in ProviderTemplates, on the Management cluster. + // AvailableProviders holds all available CAPI providers. AvailableProviders Providers `json:"availableProviders,omitempty"` // ObservedGeneration is the last observed generation. ObservedGeneration int64 `json:"observedGeneration,omitempty"` diff --git a/api/v1alpha1/providertemplate_types.go b/api/v1alpha1/providertemplate_types.go index aadb122dd..66e82f1bb 100644 --- a/api/v1alpha1/providertemplate_types.go +++ b/api/v1alpha1/providertemplate_types.go @@ -27,17 +27,15 @@ const ProviderTemplateKind = "ProviderTemplate" type ProviderTemplateSpec struct { Helm HelmSpec `json:"helm,omitempty"` CAPIContracts CompatibilityContracts `json:"capiContracts,omitempty"` - // Providers represent exposed CAPI providers with supported contract versions. + // Providers represent exposed CAPI providers. // Should be set if not present in the Helm chart metadata. - // Supported contract versions are optional to be defined. Providers Providers `json:"providers,omitempty"` } // ProviderTemplateStatus defines the observed state of ProviderTemplate type ProviderTemplateStatus struct { CAPIContracts CompatibilityContracts `json:"capiContracts,omitempty"` - // Providers represent exposed CAPI providers with supported contract versions - // if the latter has been given. + // Providers represent exposed CAPI providers. Providers Providers `json:"providers,omitempty"` TemplateStatusCommon `json:",inline"` diff --git a/internal/controller/managedcluster_controller.go b/internal/controller/managedcluster_controller.go index 55e15a217..f500abf71 100644 --- a/internal/controller/managedcluster_controller.go +++ b/internal/controller/managedcluster_controller.go @@ -95,8 +95,7 @@ func (r *ManagedClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque l.Error(err, "Failed to get Management object") return ctrl.Result{}, err } - if err := telemetry.TrackManagedClusterCreate( - string(mgmt.UID), string(managedCluster.UID), managedCluster.Spec.Template, managedCluster.Spec.DryRun); err != nil { + if err := telemetry.TrackManagedClusterCreate(string(mgmt.UID), string(managedCluster.UID), managedCluster.Spec.Template, managedCluster.Spec.DryRun); err != nil { l.Error(err, "Failed to track ManagedCluster creation") } } @@ -104,9 +103,7 @@ func (r *ManagedClusterReconciler) Reconcile(ctx context.Context, req ctrl.Reque return r.Update(ctx, managedCluster) } -func (r *ManagedClusterReconciler) setStatusFromClusterStatus( - ctx context.Context, managedCluster *hmc.ManagedCluster, -) (bool, error) { +func (r *ManagedClusterReconciler) setStatusFromClusterStatus(ctx context.Context, managedCluster *hmc.ManagedCluster) (requeue bool, _ error) { l := ctrl.LoggerFrom(ctx) resourceConditions, err := status.GetResourceConditions(ctx, managedCluster.Namespace, r.DynamicClient, schema.GroupVersionResource{ @@ -115,8 +112,7 @@ func (r *ManagedClusterReconciler) setStatusFromClusterStatus( Resource: "clusters", }, labels.SelectorFromSet(map[string]string{hmc.FluxHelmChartNameKey: managedCluster.Name}).String()) if err != nil { - notFoundErr := status.ResourceNotFoundError{} - if errors.As(err, ¬FoundErr) { + if errors.As(err, &status.ResourceNotFoundError{}) { l.Info(err.Error()) return true, nil } @@ -130,7 +126,7 @@ func (r *ManagedClusterReconciler) setStatusFromClusterStatus( } if metaCondition.Reason == "" && metaCondition.Status == "True" { - metaCondition.Reason = "Succeeded" + metaCondition.Reason = hmc.SucceededReason } apimeta.SetStatusCondition(managedCluster.GetConditions(), metaCondition) } @@ -411,7 +407,7 @@ func (r *ManagedClusterReconciler) updateServices(ctx context.Context, mc *hmc.M } var servicesStatus []hmc.ServiceStatus - servicesStatus, servicesErr = updateServicesStatus(ctx, r.Client, profileRef, sveltosv1beta1.ProfileKind, profile.Status, mc.Status.Services) + servicesStatus, servicesErr = updateServicesStatus(ctx, r.Client, profileRef, profile.Status.MatchingClusterRefs, mc.Status.Services) if servicesErr != nil { return ctrl.Result{}, nil } @@ -737,7 +733,7 @@ func (r *ManagedClusterReconciler) setAvailableUpgrades(ctx context.Context, man chains := &hmc.ClusterTemplateChainList{} err := r.List(ctx, chains, client.InNamespace(template.Namespace), - client.MatchingFields{hmc.SupportedTemplateKey: template.GetName()}, + client.MatchingFields{hmc.TemplateChainSupportedTemplatesIndexKey: template.GetName()}, ) if err != nil { return err @@ -768,13 +764,8 @@ func (r *ManagedClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { For(&hmc.ManagedCluster{}). Watches(&hcv2.HelmRelease{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []ctrl.Request { - managedCluster := hmc.ManagedCluster{} - managedClusterRef := client.ObjectKey{ - Namespace: o.GetNamespace(), - Name: o.GetName(), - } - err := r.Client.Get(ctx, managedClusterRef, &managedCluster) - if err != nil { + managedClusterRef := client.ObjectKeyFromObject(o) + if err := r.Client.Get(ctx, managedClusterRef, &hmc.ManagedCluster{}); err != nil { return []ctrl.Request{} } @@ -793,7 +784,7 @@ func (r *ManagedClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { managedClusters := &hmc.ManagedClusterList{} err := r.Client.List(ctx, managedClusters, client.InNamespace(chain.Namespace), - client.MatchingFields{hmc.TemplateKey: template}) + client.MatchingFields{hmc.ManagedClusterTemplateIndexKey: template}) if err != nil { return []ctrl.Request{} } diff --git a/internal/controller/management_controller.go b/internal/controller/management_controller.go index 1bab92ac8..88b0dbb55 100644 --- a/internal/controller/management_controller.go +++ b/internal/controller/management_controller.go @@ -275,8 +275,7 @@ func (r *ManagementReconciler) checkProviderStatus(ctx context.Context, provider labels.SelectorFromSet(map[string]string{hmc.FluxHelmChartNameKey: providerTemplateName}).String(), ) if err != nil { - notFoundErr := status.ResourceNotFoundError{} - if errors.As(err, ¬FoundErr) { + if errors.As(err, &status.ResourceNotFoundError{}) { // Check the next resource type. continue } @@ -292,16 +291,12 @@ func (r *ManagementReconciler) checkProviderStatus(ctx context.Context, provider } if len(falseConditionTypes) > 0 { - errs = errors.Join(fmt.Errorf("%s: %s is not yet ready, has false conditions: %s", + errs = errors.Join(errs, fmt.Errorf("%s: %s is not yet ready, has false conditions: %s", resourceConditions.Name, resourceConditions.Kind, strings.Join(falseConditionTypes, ", "))) } } - if errs != nil { - return errs - } - - return nil + return errs } func (r *ManagementReconciler) Delete(ctx context.Context, management *hmc.Management) (ctrl.Result, error) { @@ -439,6 +434,8 @@ func getWrappedComponents(ctx context.Context, cl client.Client, mgmt *hmc.Manag } components = append(components, capiComp) + const sveltosTargetNamespace = "projectsveltos" + for _, p := range mgmt.Spec.Providers { c := component{ Component: p.Component, helmReleaseName: p.Name, @@ -450,8 +447,8 @@ func getWrappedComponents(ctx context.Context, cl client.Client, mgmt *hmc.Manag } if p.Name == hmc.ProviderSveltosName { - c.targetNamespace = hmc.ProviderSveltosTargetNamespace - c.createNamespace = hmc.ProviderSveltosCreateNamespace + c.targetNamespace = sveltosTargetNamespace + c.createNamespace = true } components = append(components, c) diff --git a/internal/controller/multiclusterservice_controller.go b/internal/controller/multiclusterservice_controller.go index 129054451..f7bb62bc1 100644 --- a/internal/controller/multiclusterservice_controller.go +++ b/internal/controller/multiclusterservice_controller.go @@ -24,10 +24,10 @@ import ( sveltosv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" sveltoscontrollers "github.com/projectsveltos/addon-controller/controllers" libsveltosv1beta1 "github.com/projectsveltos/libsveltos/api/v1beta1" + corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" apimeta "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -151,7 +151,7 @@ func (r *MultiClusterServiceReconciler) reconcileUpdate(ctx context.Context, mcs } var servicesStatus []hmc.ServiceStatus - servicesStatus, servicesErr = updateServicesStatus(ctx, r.Client, profileRef, sveltosv1beta1.ClusterProfileKind, profile.Status, mcs.Status.Services) + servicesStatus, servicesErr = updateServicesStatus(ctx, r.Client, profileRef, profile.Status.MatchingClusterRefs, mcs.Status.Services) if servicesErr != nil { return ctrl.Result{}, nil } @@ -301,8 +301,13 @@ func updateStatusConditions(conditions []metav1.Condition, readyMsg string) []me } // updateServicesStatus updates the services deployment status. -func updateServicesStatus(ctx context.Context, c client.Client, profileRef types.NamespacedName, profileKind string, profileStatus sveltosv1beta1.Status, servicesStatus []hmc.ServiceStatus) ([]hmc.ServiceStatus, error) { - for _, obj := range profileStatus.MatchingClusterRefs { +func updateServicesStatus(ctx context.Context, c client.Client, profileRef client.ObjectKey, profileStatusMatchingClusterRefs []corev1.ObjectReference, servicesStatus []hmc.ServiceStatus) ([]hmc.ServiceStatus, error) { + profileKind := sveltosv1beta1.ProfileKind + if profileRef.Namespace == "" { + profileKind = sveltosv1beta1.ClusterProfileKind + } + + for _, obj := range profileStatusMatchingClusterRefs { isSveltosCluster := obj.APIVersion == libsveltosv1beta1.GroupVersion.String() summaryName := sveltoscontrollers.GetClusterSummaryName(profileKind, profileRef.Name, obj.Name, isSveltosCluster) diff --git a/internal/controller/release_controller.go b/internal/controller/release_controller.go index d54459646..42a93b91a 100644 --- a/internal/controller/release_controller.go +++ b/internal/controller/release_controller.go @@ -284,7 +284,7 @@ func (r *ReleaseReconciler) reconcileHMCTemplates(ctx context.Context, releaseNa func (r *ReleaseReconciler) getCurrentReleaseName(ctx context.Context) (string, error) { releases := &hmc.ReleaseList{} listOptions := client.ListOptions{ - FieldSelector: fields.SelectorFromSet(fields.Set{hmc.ReleaseVersionKey: build.Version}), + FieldSelector: fields.SelectorFromSet(fields.Set{hmc.ReleaseVersionIndexKey: build.Version}), } if err := r.Client.List(ctx, releases, &listOptions); err != nil { return "", err diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go index 5b6aeb35a..560f7c02f 100644 --- a/internal/controller/template_controller.go +++ b/internal/controller/template_controller.go @@ -29,7 +29,6 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" @@ -151,7 +150,7 @@ func (r *ProviderTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Req func (r *ProviderTemplateReconciler) setReleaseOwnership(ctx context.Context, providerTemplate *hmc.ProviderTemplate) (changed bool, err error) { releases := &hmc.ReleaseList{} err = r.Client.List(ctx, releases, - client.MatchingFields{hmc.ReleaseTemplatesKey: providerTemplate.Name}, + client.MatchingFields{hmc.ReleaseTemplatesIndexKey: providerTemplate.Name}, ) if err != nil { return changed, fmt.Errorf("failed to get associated releases: %w", err) @@ -371,8 +370,7 @@ func (r *ClusterTemplateReconciler) validateCompatibilityAttrs(ctx context.Conte exposedProviders, requiredProviders := management.Status.AvailableProviders, template.Status.Providers l := ctrl.LoggerFrom(ctx) - l.V(1).Info("providers to check", "exposed", exposedProviders, "required", requiredProviders, - "exposed_capi_contract_versions", management.Status.CAPIContracts, "required_provider_contract_versions", template.Status.ProviderContracts) + l.V(1).Info("providers to check", "exposed", exposedProviders, "required", requiredProviders) var ( merr error @@ -448,13 +446,15 @@ func (r *ProviderTemplateReconciler) SetupWithManager(mgr ctrl.Manager) error { if !ok { return nil } + templates := release.Templates() requests := make([]ctrl.Request, 0, len(templates)) for _, template := range templates { requests = append(requests, ctrl.Request{ - NamespacedName: types.NamespacedName{Name: template}, + NamespacedName: client.ObjectKey{Name: template}, }) } + return requests }), builder.WithPredicates(predicate.Funcs{ diff --git a/internal/webhook/managedcluster_webhook_test.go b/internal/webhook/managedcluster_webhook_test.go index 87a1ed148..41d881ea3 100644 --- a/internal/webhook/managedcluster_webhook_test.go +++ b/internal/webhook/managedcluster_webhook_test.go @@ -109,11 +109,11 @@ func TestManagedClusterValidateCreate(t *testing.T) { cred, template.NewClusterTemplate( template.WithName(testTemplateName), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), ), template.NewServiceTemplate( @@ -154,11 +154,11 @@ func TestManagedClusterValidateCreate(t *testing.T) { cred, template.NewClusterTemplate( template.WithName(testTemplateName), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), ), template.NewServiceTemplate( @@ -183,11 +183,11 @@ func TestManagedClusterValidateCreate(t *testing.T) { cred, template.NewClusterTemplate( template.WithName(testTemplateName), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), ), template.NewServiceTemplate( @@ -230,11 +230,11 @@ func TestManagedClusterValidateCreate(t *testing.T) { mgmt, template.NewClusterTemplate( template.WithName(testTemplateName), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), ), }, @@ -259,11 +259,11 @@ func TestManagedClusterValidateCreate(t *testing.T) { ), template.NewClusterTemplate( template.WithName(testTemplateName), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), ), }, @@ -286,11 +286,11 @@ func TestManagedClusterValidateCreate(t *testing.T) { ), template.NewClusterTemplate( template.WithName(testTemplateName), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-azure", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), ), }, @@ -373,20 +373,20 @@ func TestManagedClusterValidateUpdate(t *testing.T) { template.NewClusterTemplate( template.WithName(testTemplateName), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), ), template.NewClusterTemplate( template.WithName(upgradeTargetTemplateName), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), ), }, warnings: admission.Warnings{fmt.Sprintf("Cluster can't be upgraded from %s to %s. This upgrade sequence is not allowed", testTemplateName, upgradeTargetTemplateName)}, @@ -408,20 +408,20 @@ func TestManagedClusterValidateUpdate(t *testing.T) { template.NewClusterTemplate( template.WithName(testTemplateName), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), ), template.NewClusterTemplate( template.WithName(newTemplateName), template.WithValidationStatus(v1alpha1.TemplateValidationStatus{Valid: true}), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), ), }, }, @@ -446,11 +446,11 @@ func TestManagedClusterValidateUpdate(t *testing.T) { Valid: false, ValidationError: "validation error example", }), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), ), }, }, @@ -476,11 +476,11 @@ func TestManagedClusterValidateUpdate(t *testing.T) { Valid: false, ValidationError: "validation error example", }), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), ), template.NewServiceTemplate( template.WithName(testSvcTemplate1Name), @@ -510,11 +510,11 @@ func TestManagedClusterValidateUpdate(t *testing.T) { Valid: false, ValidationError: "validation error example", }), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), ), template.NewServiceTemplate( template.WithName(testSvcTemplate1Name), @@ -544,11 +544,11 @@ func TestManagedClusterValidateUpdate(t *testing.T) { Valid: false, ValidationError: "validation error example", }), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), ), template.NewServiceTemplate( template.WithName(testSvcTemplate1Name), @@ -580,11 +580,11 @@ func TestManagedClusterValidateUpdate(t *testing.T) { Valid: false, ValidationError: "validation error example", }), - template.WithProvidersStatus(v1alpha1.Providers{ + template.WithProvidersStatus( "infrastructure-aws", "control-plane-k0smotron", "bootstrap-k0smotron", - }), + ), ), template.NewServiceTemplate( template.WithName(testSvcTemplate1Name), diff --git a/internal/webhook/management_webhook.go b/internal/webhook/management_webhook.go index 16fee9b8c..0d33ac06a 100644 --- a/internal/webhook/management_webhook.go +++ b/internal/webhook/management_webhook.go @@ -18,9 +18,12 @@ import ( "context" "errors" "fmt" + "slices" + "strings" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -55,21 +58,115 @@ func (*ManagementValidator) ValidateCreate(_ context.Context, _ runtime.Object) } // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type. -func (v *ManagementValidator) ValidateUpdate(ctx context.Context, _, newObj runtime.Object) (admission.Warnings, error) { +func (v *ManagementValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) { const invalidMgmtMsg = "the Management is invalid" - mgmt, ok := newObj.(*hmcv1alpha1.Management) + newMgmt, ok := newObj.(*hmcv1alpha1.Management) if !ok { return nil, apierrors.NewBadRequest(fmt.Sprintf("expected Management but got a %T", newObj)) } + oldMgmt, ok := oldObj.(*hmcv1alpha1.Management) + if !ok { + return nil, apierrors.NewBadRequest(fmt.Sprintf("expected Management but got a %T", oldObj)) + } + + if err := checkComponentsRemoval(ctx, v.Client, oldMgmt, newMgmt); err != nil { + return admission.Warnings{"Some of the providers cannot be removed"}, + apierrors.NewInvalid(newMgmt.GroupVersionKind().GroupKind(), newMgmt.Name, field.ErrorList{ + field.Forbidden(field.NewPath("spec", "providers"), err.Error()), + }) + } + + incompatibleContracts, err := getIncompatibleContracts(ctx, v, newMgmt) + if err != nil { + return nil, fmt.Errorf("%s: %w", invalidMgmtMsg, err) + } + + if incompatibleContracts != "" { + return admission.Warnings{"The Management object has incompatible CAPI contract versions in ProviderTemplates"}, fmt.Errorf("%s: %s", invalidMgmtMsg, incompatibleContracts) + } + + return nil, nil +} + +func checkComponentsRemoval(ctx context.Context, cl client.Client, oldMgmt, newMgmt *hmcv1alpha1.Management) error { + removedComponents := []hmcv1alpha1.Provider{} + for _, oldComp := range oldMgmt.Spec.Providers { + if !slices.ContainsFunc(newMgmt.Spec.Providers, func(newComp hmcv1alpha1.Provider) bool { return oldComp.Name == newComp.Name }) { + removedComponents = append(removedComponents, oldComp) + } + } + + if len(removedComponents) == 0 { + return nil + } + release := new(hmcv1alpha1.Release) - if err := v.Get(ctx, client.ObjectKey{Name: mgmt.Spec.Release}, release); err != nil { + if err := cl.Get(ctx, client.ObjectKey{Name: newMgmt.Spec.Release}, release); err != nil { + return fmt.Errorf("failed to get Release %s: %w", newMgmt.Spec.Release, err) + } + + removedProvidersSet := make(map[string]struct{}) + for _, m := range removedComponents { + tplRef := m.Template + if tplRef == "" { + tplRef = release.ProviderTemplate(m.Name) + } + + // it does not matter if component has been successfully installed + prTpl := new(hmcv1alpha1.ProviderTemplate) + if err := cl.Get(ctx, client.ObjectKey{Name: tplRef}, prTpl); err != nil { + return fmt.Errorf("failed to get ProviderTemplate %s: %w", tplRef, err) + } + + for _, pn := range prTpl.Status.Providers { + removedProvidersSet[pn] = struct{}{} + } + } + + if len(removedProvidersSet) == 0 { // sanity + return nil + } + + for providerName := range removedProvidersSet { + clusterTemplates := new(hmcv1alpha1.ClusterTemplateList) + if err := cl.List(ctx, clusterTemplates, client.MatchingFields{hmcv1alpha1.ClusterTemplateProvidersIndexKey: providerName}); err != nil { + return fmt.Errorf("failed to list ClusterTemplates: %w", err) + } + + if len(clusterTemplates.Items) == 0 { + continue + } + + for _, cltpl := range clusterTemplates.Items { + mcls := new(hmcv1alpha1.ManagedClusterList) + if err := cl.List(ctx, mcls, + client.MatchingFields{hmcv1alpha1.ManagedClusterTemplateIndexKey: cltpl.Name}, + client.Limit(1)); err != nil { + return fmt.Errorf("failed to list ManagedClusters: %w", err) + } + + if len(mcls.Items) == 0 { + continue + } + + return fmt.Errorf("provider %s is required by at least one ManagedCluster (%s) and cannot be removed from the Management %s", providerName, client.ObjectKeyFromObject(&mcls.Items[0]), newMgmt.Name) + } + } + + return nil +} + +func getIncompatibleContracts(ctx context.Context, cl client.Client, mgmt *hmcv1alpha1.Management) (string, error) { + release := new(hmcv1alpha1.Release) + if err := cl.Get(ctx, client.ObjectKey{Name: mgmt.Spec.Release}, release); err != nil { // TODO: probably we do not want this skip if extra checks will be introduced if apierrors.IsNotFound(err) && (mgmt.Spec.Core == nil || mgmt.Spec.Core.CAPI.Template == "") { - return nil, nil // nothing to do + return "", nil // nothing to do } - return nil, fmt.Errorf("failed to get Release %s: %w", mgmt.Spec.Release, err) + + return "", fmt.Errorf("failed to get Release %s: %w", mgmt.Spec.Release, err) } capiTplName := release.Spec.CAPI.Template @@ -78,19 +175,19 @@ func (v *ManagementValidator) ValidateUpdate(ctx context.Context, _, newObj runt } capiTpl := new(hmcv1alpha1.ProviderTemplate) - if err := v.Get(ctx, client.ObjectKey{Name: capiTplName}, capiTpl); err != nil { - return nil, fmt.Errorf("failed to get ProviderTemplate %s: %w", capiTplName, err) + if err := cl.Get(ctx, client.ObjectKey{Name: capiTplName}, capiTpl); err != nil { + return "", fmt.Errorf("failed to get ProviderTemplate %s: %w", capiTplName, err) } if len(capiTpl.Status.CAPIContracts) == 0 { - return nil, nil // nothing to validate against + return "", nil // nothing to validate against } if !capiTpl.Status.Valid { - return nil, fmt.Errorf("%s: not valid ProviderTemplate %s", invalidMgmtMsg, capiTpl.Name) + return "", fmt.Errorf("not valid ProviderTemplate %s", capiTpl.Name) } - var wrongVersions error + incompatibleContracts := strings.Builder{} for _, p := range mgmt.Spec.Providers { tplName := p.Template if tplName == "" { @@ -102,8 +199,8 @@ func (v *ManagementValidator) ValidateUpdate(ctx context.Context, _, newObj runt } pTpl := new(hmcv1alpha1.ProviderTemplate) - if err := v.Get(ctx, client.ObjectKey{Name: tplName}, pTpl); err != nil { - return nil, fmt.Errorf("failed to get ProviderTemplate %s: %w", tplName, err) + if err := cl.Get(ctx, client.ObjectKey{Name: tplName}, pTpl); err != nil { + return "", fmt.Errorf("failed to get ProviderTemplate %s: %w", tplName, err) } if len(pTpl.Status.CAPIContracts) == 0 { @@ -111,21 +208,17 @@ func (v *ManagementValidator) ValidateUpdate(ctx context.Context, _, newObj runt } if !pTpl.Status.Valid { - return nil, fmt.Errorf("%s: not valid ProviderTemplate %s", invalidMgmtMsg, tplName) + return "", fmt.Errorf("not valid ProviderTemplate %s", tplName) } for capiVersion := range pTpl.Status.CAPIContracts { if _, ok := capiTpl.Status.CAPIContracts[capiVersion]; !ok { - wrongVersions = errors.Join(wrongVersions, fmt.Errorf("core CAPI contract versions does not support %s version in the ProviderTemplate %s", capiVersion, pTpl.Name)) + _, _ = incompatibleContracts.WriteString(fmt.Sprintf("core CAPI contract versions does not support %s version in the ProviderTemplate %s, ", capiVersion, pTpl.Name)) } } } - if wrongVersions != nil { - return admission.Warnings{"The Management object has incompatible CAPI contract versions in ProviderTemplates"}, fmt.Errorf("%s: %s", invalidMgmtMsg, wrongVersions.Error()) - } - - return nil, nil + return strings.TrimSuffix(incompatibleContracts.String(), ", "), nil } // ValidateDelete implements webhook.Validator so a webhook will be registered for the type. diff --git a/internal/webhook/management_webhook_test.go b/internal/webhook/management_webhook_test.go index 37a4c55c6..68c6fe86c 100644 --- a/internal/webhook/management_webhook_test.go +++ b/internal/webhook/management_webhook_test.go @@ -42,12 +42,15 @@ func TestManagementValidateUpdate(t *testing.T) { someContractVersion = "v1alpha4_v1beta1" capiVersion = "v1beta1" capiVersionOther = "v1alpha3" + + infraAWSProvider = "infrastructure-aws" + infraOtherProvider = "infrastructure-other-provider" ) validStatus := v1alpha1.TemplateValidationStatus{Valid: true} - providerAwsDefaultTpl := v1alpha1.Provider{ - Name: "infrastructure-aws", + componentAwsDefaultTpl := v1alpha1.Provider{ + Name: v1alpha1.ProviderCAPAName, Component: v1alpha1.Component{ Template: template.DefaultName, }, @@ -55,23 +58,126 @@ func TestManagementValidateUpdate(t *testing.T) { tests := []struct { name string + oldMgmt *v1alpha1.Management management *v1alpha1.Management existingObjects []runtime.Object err string warnings admission.Warnings }{ + { + name: "no providers removed, should succeed", + oldMgmt: management.NewManagement(management.WithProviders(componentAwsDefaultTpl)), + management: management.NewManagement(management.WithProviders(componentAwsDefaultTpl)), + }, + { + name: "release does not exist, should fail", + oldMgmt: management.NewManagement( + management.WithProviders(componentAwsDefaultTpl), + ), + management: management.NewManagement( + management.WithProviders(), + management.WithRelease(release.DefaultName), + ), + warnings: admission.Warnings{"Some of the providers cannot be removed"}, + err: fmt.Sprintf(`Management "%s" is invalid: spec.providers: Forbidden: failed to get Release %s: releases.hmc.mirantis.com "%s" not found`, management.DefaultName, release.DefaultName, release.DefaultName), + }, + { + name: "removed provider does not have related providertemplate, should fail", + oldMgmt: management.NewManagement( + management.WithProviders(componentAwsDefaultTpl), + ), + management: management.NewManagement( + management.WithProviders(), + management.WithRelease(release.DefaultName), + ), + existingObjects: []runtime.Object{ + release.New(), + }, + warnings: admission.Warnings{"Some of the providers cannot be removed"}, + err: fmt.Sprintf(`Management "%s" is invalid: spec.providers: Forbidden: failed to get ProviderTemplate %s: providertemplates.hmc.mirantis.com "%s" not found`, management.DefaultName, template.DefaultName, template.DefaultName), + }, + { + name: "no cluster templates, should succeed", + oldMgmt: management.NewManagement( + management.WithProviders(componentAwsDefaultTpl), + ), + management: management.NewManagement( + management.WithProviders(), + management.WithRelease(release.DefaultName), + ), + existingObjects: []runtime.Object{ + release.New(), + template.NewProviderTemplate(template.WithName(release.DefaultCAPITemplateName)), + template.NewProviderTemplate(template.WithProvidersStatus(infraAWSProvider)), + }, + }, + { + name: "cluster template from removed provider exists but no managed clusters, should succeed", + oldMgmt: management.NewManagement( + management.WithProviders(componentAwsDefaultTpl), + ), + management: management.NewManagement( + management.WithProviders(), + management.WithRelease(release.DefaultName), + ), + existingObjects: []runtime.Object{ + release.New(), + template.NewProviderTemplate(template.WithName(release.DefaultCAPITemplateName)), + template.NewProviderTemplate(template.WithProvidersStatus(infraAWSProvider)), + template.NewClusterTemplate(template.WithProvidersStatus(infraAWSProvider)), + }, + }, + { + name: "managed cluster uses the removed provider, should fail", + oldMgmt: management.NewManagement( + management.WithProviders(componentAwsDefaultTpl), + ), + management: management.NewManagement( + management.WithProviders(), + management.WithRelease(release.DefaultName), + ), + existingObjects: []runtime.Object{ + release.New(), + template.NewProviderTemplate(template.WithProvidersStatus(infraAWSProvider)), + template.NewProviderTemplate(template.WithName(release.DefaultCAPITemplateName)), + template.NewClusterTemplate(template.WithProvidersStatus(infraAWSProvider)), + managedcluster.NewManagedCluster(managedcluster.WithClusterTemplate(template.DefaultName)), + }, + warnings: admission.Warnings{"Some of the providers cannot be removed"}, + err: fmt.Sprintf(`Management "%s" is invalid: spec.providers: Forbidden: provider %s is required by at least one ManagedCluster (%s/%s) and cannot be removed from the Management %s`, management.DefaultName, infraAWSProvider, managedcluster.DefaultNamespace, managedcluster.DefaultName, management.DefaultName), + }, + { + name: "managed cluster does not use the removed provider, should succeed", + oldMgmt: management.NewManagement( + management.WithProviders(componentAwsDefaultTpl), + ), + management: management.NewManagement( + management.WithProviders(), + management.WithRelease(release.DefaultName), + ), + existingObjects: []runtime.Object{ + release.New(), + template.NewProviderTemplate(template.WithProvidersStatus(infraAWSProvider)), + template.NewProviderTemplate(template.WithName(release.DefaultCAPITemplateName)), + template.NewClusterTemplate(template.WithProvidersStatus(infraOtherProvider)), + managedcluster.NewManagedCluster(managedcluster.WithClusterTemplate(template.DefaultName)), + }, + }, { name: "no release and no core capi tpl set, should succeed", + oldMgmt: management.NewManagement(), management: management.NewManagement(), }, { name: "no capi providertemplate, should fail", + oldMgmt: management.NewManagement(), management: management.NewManagement(management.WithRelease(release.DefaultName)), existingObjects: []runtime.Object{release.New()}, - err: fmt.Sprintf(`failed to get ProviderTemplate %s: providertemplates.hmc.mirantis.com "%s" not found`, release.DefaultCAPITemplateName, release.DefaultCAPITemplateName), + err: fmt.Sprintf(`the Management is invalid: failed to get ProviderTemplate %s: providertemplates.hmc.mirantis.com "%s" not found`, release.DefaultCAPITemplateName, release.DefaultCAPITemplateName), }, { name: "capi providertemplate without capi version set, should succeed", + oldMgmt: management.NewManagement(), management: management.NewManagement(management.WithRelease(release.DefaultName)), existingObjects: []runtime.Object{ release.New(), @@ -80,6 +186,7 @@ func TestManagementValidateUpdate(t *testing.T) { }, { name: "capi providertemplate is not valid, should fail", + oldMgmt: management.NewManagement(), management: management.NewManagement(management.WithRelease(release.DefaultName)), existingObjects: []runtime.Object{ release.New(), @@ -91,10 +198,11 @@ func TestManagementValidateUpdate(t *testing.T) { err: "the Management is invalid: not valid ProviderTemplate " + release.DefaultCAPITemplateName, }, { - name: "no providertemplates that declared in mgmt spec.providers, should fail", + name: "no providertemplates that declared in mgmt spec.providers, should fail", + oldMgmt: management.NewManagement(), management: management.NewManagement( management.WithRelease(release.DefaultName), - management.WithProviders([]v1alpha1.Provider{providerAwsDefaultTpl}), + management.WithProviders(componentAwsDefaultTpl), ), existingObjects: []runtime.Object{ release.New(), @@ -104,13 +212,14 @@ func TestManagementValidateUpdate(t *testing.T) { template.WithValidationStatus(validStatus), ), }, - err: fmt.Sprintf(`failed to get ProviderTemplate %s: providertemplates.hmc.mirantis.com "%s" not found`, template.DefaultName, template.DefaultName), + err: fmt.Sprintf(`the Management is invalid: failed to get ProviderTemplate %s: providertemplates.hmc.mirantis.com "%s" not found`, template.DefaultName, template.DefaultName), }, { - name: "providertemplates without specified capi contracts, should succeed", + name: "providertemplates without specified capi contracts, should succeed", + oldMgmt: management.NewManagement(), management: management.NewManagement( management.WithRelease(release.DefaultName), - management.WithProviders([]v1alpha1.Provider{providerAwsDefaultTpl}), + management.WithProviders(componentAwsDefaultTpl), ), existingObjects: []runtime.Object{ release.New(), @@ -123,10 +232,11 @@ func TestManagementValidateUpdate(t *testing.T) { }, }, { - name: "providertemplates is not ready, should succeed", + name: "providertemplates is not ready, should succeed", + oldMgmt: management.NewManagement(), management: management.NewManagement( management.WithRelease(release.DefaultName), - management.WithProviders([]v1alpha1.Provider{providerAwsDefaultTpl}), + management.WithProviders(componentAwsDefaultTpl), ), existingObjects: []runtime.Object{ release.New(), @@ -142,10 +252,11 @@ func TestManagementValidateUpdate(t *testing.T) { err: "the Management is invalid: not valid ProviderTemplate " + template.DefaultName, }, { - name: "providertemplates do not match capi contracts, should fail", + name: "providertemplates do not match capi contracts, should fail", + oldMgmt: management.NewManagement(), management: management.NewManagement( management.WithRelease(release.DefaultName), - management.WithProviders([]v1alpha1.Provider{providerAwsDefaultTpl}), + management.WithProviders(componentAwsDefaultTpl), ), existingObjects: []runtime.Object{ release.New(), @@ -163,10 +274,11 @@ func TestManagementValidateUpdate(t *testing.T) { err: fmt.Sprintf("the Management is invalid: core CAPI contract versions does not support %s version in the ProviderTemplate %s", capiVersionOther, template.DefaultName), }, { - name: "providertemplates match capi contracts, should succeed", + name: "providertemplates match capi contracts, should succeed", + oldMgmt: management.NewManagement(), management: management.NewManagement( management.WithRelease(release.DefaultName), - management.WithProviders([]v1alpha1.Provider{providerAwsDefaultTpl}), + management.WithProviders(componentAwsDefaultTpl), ), existingObjects: []runtime.Object{ release.New(), @@ -185,10 +297,15 @@ func TestManagementValidateUpdate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(_ *testing.T) { - c := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(tt.existingObjects...).Build() + c := fake.NewClientBuilder(). + WithScheme(scheme.Scheme). + WithRuntimeObjects(tt.existingObjects...). + WithIndex(&v1alpha1.ClusterTemplate{}, v1alpha1.ClusterTemplateProvidersIndexKey, v1alpha1.ExtractProvidersFromClusterTemplate). + WithIndex(&v1alpha1.ManagedCluster{}, v1alpha1.ManagedClusterTemplateIndexKey, v1alpha1.ExtractTemplateNameFromManagedCluster). + Build() validator := &ManagementValidator{Client: c} - warnings, err := validator.ValidateUpdate(ctx, nil, tt.management) + warnings, err := validator.ValidateUpdate(ctx, tt.oldMgmt, tt.management) if tt.err != "" { g.Expect(err).To(HaveOccurred()) g.Expect(err).To(MatchError(tt.err)) diff --git a/internal/webhook/template_webhook.go b/internal/webhook/template_webhook.go index 7f4dda043..e6dfe9e47 100644 --- a/internal/webhook/template_webhook.go +++ b/internal/webhook/template_webhook.go @@ -69,7 +69,7 @@ func (v *ClusterTemplateValidator) ValidateDelete(ctx context.Context, obj runti managedClusters := &v1alpha1.ManagedClusterList{} if err := v.Client.List(ctx, managedClusters, client.InNamespace(template.Namespace), - client.MatchingFields{v1alpha1.TemplateKey: template.Name}, + client.MatchingFields{v1alpha1.ManagedClusterTemplateIndexKey: template.Name}, client.Limit(1)); err != nil { return nil, err } @@ -125,7 +125,7 @@ func (v *ServiceTemplateValidator) ValidateDelete(ctx context.Context, obj runti managedClusters := &v1alpha1.ManagedClusterList{} if err := v.Client.List(ctx, managedClusters, client.InNamespace(tmpl.Namespace), - client.MatchingFields{v1alpha1.ServicesTemplateKey: tmpl.Name}, + client.MatchingFields{v1alpha1.ManagedClusterServiceTemplatesIndexKey: tmpl.Name}, client.Limit(1)); err != nil { return nil, err } @@ -138,7 +138,7 @@ func (v *ServiceTemplateValidator) ValidateDelete(ctx context.Context, obj runti if tmpl.Namespace == v.SystemNamespace { multiSvcClusters := &v1alpha1.MultiClusterServiceList{} if err := v.Client.List(ctx, multiSvcClusters, - client.MatchingFields{v1alpha1.ServicesTemplateKey: tmpl.Name}, + client.MatchingFields{v1alpha1.MultiClusterServiceTemplatesIndexKey: tmpl.Name}, client.Limit(1)); err != nil { return nil, err } diff --git a/internal/webhook/template_webhook_test.go b/internal/webhook/template_webhook_test.go index 8672d8b32..bdc034dd2 100644 --- a/internal/webhook/template_webhook_test.go +++ b/internal/webhook/template_webhook_test.go @@ -80,7 +80,7 @@ func TestClusterTemplateValidateDelete(t *testing.T) { c := fake.NewClientBuilder(). WithScheme(scheme.Scheme). WithRuntimeObjects(tt.existingObjects...). - WithIndex(tt.existingObjects[0], v1alpha1.TemplateKey, v1alpha1.ExtractTemplateName). + WithIndex(&v1alpha1.ManagedCluster{}, v1alpha1.ManagedClusterTemplateIndexKey, v1alpha1.ExtractTemplateNameFromManagedCluster). Build() validator := &ClusterTemplateValidator{Client: c} warn, err := validator.ValidateDelete(ctx, tt.template) @@ -159,8 +159,8 @@ func TestServiceTemplateValidateDelete(t *testing.T) { NewClientBuilder(). WithScheme(scheme.Scheme). WithRuntimeObjects(tt.existingObjects...). - WithIndex(&v1alpha1.ManagedCluster{}, v1alpha1.ServicesTemplateKey, v1alpha1.ExtractServiceTemplateFromManagedCluster). - WithIndex(&v1alpha1.MultiClusterService{}, v1alpha1.ServicesTemplateKey, v1alpha1.ExtractServiceTemplateFromMultiClusterService). + WithIndex(&v1alpha1.ManagedCluster{}, v1alpha1.ManagedClusterServiceTemplatesIndexKey, v1alpha1.ExtractServiceTemplateNamesFromManagedCluster). + WithIndex(&v1alpha1.MultiClusterService{}, v1alpha1.MultiClusterServiceTemplatesIndexKey, v1alpha1.ExtractServiceTemplateNamesFromMultiClusterService). Build() validator := &ServiceTemplateValidator{Client: c, SystemNamespace: testSystemNamespace} warn, err := validator.ValidateDelete(ctx, tt.template) diff --git a/internal/webhook/templatemanagement_webhook_test.go b/internal/webhook/templatemanagement_webhook_test.go index 45d4a4718..b23a53e0c 100644 --- a/internal/webhook/templatemanagement_webhook_test.go +++ b/internal/webhook/templatemanagement_webhook_test.go @@ -60,7 +60,7 @@ func TestTemplateManagementValidateCreate(t *testing.T) { c := fake.NewClientBuilder(). WithScheme(scheme.Scheme). WithRuntimeObjects(tt.existingObjects...). - WithIndex(&v1alpha1.ManagedCluster{}, v1alpha1.TemplateKey, v1alpha1.ExtractTemplateName). + WithIndex(&v1alpha1.ManagedCluster{}, v1alpha1.ManagedClusterTemplateIndexKey, v1alpha1.ExtractTemplateNameFromManagedCluster). Build() validator := &TemplateManagementValidator{Client: c, SystemNamespace: utils.DefaultSystemNamespace} warn, err := validator.ValidateCreate(ctx, tt.tm) @@ -117,7 +117,7 @@ func TestTemplateManagementValidateDelete(t *testing.T) { c := fake.NewClientBuilder(). WithScheme(scheme.Scheme). WithRuntimeObjects(tt.existingObjects...). - WithIndex(&v1alpha1.ManagedCluster{}, v1alpha1.TemplateKey, v1alpha1.ExtractTemplateName). + WithIndex(&v1alpha1.ManagedCluster{}, v1alpha1.ManagedClusterTemplateIndexKey, v1alpha1.ExtractTemplateNameFromManagedCluster). Build() validator := &TemplateManagementValidator{Client: c, SystemNamespace: utils.DefaultSystemNamespace} warn, err := validator.ValidateDelete(ctx, tt.tm) diff --git a/templates/provider/hmc/templates/crds/hmc.mirantis.com_clustertemplates.yaml b/templates/provider/hmc/templates/crds/hmc.mirantis.com_clustertemplates.yaml index acb5ac0bf..94bfcbb20 100644 --- a/templates/provider/hmc/templates/crds/hmc.mirantis.com_clustertemplates.yaml +++ b/templates/provider/hmc/templates/crds/hmc.mirantis.com_clustertemplates.yaml @@ -120,9 +120,8 @@ spec: type: object providers: description: |- - Providers represent required CAPI providers with supported contract versions. + Providers represent required CAPI providers. Should be set if not present in the Helm chart metadata. - Compatibility attributes are optional to be defined. items: type: string type: array @@ -193,9 +192,7 @@ spec: [contract versions]: https://cluster-api.sigs.k8s.io/developer/providers/contracts type: object providers: - description: |- - Providers represent required CAPI providers with supported contract versions - if the latter has been given. + description: Providers represent required CAPI providers. items: type: string type: array diff --git a/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml b/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml index af53adbdc..1dc941cca 100644 --- a/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml +++ b/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml @@ -112,9 +112,7 @@ spec: description: ManagementStatus defines the observed state of Management properties: availableProviders: - description: |- - AvailableProviders holds all CAPI providers available along with - their supported contract versions, if specified in ProviderTemplates, on the Management cluster. + description: AvailableProviders holds all available CAPI providers. items: type: string type: array diff --git a/templates/provider/hmc/templates/crds/hmc.mirantis.com_providertemplates.yaml b/templates/provider/hmc/templates/crds/hmc.mirantis.com_providertemplates.yaml index b6c8751ad..3157fb79c 100644 --- a/templates/provider/hmc/templates/crds/hmc.mirantis.com_providertemplates.yaml +++ b/templates/provider/hmc/templates/crds/hmc.mirantis.com_providertemplates.yaml @@ -116,9 +116,8 @@ spec: && has(self.chartRef)) providers: description: |- - Providers represent exposed CAPI providers with supported contract versions. + Providers represent exposed CAPI providers. Should be set if not present in the Helm chart metadata. - Supported contract versions are optional to be defined. items: type: string type: array @@ -183,9 +182,7 @@ spec: format: int64 type: integer providers: - description: |- - Providers represent exposed CAPI providers with supported contract versions - if the latter has been given. + description: Providers represent exposed CAPI providers. items: type: string type: array diff --git a/test/objects/managedcluster/managedcluster.go b/test/objects/managedcluster/managedcluster.go index 4bc2d5db1..7310cb49e 100644 --- a/test/objects/managedcluster/managedcluster.go +++ b/test/objects/managedcluster/managedcluster.go @@ -23,7 +23,7 @@ import ( const ( DefaultName = "managedcluster" - DefaultNamespace = "default" + DefaultNamespace = metav1.NamespaceDefault ) type Opt func(managedCluster *v1alpha1.ManagedCluster) diff --git a/test/objects/management/management.go b/test/objects/management/management.go index b120c6be5..785faa643 100644 --- a/test/objects/management/management.go +++ b/test/objects/management/management.go @@ -28,6 +28,10 @@ type Opt func(management *v1alpha1.Management) func NewManagement(opts ...Opt) *v1alpha1.Management { p := &v1alpha1.Management{ + TypeMeta: metav1.TypeMeta{ + Kind: "Management", + APIVersion: v1alpha1.GroupVersion.Version, + }, ObjectMeta: metav1.ObjectMeta{ Name: DefaultName, Finalizers: []string{v1alpha1.ManagementFinalizer}, @@ -58,7 +62,7 @@ func WithCoreComponents(core *v1alpha1.Core) Opt { } } -func WithProviders(providers []v1alpha1.Provider) Opt { +func WithProviders(providers ...v1alpha1.Provider) Opt { return func(p *v1alpha1.Management) { p.Spec.Providers = providers } diff --git a/test/objects/template/template.go b/test/objects/template/template.go index 3d2b0e550..8518c861d 100644 --- a/test/objects/template/template.go +++ b/test/objects/template/template.go @@ -26,7 +26,7 @@ import ( const ( DefaultName = "template" - DefaultNamespace = "default" + DefaultNamespace = metav1.NamespaceDefault ) type ( @@ -140,27 +140,15 @@ func WithValidationStatus(validationStatus v1alpha1.TemplateValidationStatus) Op } } -func WithProvidersStatus(providers v1alpha1.Providers) Opt { +func WithProvidersStatus(providers ...string) Opt { return func(t Template) { switch v := t.(type) { case *v1alpha1.ClusterTemplate: - var ok bool - v.Status.Providers, ok = any(providers).(v1alpha1.Providers) - if !ok { - panic(fmt.Sprintf("unexpected type %T", providers)) - } + v.Status.Providers = providers case *v1alpha1.ProviderTemplate: - var ok bool - v.Status.Providers, ok = any(providers).(v1alpha1.Providers) - if !ok { - panic(fmt.Sprintf("unexpected type %T", providers)) - } + v.Status.Providers = providers case *v1alpha1.ServiceTemplate: - var ok bool - v.Status.Providers, ok = any(providers).(v1alpha1.Providers) - if !ok { - panic(fmt.Sprintf("unexpected type %T", providers)) - } + v.Status.Providers = providers } } }