From 32388d98efa58ec7b2ebcde12e20c12d4070d6b0 Mon Sep 17 00:00:00 2001 From: Wahab Ali Date: Fri, 20 Dec 2024 11:55:47 -0500 Subject: [PATCH] Expose additional sveltos settings for service deployment --- api/v1alpha1/indexers.go | 6 +- api/v1alpha1/managedcluster_types.go | 23 +- api/v1alpha1/multiclusterservice_types.go | 37 ++- api/v1alpha1/zz_generated.deepcopy.go | 50 +++- .../controller/managedcluster_controller.go | 11 +- .../managedcluster_controller_test.go | 10 +- .../multiclusterservice_controller.go | 13 +- .../multiclusterservice_controller_test.go | 18 +- internal/sveltos/profile.go | 28 +- internal/webhook/managedcluster_webhook.go | 8 +- .../webhook/multiclusterservice_webhook.go | 6 +- .../hmc.mirantis.com_managedclusters.yaml | 277 +++++++++++++---- ...hmc.mirantis.com_multiclusterservices.yaml | 278 ++++++++++++++---- test/objects/managedcluster/managedcluster.go | 2 +- .../multiclusterservice.go | 2 +- 15 files changed, 575 insertions(+), 194 deletions(-) diff --git a/api/v1alpha1/indexers.go b/api/v1alpha1/indexers.go index 684dfce4..8e1bfdf7 100644 --- a/api/v1alpha1/indexers.go +++ b/api/v1alpha1/indexers.go @@ -78,7 +78,7 @@ func ExtractServiceTemplateNamesFromManagedCluster(rawObj client.Object) []strin } templates := []string{} - for _, s := range cluster.Spec.Services { + for _, s := range cluster.Spec.ServiceSpec.Services { templates = append(templates, s.Template) } @@ -204,8 +204,8 @@ func ExtractServiceTemplateNamesFromMultiClusterService(rawObj client.Object) [] return nil } - templates := make([]string, len(mcs.Spec.Services)) - for i, s := range mcs.Spec.Services { + templates := make([]string, len(mcs.Spec.ServiceSpec.Services)) + for i, s := range mcs.Spec.ServiceSpec.Services { templates[i] = s.Template } diff --git a/api/v1alpha1/managedcluster_types.go b/api/v1alpha1/managedcluster_types.go index 404a6de1..68bc05bc 100644 --- a/api/v1alpha1/managedcluster_types.go +++ b/api/v1alpha1/managedcluster_types.go @@ -61,29 +61,10 @@ type ManagedClusterSpec struct { Template string `json:"template"` // Name reference to the related Credentials object. Credential string `json:"credential,omitempty"` - // Services is a list of services created via ServiceTemplates - // that could be installed on the target cluster. - Services []ServiceSpec `json:"services,omitempty"` - - // +kubebuilder:default:=100 - // +kubebuilder:validation:Minimum=1 - // +kubebuilder:validation:Maximum=2147483646 - - // ServicesPriority sets the priority for the services defined in this spec. - // Higher value means higher priority and lower means lower. - // In case of conflict with another object managing the service, - // the one with higher priority will get to deploy its services. - ServicesPriority int32 `json:"servicesPriority,omitempty"` + // ServiceSpec is spec related to deployment of services. + ServiceSpec ServiceSpec `json:"serviceSpec,omitempty"` // DryRun specifies whether the template should be applied after validation or only validated. DryRun bool `json:"dryRun,omitempty"` - - // +kubebuilder:default:=false - - // StopOnConflict specifies what to do in case of a conflict. - // E.g. If another object is already managing a service. - // By default the remaining services will be deployed even if conflict is detected. - // If set to true, the deployment will stop after encountering the first conflict. - StopOnConflict bool `json:"stopOnConflict,omitempty"` } // ManagedClusterStatus defines the observed state of ManagedCluster diff --git a/api/v1alpha1/multiclusterservice_types.go b/api/v1alpha1/multiclusterservice_types.go index 70cd7b1e..9f7d118a 100644 --- a/api/v1alpha1/multiclusterservice_types.go +++ b/api/v1alpha1/multiclusterservice_types.go @@ -15,6 +15,7 @@ package v1alpha1 import ( + sveltosv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -37,8 +38,8 @@ const ( FetchServicesStatusSuccessCondition = "FetchServicesStatusSuccess" ) -// ServiceSpec represents a Service to be managed -type ServiceSpec struct { +// Service represents a Service to be deployed. +type Service struct { // Values is the helm values to be passed to the chart used by the template. // The string type is used in order to allow for templating. Values string `json:"values,omitempty"` @@ -57,27 +58,35 @@ type ServiceSpec struct { // Namespace is the namespace the release will be installed in. // It will default to Name if not provided. Namespace string `json:"namespace,omitempty"` + // ValuesFrom can reference a ConfigMap or Secret containing helm values. + ValuesFrom []sveltosv1beta1.ValueFrom `json:"valuesFrom,omitempty"` // Disable can be set to disable handling of this service. Disable bool `json:"disable,omitempty"` } -// MultiClusterServiceSpec defines the desired state of MultiClusterService -type MultiClusterServiceSpec struct { - // ClusterSelector identifies target clusters to manage services on. - ClusterSelector metav1.LabelSelector `json:"clusterSelector,omitempty"` +// ServiceSpec contains all the spec related to deployment of services. +type ServiceSpec struct { // Services is a list of services created via ServiceTemplates // that could be installed on the target cluster. - Services []ServiceSpec `json:"services,omitempty"` + Services []Service `json:"services,omitempty"` + // TemplateResourceRefs is a list of resources to collect from the management cluster, + // the values from which can be used in templates. + TemplateResourceRefs []sveltosv1beta1.TemplateResourceRef `json:"templateResourceRefs,omitempty"` + // PolicyRefs is a list of kubernetes resources that need to be deployed in matching clusters. + // These resources may contain static values or leverage Go templates for dynamic customization. + // When expressed as templates, the values are rendered using data from resources + // (like Cluster & TemplateResourceRefs) within the management cluster before deployement. + PolicyRefs []sveltosv1beta1.PolicyRef `json:"policyRefs,omitempty"` // +kubebuilder:default:=100 // +kubebuilder:validation:Minimum=1 // +kubebuilder:validation:Maximum=2147483646 - // ServicesPriority sets the priority for the services defined in this spec. + // Priority sets the priority for the services defined in this spec. // Higher value means higher priority and lower means lower. // In case of conflict with another object managing the service, // the one with higher priority will get to deploy its services. - ServicesPriority int32 `json:"servicesPriority,omitempty"` + Priority int32 `json:"priority,omitempty"` // +kubebuilder:default:=false @@ -86,6 +95,16 @@ type MultiClusterServiceSpec struct { // By default the remaining services will be deployed even if conflict is detected. // If set to true, the deployment will stop after encountering the first conflict. StopOnConflict bool `json:"stopOnConflict,omitempty"` + // Reload instances via rolling upgrade when a ConfigMap/Secret mounted as volume is modified. + Reload bool `json:"reload,omitempty"` +} + +// MultiClusterServiceSpec defines the desired state of MultiClusterService +type MultiClusterServiceSpec struct { + // ClusterSelector identifies target clusters to manage services on. + ClusterSelector metav1.LabelSelector `json:"clusterSelector,omitempty"` + // ServiceSpec is spec related to deployment of services. + ServiceSpec ServiceSpec `json:"serviceSpec,omitempty"` } // ServiceStatus contains details for the state of services. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 059d6037..10ae8e0f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -21,6 +21,7 @@ package v1alpha1 import ( "github.com/fluxcd/helm-controller/api/v2" apiv1 "github.com/fluxcd/source-controller/api/v1" + "github.com/projectsveltos/addon-controller/api/v1beta1" velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -739,11 +740,7 @@ func (in *ManagedClusterSpec) DeepCopyInto(out *ManagedClusterSpec) { *out = new(apiextensionsv1.JSON) (*in).DeepCopyInto(*out) } - if in.Services != nil { - in, out := &in.Services, &out.Services - *out = make([]ServiceSpec, len(*in)) - copy(*out, *in) - } + in.ServiceSpec.DeepCopyInto(&out.ServiceSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagedClusterSpec. @@ -1000,11 +997,7 @@ func (in *MultiClusterServiceList) DeepCopyObject() runtime.Object { func (in *MultiClusterServiceSpec) DeepCopyInto(out *MultiClusterServiceSpec) { *out = *in in.ClusterSelector.DeepCopyInto(&out.ClusterSelector) - if in.Services != nil { - in, out := &in.Services, &out.Services - *out = make([]ServiceSpec, len(*in)) - copy(*out, *in) - } + in.ServiceSpec.DeepCopyInto(&out.ServiceSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MultiClusterServiceSpec. @@ -1315,9 +1308,46 @@ func (in *ReleaseStatus) DeepCopy() *ReleaseStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Service) DeepCopyInto(out *Service) { + *out = *in + if in.ValuesFrom != nil { + in, out := &in.ValuesFrom, &out.ValuesFrom + *out = make([]v1beta1.ValueFrom, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Service. +func (in *Service) DeepCopy() *Service { + if in == nil { + return nil + } + out := new(Service) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceSpec) DeepCopyInto(out *ServiceSpec) { *out = *in + if in.Services != nil { + in, out := &in.Services, &out.Services + *out = make([]Service, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.TemplateResourceRefs != nil { + in, out := &in.TemplateResourceRefs, &out.TemplateResourceRefs + *out = make([]v1beta1.TemplateResourceRef, len(*in)) + copy(*out, *in) + } + if in.PolicyRefs != nil { + in, out := &in.PolicyRefs, &out.PolicyRefs + *out = make([]v1beta1.PolicyRef, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceSpec. diff --git a/internal/controller/managedcluster_controller.go b/internal/controller/managedcluster_controller.go index c89fdb1c..ac7f8deb 100644 --- a/internal/controller/managedcluster_controller.go +++ b/internal/controller/managedcluster_controller.go @@ -435,7 +435,7 @@ func (r *ManagedClusterReconciler) updateServices(ctx context.Context, mc *hmc.M err = errors.Join(err, servicesErr) }() - opts, err := sveltos.GetHelmChartOpts(ctx, r.Client, mc.Namespace, mc.Spec.Services) + opts, err := sveltos.GetHelmChartOpts(ctx, r.Client, mc.Namespace, mc.Spec.ServiceSpec.Services) if err != nil { return ctrl.Result{}, err } @@ -454,9 +454,12 @@ func (r *ManagedClusterReconciler) updateServices(ctx context.Context, mc *hmc.M hmc.FluxHelmChartNameKey: mc.Name, }, }, - HelmChartOpts: opts, - Priority: mc.Spec.ServicesPriority, - StopOnConflict: mc.Spec.StopOnConflict, + HelmChartOpts: opts, + Priority: mc.Spec.ServiceSpec.Priority, + StopOnConflict: mc.Spec.ServiceSpec.StopOnConflict, + Reload: mc.Spec.ServiceSpec.Reload, + TemplateResourceRefs: mc.Spec.ServiceSpec.TemplateResourceRefs, + PolicyRefs: mc.Spec.ServiceSpec.PolicyRefs, }); err != nil { return ctrl.Result{}, fmt.Errorf("failed to reconcile Profile: %w", err) } diff --git a/internal/controller/managedcluster_controller_test.go b/internal/controller/managedcluster_controller_test.go index f252e2e7..886abdf7 100644 --- a/internal/controller/managedcluster_controller_test.go +++ b/internal/controller/managedcluster_controller_test.go @@ -182,10 +182,12 @@ var _ = Describe("ManagedCluster Controller", func() { Spec: hmc.ManagedClusterSpec{ Template: templateName, Credential: credentialName, - Services: []hmc.ServiceSpec{ - { - Template: svcTemplateName, - Name: "test-svc-name", + ServiceSpec: hmc.ServiceSpec{ + Services: []hmc.Service{ + { + Template: svcTemplateName, + Name: "test-svc-name", + }, }, }, }, diff --git a/internal/controller/multiclusterservice_controller.go b/internal/controller/multiclusterservice_controller.go index d2bd10fd..553dec13 100644 --- a/internal/controller/multiclusterservice_controller.go +++ b/internal/controller/multiclusterservice_controller.go @@ -115,7 +115,7 @@ func (r *MultiClusterServiceReconciler) reconcileUpdate(ctx context.Context, mcs // We are enforcing that MultiClusterService may only use // ServiceTemplates that are present in the system namespace. - opts, err := sveltos.GetHelmChartOpts(ctx, r.Client, r.SystemNamespace, mcs.Spec.Services) + opts, err := sveltos.GetHelmChartOpts(ctx, r.Client, r.SystemNamespace, mcs.Spec.ServiceSpec.Services) if err != nil { return ctrl.Result{}, err } @@ -128,10 +128,13 @@ func (r *MultiClusterServiceReconciler) reconcileUpdate(ctx context.Context, mcs Name: mcs.Name, UID: mcs.UID, }, - LabelSelector: mcs.Spec.ClusterSelector, - HelmChartOpts: opts, - Priority: mcs.Spec.ServicesPriority, - StopOnConflict: mcs.Spec.StopOnConflict, + LabelSelector: mcs.Spec.ClusterSelector, + HelmChartOpts: opts, + Priority: mcs.Spec.ServiceSpec.Priority, + StopOnConflict: mcs.Spec.ServiceSpec.StopOnConflict, + Reload: mcs.Spec.ServiceSpec.Reload, + TemplateResourceRefs: mcs.Spec.ServiceSpec.TemplateResourceRefs, + PolicyRefs: mcs.Spec.ServiceSpec.PolicyRefs, }); err != nil { return ctrl.Result{}, fmt.Errorf("failed to reconcile ClusterProfile: %w", err) } diff --git a/internal/controller/multiclusterservice_controller_test.go b/internal/controller/multiclusterservice_controller_test.go index f3f7f9b7..e5a3d376 100644 --- a/internal/controller/multiclusterservice_controller_test.go +++ b/internal/controller/multiclusterservice_controller_test.go @@ -215,14 +215,16 @@ var _ = Describe("MultiClusterService Controller", func() { }, }, Spec: hmc.MultiClusterServiceSpec{ - Services: []hmc.ServiceSpec{ - { - Template: serviceTemplate1Name, - Name: helmChartReleaseName, - }, - { - Template: serviceTemplate2Name, - Name: helmChartReleaseName, + ServiceSpec: hmc.ServiceSpec{ + Services: []hmc.Service{ + { + Template: serviceTemplate1Name, + Name: helmChartReleaseName, + }, + { + Template: serviceTemplate2Name, + Name: helmChartReleaseName, + }, }, }, }, diff --git a/internal/sveltos/profile.go b/internal/sveltos/profile.go index 72e781e0..790eac4e 100644 --- a/internal/sveltos/profile.go +++ b/internal/sveltos/profile.go @@ -33,11 +33,14 @@ import ( ) type ReconcileProfileOpts struct { - OwnerReference *metav1.OwnerReference - LabelSelector metav1.LabelSelector - HelmChartOpts []HelmChartOpts - Priority int32 - StopOnConflict bool + OwnerReference *metav1.OwnerReference + LabelSelector metav1.LabelSelector + HelmChartOpts []HelmChartOpts + TemplateResourceRefs []sveltosv1beta1.TemplateResourceRef + PolicyRefs []sveltosv1beta1.PolicyRef + Priority int32 + StopOnConflict bool + Reload bool } type HelmChartOpts struct { @@ -49,6 +52,7 @@ type HelmChartOpts struct { ChartVersion string ReleaseName string ReleaseNamespace string + ValuesFrom []sveltosv1beta1.ValueFrom PlainHTTP bool InsecureSkipTLSVerify bool } @@ -127,7 +131,7 @@ func ReconcileProfile( // GetHelmChartOpts returns slice of helm chart options to use with Sveltos. // Namespace is the namespace of the referred templates in services slice. -func GetHelmChartOpts(ctx context.Context, c client.Client, namespace string, services []hmc.ServiceSpec) ([]HelmChartOpts, error) { +func GetHelmChartOpts(ctx context.Context, c client.Client, namespace string, services []hmc.Service) ([]HelmChartOpts, error) { l := ctrl.LoggerFrom(ctx) opts := []HelmChartOpts{} @@ -178,6 +182,7 @@ func GetHelmChartOpts(ctx context.Context, c client.Client, namespace string, se chartName := chart.Spec.Chart opt := HelmChartOpts{ Values: svc.Values, + ValuesFrom: svc.ValuesFrom, RepositoryURL: repo.Spec.URL, // We don't have repository name so chart name becomes repository name. RepositoryName: chartName, @@ -230,9 +235,12 @@ func GetSpec(opts *ReconcileProfileOpts) (*sveltosv1beta1.Spec, error) { ClusterSelector: libsveltosv1beta1.Selector{ LabelSelector: opts.LabelSelector, }, - Tier: tier, - ContinueOnConflict: !opts.StopOnConflict, - HelmCharts: make([]sveltosv1beta1.HelmChart, 0, len(opts.HelmChartOpts)), + Tier: tier, + ContinueOnConflict: !opts.StopOnConflict, + HelmCharts: make([]sveltosv1beta1.HelmChart, 0, len(opts.HelmChartOpts)), + Reloader: opts.Reload, + TemplateResourceRefs: opts.TemplateResourceRefs, + PolicyRefs: opts.PolicyRefs, } for _, hc := range opts.HelmChartOpts { @@ -259,6 +267,8 @@ func GetSpec(opts *ReconcileProfileOpts) (*sveltosv1beta1.Spec, error) { } helmChart.Values = hc.Values + helmChart.ValuesFrom = hc.ValuesFrom + spec.HelmCharts = append(spec.HelmCharts, helmChart) } diff --git a/internal/webhook/managedcluster_webhook.go b/internal/webhook/managedcluster_webhook.go index 71603351..33fe1f25 100644 --- a/internal/webhook/managedcluster_webhook.go +++ b/internal/webhook/managedcluster_webhook.go @@ -79,7 +79,7 @@ func (v *ManagedClusterValidator) ValidateCreate(ctx context.Context, obj runtim return nil, fmt.Errorf("%s: %w", invalidManagedClusterMsg, err) } - if err := validateServices(ctx, v.Client, managedCluster.Namespace, managedCluster.Spec.Services); err != nil { + if err := validateServices(ctx, v.Client, managedCluster.Namespace, managedCluster.Spec.ServiceSpec.Services); err != nil { return nil, fmt.Errorf("%s: %w", invalidManagedClusterMsg, err) } @@ -123,7 +123,7 @@ func (v *ManagedClusterValidator) ValidateUpdate(ctx context.Context, oldObj, ne return nil, fmt.Errorf("%s: %w", invalidManagedClusterMsg, err) } - if err := validateServices(ctx, v.Client, newManagedCluster.Namespace, newManagedCluster.Spec.Services); err != nil { + if err := validateServices(ctx, v.Client, newManagedCluster.Namespace, newManagedCluster.Spec.ServiceSpec.Services); err != nil { return nil, fmt.Errorf("%s: %w", invalidManagedClusterMsg, err) } @@ -131,7 +131,7 @@ func (v *ManagedClusterValidator) ValidateUpdate(ctx context.Context, oldObj, ne } func validateK8sCompatibility(ctx context.Context, cl client.Client, template *hmcv1alpha1.ClusterTemplate, mc *hmcv1alpha1.ManagedCluster) error { - if len(mc.Spec.Services) == 0 || template.Status.KubernetesVersion == "" { + if len(mc.Spec.ServiceSpec.Services) == 0 || template.Status.KubernetesVersion == "" { return nil // nothing to do } @@ -140,7 +140,7 @@ func validateK8sCompatibility(ctx context.Context, cl client.Client, template *h return fmt.Errorf("failed to parse k8s version %s of the ManagedCluster %s/%s: %w", template.Status.KubernetesVersion, mc.Namespace, mc.Name, err) } - for _, v := range mc.Spec.Services { + for _, v := range mc.Spec.ServiceSpec.Services { if v.Disable { continue } diff --git a/internal/webhook/multiclusterservice_webhook.go b/internal/webhook/multiclusterservice_webhook.go index fd84a77b..ca9a54b5 100644 --- a/internal/webhook/multiclusterservice_webhook.go +++ b/internal/webhook/multiclusterservice_webhook.go @@ -63,7 +63,7 @@ func (v *MultiClusterServiceValidator) ValidateCreate(ctx context.Context, obj r return nil, apierrors.NewBadRequest(fmt.Sprintf("expected MultiClusterService but got a %T", obj)) } - if err := validateServices(ctx, v.Client, v.SystemNamespace, mcs.Spec.Services); err != nil { + if err := validateServices(ctx, v.Client, v.SystemNamespace, mcs.Spec.ServiceSpec.Services); err != nil { return nil, fmt.Errorf("%s: %w", invalidMultiClusterServiceMsg, err) } @@ -77,7 +77,7 @@ func (v *MultiClusterServiceValidator) ValidateUpdate(ctx context.Context, _, ne return nil, apierrors.NewBadRequest(fmt.Sprintf("expected MultiClusterService but got a %T", newObj)) } - if err := validateServices(ctx, v.Client, v.SystemNamespace, mcs.Spec.Services); err != nil { + if err := validateServices(ctx, v.Client, v.SystemNamespace, mcs.Spec.ServiceSpec.Services); err != nil { return nil, fmt.Errorf("%s: %w", invalidMultiClusterServiceMsg, err) } @@ -94,7 +94,7 @@ func getServiceTemplate(ctx context.Context, c client.Client, templateNamespace, return tpl, c.Get(ctx, client.ObjectKey{Namespace: templateNamespace, Name: templateName}, tpl) } -func validateServices(ctx context.Context, c client.Client, namespace string, services []v1alpha1.ServiceSpec) (errs error) { +func validateServices(ctx context.Context, c client.Client, namespace string, services []v1alpha1.Service) (errs error) { for _, svc := range services { tpl, err := getServiceTemplate(ctx, c, namespace, svc.Template) if err != nil { diff --git a/templates/provider/hmc/templates/crds/hmc.mirantis.com_managedclusters.yaml b/templates/provider/hmc/templates/crds/hmc.mirantis.com_managedclusters.yaml index 8e0ec18a..1038eb23 100644 --- a/templates/provider/hmc/templates/crds/hmc.mirantis.com_managedclusters.yaml +++ b/templates/provider/hmc/templates/crds/hmc.mirantis.com_managedclusters.yaml @@ -69,62 +69,227 @@ spec: description: DryRun specifies whether the template should be applied after validation or only validated. type: boolean - services: - description: |- - Services is a list of services created via ServiceTemplates - that could be installed on the target cluster. - items: - description: ServiceSpec represents a Service to be managed - properties: - disable: - description: Disable can be set to disable handling of this - service. - type: boolean - name: - description: Name is the chart release. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace is the namespace the release will be installed in. - It will default to Name if not provided. - type: string - template: - description: Template is a reference to a Template object located - in the same namespace. - maxLength: 253 - minLength: 1 - type: string - values: - description: |- - Values is the helm values to be passed to the chart used by the template. - The string type is used in order to allow for templating. - type: string - required: - - name - - template - type: object - type: array - servicesPriority: - default: 100 - description: |- - ServicesPriority sets the priority for the services defined in this spec. - Higher value means higher priority and lower means lower. - In case of conflict with another object managing the service, - the one with higher priority will get to deploy its services. - format: int32 - maximum: 2147483646 - minimum: 1 - type: integer - stopOnConflict: - default: false - description: |- - StopOnConflict specifies what to do in case of a conflict. - E.g. If another object is already managing a service. - By default the remaining services will be deployed even if conflict is detected. - If set to true, the deployment will stop after encountering the first conflict. - type: boolean + serviceSpec: + description: ServiceSpec is spec related to deployment of services. + properties: + policyRefs: + description: |- + PolicyRefs is a list of kubernetes resources that need to be deployed in matching clusters. + These resources may contain static values or leverage Go templates for dynamic customization. + When expressed as templates, the values are rendered using data from resources + (like Cluster & TemplateResourceRefs) within the management cluster before deployement. + items: + properties: + deploymentType: + default: Remote + description: |- + DeploymentType indicates whether resources need to be deployed + into the management cluster (local) or the managed cluster (remote) + enum: + - Local + - Remote + type: string + kind: + description: |- + Kind of the resource. Supported kinds are: + - ConfigMap/Secret + - flux GitRepository;OCIRepository;Bucket + enum: + - GitRepository + - OCIRepository + - Bucket + - ConfigMap + - Secret + type: string + name: + description: |- + Name of the referenced resource. + Name can be expressed as a template and instantiate using + - cluster namespace: .Cluster.metadata.namespace + - cluster name: .Cluster.metadata.name + - cluster type: .Cluster.kind + minLength: 1 + type: string + namespace: + description: |- + Namespace of the referenced resource. + For ClusterProfile namespace can be left empty. In such a case, namespace will + be implicit set to cluster's namespace. + For Profile namespace must be left empty. Profile namespace will be used. + type: string + path: + description: |- + Path to the directory containing the YAML files. + Defaults to 'None', which translates to the root path of the SourceRef. + Used only for GitRepository;OCIRepository;Bucket + type: string + required: + - kind + - name + type: object + type: array + priority: + default: 100 + description: |- + Priority sets the priority for the services defined in this spec. + Higher value means higher priority and lower means lower. + In case of conflict with another object managing the service, + the one with higher priority will get to deploy its services. + format: int32 + maximum: 2147483646 + minimum: 1 + type: integer + reload: + description: Reload instances via rolling upgrade when a ConfigMap/Secret + mounted as volume is modified. + type: boolean + services: + description: |- + Services is a list of services created via ServiceTemplates + that could be installed on the target cluster. + items: + description: Service represents a Service to be deployed. + properties: + disable: + description: Disable can be set to disable handling of this + service. + type: boolean + name: + description: Name is the chart release. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace the release will be installed in. + It will default to Name if not provided. + type: string + template: + description: Template is a reference to a Template object + located in the same namespace. + maxLength: 253 + minLength: 1 + type: string + values: + description: |- + Values is the helm values to be passed to the chart used by the template. + The string type is used in order to allow for templating. + type: string + valuesFrom: + description: ValuesFrom can reference a ConfigMap or Secret + containing helm values. + items: + properties: + kind: + description: |- + Kind of the resource. Supported kinds are: + - ConfigMap/Secret + enum: + - ConfigMap + - Secret + type: string + name: + description: |- + Name of the referenced resource. + Name can be expressed as a template and instantiate using + - cluster namespace: .Cluster.metadata.namespace + - cluster name: .Cluster.metadata.name + - cluster type: .Cluster.kind + minLength: 1 + type: string + namespace: + description: |- + Namespace of the referenced resource. + For ClusterProfile namespace can be left empty. In such a case, namespace will + be implicit set to cluster's namespace. + For Profile namespace must be left empty. The Profile namespace will be used. + type: string + required: + - kind + - name + type: object + type: array + required: + - name + - template + type: object + type: array + stopOnConflict: + default: false + description: |- + StopOnConflict specifies what to do in case of a conflict. + E.g. If another object is already managing a service. + By default the remaining services will be deployed even if conflict is detected. + If set to true, the deployment will stop after encountering the first conflict. + type: boolean + templateResourceRefs: + description: |- + TemplateResourceRefs is a list of resources to collect from the management cluster, + the values from which can be used in templates. + items: + properties: + identifier: + description: |- + Identifier is how the resource will be referred to in the + template + type: string + resource: + description: |- + Resource references a Kubernetes instance in the management + cluster to fetch and use during template instantiation. + For ClusterProfile namespace can be left empty. In such a case, namespace will + be implicit set to cluster's namespace. + Name and namespace can be expressed as a template and instantiate using + - cluster namespace: .Cluster.metadata.namespace + - cluster name: .Cluster.metadata.name + - cluster type: .Cluster.kind + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + required: + - identifier + - resource + type: object + type: array + type: object template: description: Template is a reference to a Template object located in the same namespace. diff --git a/templates/provider/hmc/templates/crds/hmc.mirantis.com_multiclusterservices.yaml b/templates/provider/hmc/templates/crds/hmc.mirantis.com_multiclusterservices.yaml index d4f05b68..7767cdec 100644 --- a/templates/provider/hmc/templates/crds/hmc.mirantis.com_multiclusterservices.yaml +++ b/templates/provider/hmc/templates/crds/hmc.mirantis.com_multiclusterservices.yaml @@ -87,62 +87,228 @@ spec: type: object type: object x-kubernetes-map-type: atomic - services: - description: |- - Services is a list of services created via ServiceTemplates - that could be installed on the target cluster. - items: - description: ServiceSpec represents a Service to be managed - properties: - disable: - description: Disable can be set to disable handling of this - service. - type: boolean - name: - description: Name is the chart release. - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace is the namespace the release will be installed in. - It will default to Name if not provided. - type: string - template: - description: Template is a reference to a Template object located - in the same namespace. - maxLength: 253 - minLength: 1 - type: string - values: - description: |- - Values is the helm values to be passed to the chart used by the template. - The string type is used in order to allow for templating. - type: string - required: - - name - - template - type: object - type: array - servicesPriority: - default: 100 - description: |- - ServicesPriority sets the priority for the services defined in this spec. - Higher value means higher priority and lower means lower. - In case of conflict with another object managing the service, - the one with higher priority will get to deploy its services. - format: int32 - maximum: 2147483646 - minimum: 1 - type: integer - stopOnConflict: - default: false - description: |- - StopOnConflict specifies what to do in case of a conflict. - E.g. If another object is already managing a service. - By default the remaining services will be deployed even if conflict is detected. - If set to true, the deployment will stop after encountering the first conflict. - type: boolean + serviceSpec: + description: ServiceSpec contains all the spec related to deployment + of services. + properties: + policyRefs: + description: |- + PolicyRefs is a list of kubernetes resources that need to be deployed in matching clusters. + These resources may contain static values or leverage Go templates for dynamic customization. + When expressed as templates, the values are rendered using data from resources + (like Cluster & TemplateResourceRefs) within the management cluster before deployement. + items: + properties: + deploymentType: + default: Remote + description: |- + DeploymentType indicates whether resources need to be deployed + into the management cluster (local) or the managed cluster (remote) + enum: + - Local + - Remote + type: string + kind: + description: |- + Kind of the resource. Supported kinds are: + - ConfigMap/Secret + - flux GitRepository;OCIRepository;Bucket + enum: + - GitRepository + - OCIRepository + - Bucket + - ConfigMap + - Secret + type: string + name: + description: |- + Name of the referenced resource. + Name can be expressed as a template and instantiate using + - cluster namespace: .Cluster.metadata.namespace + - cluster name: .Cluster.metadata.name + - cluster type: .Cluster.kind + minLength: 1 + type: string + namespace: + description: |- + Namespace of the referenced resource. + For ClusterProfile namespace can be left empty. In such a case, namespace will + be implicit set to cluster's namespace. + For Profile namespace must be left empty. Profile namespace will be used. + type: string + path: + description: |- + Path to the directory containing the YAML files. + Defaults to 'None', which translates to the root path of the SourceRef. + Used only for GitRepository;OCIRepository;Bucket + type: string + required: + - kind + - name + type: object + type: array + priority: + default: 100 + description: |- + Priority sets the priority for the services defined in this spec. + Higher value means higher priority and lower means lower. + In case of conflict with another object managing the service, + the one with higher priority will get to deploy its services. + format: int32 + maximum: 2147483646 + minimum: 1 + type: integer + reload: + description: Reload instances via rolling upgrade when a ConfigMap/Secret + mounted as volume is modified. + type: boolean + services: + description: |- + Services is a list of services created via ServiceTemplates + that could be installed on the target cluster. + items: + description: Service represents a Service to be deployed. + properties: + disable: + description: Disable can be set to disable handling of this + service. + type: boolean + name: + description: Name is the chart release. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace the release will be installed in. + It will default to Name if not provided. + type: string + template: + description: Template is a reference to a Template object + located in the same namespace. + maxLength: 253 + minLength: 1 + type: string + values: + description: |- + Values is the helm values to be passed to the chart used by the template. + The string type is used in order to allow for templating. + type: string + valuesFrom: + description: ValuesFrom can reference a ConfigMap or Secret + containing helm values. + items: + properties: + kind: + description: |- + Kind of the resource. Supported kinds are: + - ConfigMap/Secret + enum: + - ConfigMap + - Secret + type: string + name: + description: |- + Name of the referenced resource. + Name can be expressed as a template and instantiate using + - cluster namespace: .Cluster.metadata.namespace + - cluster name: .Cluster.metadata.name + - cluster type: .Cluster.kind + minLength: 1 + type: string + namespace: + description: |- + Namespace of the referenced resource. + For ClusterProfile namespace can be left empty. In such a case, namespace will + be implicit set to cluster's namespace. + For Profile namespace must be left empty. The Profile namespace will be used. + type: string + required: + - kind + - name + type: object + type: array + required: + - name + - template + type: object + type: array + stopOnConflict: + default: false + description: |- + StopOnConflict specifies what to do in case of a conflict. + E.g. If another object is already managing a service. + By default the remaining services will be deployed even if conflict is detected. + If set to true, the deployment will stop after encountering the first conflict. + type: boolean + templateResourceRefs: + description: |- + TemplateResourceRefs is a list of resources to collect from the management cluster, + the values from which can be used in templates. + items: + properties: + identifier: + description: |- + Identifier is how the resource will be referred to in the + template + type: string + resource: + description: |- + Resource references a Kubernetes instance in the management + cluster to fetch and use during template instantiation. + For ClusterProfile namespace can be left empty. In such a case, namespace will + be implicit set to cluster's namespace. + Name and namespace can be expressed as a template and instantiate using + - cluster namespace: .Cluster.metadata.namespace + - cluster name: .Cluster.metadata.name + - cluster type: .Cluster.kind + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + required: + - identifier + - resource + type: object + type: array + type: object type: object status: description: MultiClusterServiceStatus defines the observed state of MultiClusterService. diff --git a/test/objects/managedcluster/managedcluster.go b/test/objects/managedcluster/managedcluster.go index 7310cb49..a140493d 100644 --- a/test/objects/managedcluster/managedcluster.go +++ b/test/objects/managedcluster/managedcluster.go @@ -76,7 +76,7 @@ func WithConfig(config string) Opt { func WithServiceTemplate(templateName string) Opt { return func(p *v1alpha1.ManagedCluster) { - p.Spec.Services = append(p.Spec.Services, v1alpha1.ServiceSpec{ + p.Spec.ServiceSpec.Services = append(p.Spec.ServiceSpec.Services, v1alpha1.Service{ Template: templateName, }) } diff --git a/test/objects/multiclusterservice/multiclusterservice.go b/test/objects/multiclusterservice/multiclusterservice.go index cc6aec97..70d2e90f 100644 --- a/test/objects/multiclusterservice/multiclusterservice.go +++ b/test/objects/multiclusterservice/multiclusterservice.go @@ -47,7 +47,7 @@ func WithName(name string) Opt { func WithServiceTemplate(templateName string) Opt { return func(p *v1alpha1.MultiClusterService) { - p.Spec.Services = append(p.Spec.Services, v1alpha1.ServiceSpec{ + p.Spec.ServiceSpec.Services = append(p.Spec.ServiceSpec.Services, v1alpha1.Service{ Template: templateName, }) }