From ab3cf7d682c1026eb728a1e40d23e74ff472f60e Mon Sep 17 00:00:00 2001 From: Andrei Pavlov Date: Thu, 26 Sep 2024 20:47:02 +0700 Subject: [PATCH 1/5] Switch from poller to reconciler for Release controller Signed-off-by: Andrei Pavlov --- cmd/main.go | 9 +- internal/controller/release_controller.go | 118 +++++++++++----------- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 3fb7bd837..6fcdaae02 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -35,12 +35,13 @@ import ( metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" "sigs.k8s.io/controller-runtime/pkg/webhook" + sveltosv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" + hmcmirantiscomv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" "github.com/Mirantis/hmc/internal/controller" "github.com/Mirantis/hmc/internal/telemetry" "github.com/Mirantis/hmc/internal/utils" hmcwebhook "github.com/Mirantis/hmc/internal/webhook" - sveltosv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" // +kubebuilder:scaffold:imports ) @@ -252,7 +253,7 @@ func main() { os.Exit(1) } - if err = mgr.Add(&controller.Poller{ + if err = (&controller.ReleaseReconciler{ Client: mgr.GetClient(), Config: mgr.GetConfig(), CreateManagement: createManagement, @@ -260,8 +261,8 @@ func main() { CreateTemplates: createTemplates, HMCTemplatesChartName: hmcTemplatesChartName, SystemNamespace: currentNamespace, - }); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "ReleaseController") + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Release") os.Exit(1) } diff --git a/internal/controller/release_controller.go b/internal/controller/release_controller.go index 3b1149a51..f7b98d113 100644 --- a/internal/controller/release_controller.go +++ b/internal/controller/release_controller.go @@ -19,7 +19,6 @@ import ( "encoding/json" "errors" "fmt" - "time" hcv2 "github.com/fluxcd/helm-controller/api/v2" sourcev1 "github.com/fluxcd/source-controller/api/v1" @@ -32,8 +31,13 @@ import ( "k8s.io/apimachinery/pkg/fields" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/source" hmc "github.com/Mirantis/hmc/api/v1alpha1" "github.com/Mirantis/hmc/internal/build" @@ -41,14 +45,11 @@ import ( ) const ( - pollPeriod = 10 * time.Minute - errPollPeriod = 10 * time.Second - hmcTemplatesReleaseName = "hmc-templates" ) -// Poller reconciles a Template object -type Poller struct { +// ReleaseReconciler reconciles a Template object +type ReleaseReconciler struct { client.Client Config *rest.Config @@ -61,48 +62,30 @@ type Poller struct { SystemNamespace string } -func (p *Poller) Start(ctx context.Context) error { - timer := time.NewTimer(0) - for { - select { - case <-timer.C: - err := p.Tick(ctx) - if err != nil { - timer.Reset(errPollPeriod) - } else { - timer.Reset(pollPeriod) - } - case <-ctx.Done(): - return nil - } - } -} - -func (p *Poller) Tick(ctx context.Context) error { +func (r *ReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { l := ctrl.LoggerFrom(ctx).WithValues("controller", "ReleaseController") + l.Info("Reconciling Release") + defer l.Info("Release reconcile is finished") - l.Info("Poll is run") - defer l.Info("Poll is finished") - - err := p.reconcileHMCTemplates(ctx) + err := r.reconcileHMCTemplates(ctx) if err != nil { l.Error(err, "failed to reconcile HMC Templates") - return err + return ctrl.Result{}, err } - mgmt, err := p.getOrCreateManagement(ctx) + mgmt, err := r.getOrCreateManagement(ctx) if err != nil { l.Error(err, "failed to get or create Management object") - return err + return ctrl.Result{}, err } - err = p.ensureTemplateManagement(ctx, mgmt) + err = r.ensureTemplateManagement(ctx, mgmt) if err != nil { l.Error(err, "failed to ensure default TemplateManagement object") - return err + return ctrl.Result{}, err } - return nil + return ctrl.Result{}, nil } -func (p *Poller) getOrCreateManagement(ctx context.Context) (*hmc.Management, error) { +func (r *ReleaseReconciler) getOrCreateManagement(ctx context.Context) (*hmc.Management, error) { l := ctrl.LoggerFrom(ctx) mgmtObj := &hmc.Management{ ObjectMeta: metav1.ObjectMeta{ @@ -110,17 +93,17 @@ func (p *Poller) getOrCreateManagement(ctx context.Context) (*hmc.Management, er Finalizers: []string{hmc.ManagementFinalizer}, }, } - err := p.Get(ctx, client.ObjectKey{ + err := r.Get(ctx, client.ObjectKey{ Name: hmc.ManagementName, }, mgmtObj) if err != nil { if !apierrors.IsNotFound(err) { return nil, fmt.Errorf("failed to get %s Management object: %w", hmc.ManagementName, err) } - if !p.CreateManagement { + if !r.CreateManagement { return nil, nil } - mgmtObj.Spec.Release, err = p.getCurrentReleaseName(ctx) + mgmtObj.Spec.Release, err = r.getCurrentReleaseName(ctx) if err != nil { return nil, err } @@ -129,9 +112,9 @@ func (p *Poller) getOrCreateManagement(ctx context.Context) (*hmc.Management, er return nil, err } - getter := helm.NewMemoryRESTClientGetter(p.Config, p.RESTMapper()) + getter := helm.NewMemoryRESTClientGetter(r.Config, r.RESTMapper()) actionConfig := new(action.Configuration) - err = actionConfig.Init(getter, p.SystemNamespace, "secret", l.Info) + err = actionConfig.Init(getter, r.SystemNamespace, "secret", l.Info) if err != nil { return nil, err } @@ -166,7 +149,7 @@ func (p *Poller) getOrCreateManagement(ctx context.Context) (*hmc.Management, er }, } - err = p.Create(ctx, mgmtObj) + err = r.Create(ctx, mgmtObj) if err != nil { return nil, fmt.Errorf("failed to create %s Management object: %s", hmc.ManagementName, err) } @@ -175,9 +158,9 @@ func (p *Poller) getOrCreateManagement(ctx context.Context) (*hmc.Management, er return mgmtObj, nil } -func (p *Poller) ensureTemplateManagement(ctx context.Context, mgmt *hmc.Management) error { +func (r *ReleaseReconciler) ensureTemplateManagement(ctx context.Context, mgmt *hmc.Management) error { l := ctrl.LoggerFrom(ctx) - if !p.CreateTemplateManagement { + if !r.CreateTemplateManagement { return nil } if mgmt == nil { @@ -196,14 +179,14 @@ func (p *Poller) ensureTemplateManagement(ctx context.Context, mgmt *hmc.Managem }, }, } - err := p.Get(ctx, client.ObjectKey{ + err := r.Get(ctx, client.ObjectKey{ Name: hmc.TemplateManagementName, }, tmObj) if err != nil { if !apierrors.IsNotFound(err) { return fmt.Errorf("failed to get %s TemplateManagement object: %w", hmc.TemplateManagementName, err) } - err = p.Create(ctx, tmObj) + err = r.Create(ctx, tmObj) if err != nil { return fmt.Errorf("failed to create %s TemplateManagement object: %w", hmc.TemplateManagementName, err) } @@ -212,26 +195,26 @@ func (p *Poller) ensureTemplateManagement(ctx context.Context, mgmt *hmc.Managem return nil } -func (p *Poller) reconcileHMCTemplates(ctx context.Context) error { +func (r *ReleaseReconciler) reconcileHMCTemplates(ctx context.Context) error { l := ctrl.LoggerFrom(ctx) - if !p.CreateTemplates { + if !r.CreateTemplates { l.Info("Reconciling HMC Templates is skipped") return nil } helmChart := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ - Name: p.HMCTemplatesChartName, - Namespace: p.SystemNamespace, + Name: r.HMCTemplatesChartName, + Namespace: r.SystemNamespace, }, } - operation, err := ctrl.CreateOrUpdate(ctx, p.Client, helmChart, func() error { + operation, err := ctrl.CreateOrUpdate(ctx, r.Client, helmChart, func() error { if helmChart.Labels == nil { helmChart.Labels = make(map[string]string) } helmChart.Labels[hmc.HMCManagedLabelKey] = hmc.HMCManagedLabelValue helmChart.Spec = sourcev1.HelmChartSpec{ - Chart: p.HMCTemplatesChartName, + Chart: r.HMCTemplatesChartName, Version: build.Version, SourceRef: sourcev1.LocalHelmChartSourceReference{ Kind: sourcev1.HelmRepositoryKind, @@ -245,14 +228,14 @@ func (p *Poller) reconcileHMCTemplates(ctx context.Context) error { return err } if operation == controllerutil.OperationResultCreated || operation == controllerutil.OperationResultUpdated { - l.Info(fmt.Sprintf("Successfully %s %s/%s HelmChart", operation, p.SystemNamespace, p.HMCTemplatesChartName)) + l.Info(fmt.Sprintf("Successfully %s %s/%s HelmChart", operation, r.SystemNamespace, r.HMCTemplatesChartName)) } if _, err := helm.ArtifactReady(helmChart); err != nil { - return fmt.Errorf("HelmChart %s/%s Artifact is not ready: %w", p.SystemNamespace, p.HMCTemplatesChartName, err) + return fmt.Errorf("HelmChart %s/%s Artifact is not ready: %w", r.SystemNamespace, r.HMCTemplatesChartName, err) } - _, operation, err = helm.ReconcileHelmRelease(ctx, p.Client, hmcTemplatesReleaseName, p.SystemNamespace, helm.ReconcileHelmReleaseOpts{ + _, operation, err = helm.ReconcileHelmRelease(ctx, r.Client, hmcTemplatesReleaseName, r.SystemNamespace, helm.ReconcileHelmReleaseOpts{ ChartRef: &hcv2.CrossNamespaceSourceReference{ Kind: helmChart.Kind, Name: helmChart.Name, @@ -263,17 +246,17 @@ func (p *Poller) reconcileHMCTemplates(ctx context.Context) error { return err } if operation == controllerutil.OperationResultCreated || operation == controllerutil.OperationResultUpdated { - l.Info(fmt.Sprintf("Successfully %s %s/%s HelmRelease", operation, p.SystemNamespace, hmcTemplatesReleaseName)) + l.Info(fmt.Sprintf("Successfully %s %s/%s HelmRelease", operation, r.SystemNamespace, hmcTemplatesReleaseName)) } return nil } -func (p *Poller) getCurrentReleaseName(ctx context.Context) (string, error) { +func (r *ReleaseReconciler) getCurrentReleaseName(ctx context.Context) (string, error) { releases := &hmc.ReleaseList{} listOptions := client.ListOptions{ FieldSelector: fields.SelectorFromSet(fields.Set{hmc.VersionKey: build.Version}), } - if err := p.Client.List(ctx, releases, &listOptions); err != nil { + if err := r.Client.List(ctx, releases, &listOptions); err != nil { return "", err } if len(releases.Items) != 1 { @@ -281,3 +264,24 @@ func (p *Poller) getCurrentReleaseName(ctx context.Context) (string, error) { } return releases.Items[0].Name, nil } + +// SetupWithManager sets up the controller with the Manager. +func (r *ReleaseReconciler) SetupWithManager(mgr ctrl.Manager) error { + c, err := ctrl.NewControllerManagedBy(mgr). + For(&hmc.Release{}, builder.WithPredicates(predicate.Funcs{ + DeleteFunc: func(event.DeleteEvent) bool { return false }, + GenericFunc: func(event.GenericEvent) bool { return false }, + })). + Build(r) + if err != nil { + return err + } + // + if !r.CreateManagement { + return nil + } + // There's no Release objects created yet and we need a way to trigger reconcile + initChannel := make(chan event.GenericEvent, 1) + initChannel <- event.GenericEvent{Object: &hmc.Release{}} + return c.Watch(source.Channel(initChannel, &handler.EnqueueRequestForObject{})) +} From d57cb59d73af84e876ea20449a699ef7a17baf4e Mon Sep 17 00:00:00 2001 From: Andrei Pavlov Date: Mon, 30 Sep 2024 19:16:54 +0700 Subject: [PATCH 2/5] Create hmc templates based on Release objects Signed-off-by: Andrei Pavlov --- internal/controller/release_controller.go | 70 ++++++++++++++++------- internal/utils/release.go | 21 +++++++ internal/utils/release_test.go | 37 ++++++++++++ 3 files changed, 106 insertions(+), 22 deletions(-) create mode 100644 internal/utils/release.go create mode 100644 internal/utils/release_test.go diff --git a/internal/controller/release_controller.go b/internal/controller/release_controller.go index f7b98d113..66f1362f3 100644 --- a/internal/controller/release_controller.go +++ b/internal/controller/release_controller.go @@ -42,10 +42,7 @@ import ( hmc "github.com/Mirantis/hmc/api/v1alpha1" "github.com/Mirantis/hmc/internal/build" "github.com/Mirantis/hmc/internal/helm" -) - -const ( - hmcTemplatesReleaseName = "hmc-templates" + "github.com/Mirantis/hmc/internal/utils" ) // ReleaseReconciler reconciles a Template object @@ -67,24 +64,31 @@ func (r *ReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct l.Info("Reconciling Release") defer l.Info("Release reconcile is finished") - err := r.reconcileHMCTemplates(ctx) + err := r.reconcileHMCTemplates(ctx, req) if err != nil { l.Error(err, "failed to reconcile HMC Templates") return ctrl.Result{}, err } - mgmt, err := r.getOrCreateManagement(ctx) - if err != nil { - l.Error(err, "failed to get or create Management object") - return ctrl.Result{}, err - } - err = r.ensureTemplateManagement(ctx, mgmt) - if err != nil { - l.Error(err, "failed to ensure default TemplateManagement object") - return ctrl.Result{}, err + + if initialReconcile(req) { + mgmt, err := r.getOrCreateManagement(ctx) + if err != nil { + l.Error(err, "failed to get or create Management object") + return ctrl.Result{}, err + } + err = r.ensureTemplateManagement(ctx, mgmt) + if err != nil { + l.Error(err, "failed to ensure default TemplateManagement object") + return ctrl.Result{}, err + } } return ctrl.Result{}, nil } +func initialReconcile(req ctrl.Request) bool { + return req.Name == "" +} + func (r *ReleaseReconciler) getOrCreateManagement(ctx context.Context) (*hmc.Management, error) { l := ctrl.LoggerFrom(ctx) mgmtObj := &hmc.Management{ @@ -195,27 +199,49 @@ func (r *ReleaseReconciler) ensureTemplateManagement(ctx context.Context, mgmt * return nil } -func (r *ReleaseReconciler) reconcileHMCTemplates(ctx context.Context) error { +func (r *ReleaseReconciler) reconcileHMCTemplates(ctx context.Context, req ctrl.Request) error { l := ctrl.LoggerFrom(ctx) if !r.CreateTemplates { l.Info("Reconciling HMC Templates is skipped") return nil } + name := utils.ReleaseNameFromVersion(build.Version) + version := build.Version + var ownerRefs []metav1.OwnerReference + release := &hmc.Release{} + if !initialReconcile(req) { + if err := r.Client.Get(ctx, req.NamespacedName, release); err != nil { + l.Error(err, "failed to get Release") + return err + } + name = req.Name + version = release.Spec.Version + ownerRefs = []metav1.OwnerReference{ + { + APIVersion: hmc.GroupVersion.String(), + Kind: release.Kind, + Name: release.Name, + UID: release.UID, + }, + } + } + helmChart := &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ - Name: r.HMCTemplatesChartName, + Name: name, Namespace: r.SystemNamespace, }, } operation, err := ctrl.CreateOrUpdate(ctx, r.Client, helmChart, func() error { + helmChart.OwnerReferences = ownerRefs if helmChart.Labels == nil { helmChart.Labels = make(map[string]string) } helmChart.Labels[hmc.HMCManagedLabelKey] = hmc.HMCManagedLabelValue helmChart.Spec = sourcev1.HelmChartSpec{ Chart: r.HMCTemplatesChartName, - Version: build.Version, + Version: version, SourceRef: sourcev1.LocalHelmChartSourceReference{ Kind: sourcev1.HelmRepositoryKind, Name: defaultRepoName, @@ -228,14 +254,14 @@ func (r *ReleaseReconciler) reconcileHMCTemplates(ctx context.Context) error { return err } if operation == controllerutil.OperationResultCreated || operation == controllerutil.OperationResultUpdated { - l.Info(fmt.Sprintf("Successfully %s %s/%s HelmChart", operation, r.SystemNamespace, r.HMCTemplatesChartName)) + l.Info(fmt.Sprintf("Successfully %s %s/%s HelmChart", operation, r.SystemNamespace, name)) } if _, err := helm.ArtifactReady(helmChart); err != nil { - return fmt.Errorf("HelmChart %s/%s Artifact is not ready: %w", r.SystemNamespace, r.HMCTemplatesChartName, err) + return fmt.Errorf("HelmChart %s/%s Artifact is not ready: %w", r.SystemNamespace, name, err) } - _, operation, err = helm.ReconcileHelmRelease(ctx, r.Client, hmcTemplatesReleaseName, r.SystemNamespace, helm.ReconcileHelmReleaseOpts{ + _, operation, err = helm.ReconcileHelmRelease(ctx, r.Client, name, r.SystemNamespace, helm.ReconcileHelmReleaseOpts{ ChartRef: &hcv2.CrossNamespaceSourceReference{ Kind: helmChart.Kind, Name: helmChart.Name, @@ -246,7 +272,7 @@ func (r *ReleaseReconciler) reconcileHMCTemplates(ctx context.Context) error { return err } if operation == controllerutil.OperationResultCreated || operation == controllerutil.OperationResultUpdated { - l.Info(fmt.Sprintf("Successfully %s %s/%s HelmRelease", operation, r.SystemNamespace, hmcTemplatesReleaseName)) + l.Info(fmt.Sprintf("Successfully %s %s/%s HelmRelease", operation, r.SystemNamespace, name)) } return nil } @@ -280,7 +306,7 @@ func (r *ReleaseReconciler) SetupWithManager(mgr ctrl.Manager) error { if !r.CreateManagement { return nil } - // There's no Release objects created yet and we need a way to trigger reconcile + // There's no Release objects created yet and we need to trigger reconcile initChannel := make(chan event.GenericEvent, 1) initChannel <- event.GenericEvent{Object: &hmc.Release{}} return c.Watch(source.Channel(initChannel, &handler.EnqueueRequestForObject{})) diff --git a/internal/utils/release.go b/internal/utils/release.go new file mode 100644 index 000000000..814ed96de --- /dev/null +++ b/internal/utils/release.go @@ -0,0 +1,21 @@ +// 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 utils + +import "strings" + +func ReleaseNameFromVersion(version string) string { + return "hmc-" + strings.ReplaceAll(strings.TrimPrefix(version, "v"), ".", "-") +} diff --git a/internal/utils/release_test.go b/internal/utils/release_test.go new file mode 100644 index 000000000..f1771f799 --- /dev/null +++ b/internal/utils/release_test.go @@ -0,0 +1,37 @@ +// 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 utils + +import ( + "testing" +) + +func TestReleaseNameFromVersion(t *testing.T) { + for _, tc := range []struct { + version string + expectedName string + }{ + {version: "v0.0.1", expectedName: "hmc-0-0-1"}, + {version: "v0.0.1-rc", expectedName: "hmc-0-0-1-rc"}, + {version: "0.0.1", expectedName: "hmc-0-0-1"}, + } { + t.Run(tc.version, func(t *testing.T) { + actual := ReleaseNameFromVersion(tc.version) + if actual != tc.expectedName { + t.Errorf("expected name %s, got %s", tc.expectedName, actual) + } + }) + } +} From 05edc4656d5fc52a76304e673363766d48c84403 Mon Sep 17 00:00:00 2001 From: Andrei Pavlov Date: Mon, 30 Sep 2024 22:51:38 +0700 Subject: [PATCH 3/5] Set helm resource policy "keep" for templates Signed-off-by: Andrei Pavlov --- hack/templates.sh | 2 ++ .../provider/hmc-templates/files/templates/aws-hosted-cp.yaml | 2 ++ .../hmc-templates/files/templates/aws-standalone-cp.yaml | 2 ++ .../provider/hmc-templates/files/templates/azure-hosted-cp.yaml | 2 ++ .../hmc-templates/files/templates/azure-standalone-cp.yaml | 2 ++ .../hmc-templates/files/templates/cluster-api-provider-aws.yaml | 2 ++ .../files/templates/cluster-api-provider-azure.yaml | 2 ++ .../files/templates/cluster-api-provider-vsphere.yaml | 2 ++ .../provider/hmc-templates/files/templates/cluster-api.yaml | 2 ++ templates/provider/hmc-templates/files/templates/hmc.yaml | 2 ++ .../provider/hmc-templates/files/templates/ingress-nginx.yaml | 2 ++ templates/provider/hmc-templates/files/templates/k0smotron.yaml | 2 ++ templates/provider/hmc-templates/files/templates/kyverno.yaml | 2 ++ .../provider/hmc-templates/files/templates/projectsveltos.yaml | 2 ++ .../hmc-templates/files/templates/vsphere-hosted-cp.yaml | 2 ++ .../hmc-templates/files/templates/vsphere-standalone-cp.yaml | 2 ++ 16 files changed, 32 insertions(+) diff --git a/hack/templates.sh b/hack/templates.sh index c23f6da30..e53a8271e 100755 --- a/hack/templates.sh +++ b/hack/templates.sh @@ -41,6 +41,8 @@ EOF cat <> $TEMPLATES_OUTPUT_DIR/$name.yaml metadata: name: $name + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: $name diff --git a/templates/provider/hmc-templates/files/templates/aws-hosted-cp.yaml b/templates/provider/hmc-templates/files/templates/aws-hosted-cp.yaml index dc54e981f..d64f487a7 100644 --- a/templates/provider/hmc-templates/files/templates/aws-hosted-cp.yaml +++ b/templates/provider/hmc-templates/files/templates/aws-hosted-cp.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ClusterTemplate metadata: name: aws-hosted-cp + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: aws-hosted-cp diff --git a/templates/provider/hmc-templates/files/templates/aws-standalone-cp.yaml b/templates/provider/hmc-templates/files/templates/aws-standalone-cp.yaml index aa0ac7d1f..e837d868b 100644 --- a/templates/provider/hmc-templates/files/templates/aws-standalone-cp.yaml +++ b/templates/provider/hmc-templates/files/templates/aws-standalone-cp.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ClusterTemplate metadata: name: aws-standalone-cp + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: aws-standalone-cp diff --git a/templates/provider/hmc-templates/files/templates/azure-hosted-cp.yaml b/templates/provider/hmc-templates/files/templates/azure-hosted-cp.yaml index d8c45c35d..e21e40c30 100644 --- a/templates/provider/hmc-templates/files/templates/azure-hosted-cp.yaml +++ b/templates/provider/hmc-templates/files/templates/azure-hosted-cp.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ClusterTemplate metadata: name: azure-hosted-cp + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: azure-hosted-cp diff --git a/templates/provider/hmc-templates/files/templates/azure-standalone-cp.yaml b/templates/provider/hmc-templates/files/templates/azure-standalone-cp.yaml index bf5aece44..1e142abfe 100644 --- a/templates/provider/hmc-templates/files/templates/azure-standalone-cp.yaml +++ b/templates/provider/hmc-templates/files/templates/azure-standalone-cp.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ClusterTemplate metadata: name: azure-standalone-cp + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: azure-standalone-cp diff --git a/templates/provider/hmc-templates/files/templates/cluster-api-provider-aws.yaml b/templates/provider/hmc-templates/files/templates/cluster-api-provider-aws.yaml index df4de06c4..7866b1913 100644 --- a/templates/provider/hmc-templates/files/templates/cluster-api-provider-aws.yaml +++ b/templates/provider/hmc-templates/files/templates/cluster-api-provider-aws.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ProviderTemplate metadata: name: cluster-api-provider-aws + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: cluster-api-provider-aws diff --git a/templates/provider/hmc-templates/files/templates/cluster-api-provider-azure.yaml b/templates/provider/hmc-templates/files/templates/cluster-api-provider-azure.yaml index e3e31ccc7..ea65827ba 100644 --- a/templates/provider/hmc-templates/files/templates/cluster-api-provider-azure.yaml +++ b/templates/provider/hmc-templates/files/templates/cluster-api-provider-azure.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ProviderTemplate metadata: name: cluster-api-provider-azure + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: cluster-api-provider-azure diff --git a/templates/provider/hmc-templates/files/templates/cluster-api-provider-vsphere.yaml b/templates/provider/hmc-templates/files/templates/cluster-api-provider-vsphere.yaml index 33e777c09..c077148ac 100644 --- a/templates/provider/hmc-templates/files/templates/cluster-api-provider-vsphere.yaml +++ b/templates/provider/hmc-templates/files/templates/cluster-api-provider-vsphere.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ProviderTemplate metadata: name: cluster-api-provider-vsphere + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: cluster-api-provider-vsphere diff --git a/templates/provider/hmc-templates/files/templates/cluster-api.yaml b/templates/provider/hmc-templates/files/templates/cluster-api.yaml index 5b3bc3970..8477f705e 100644 --- a/templates/provider/hmc-templates/files/templates/cluster-api.yaml +++ b/templates/provider/hmc-templates/files/templates/cluster-api.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ProviderTemplate metadata: name: cluster-api + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: cluster-api diff --git a/templates/provider/hmc-templates/files/templates/hmc.yaml b/templates/provider/hmc-templates/files/templates/hmc.yaml index 4dd8f23f1..e002ad5a2 100644 --- a/templates/provider/hmc-templates/files/templates/hmc.yaml +++ b/templates/provider/hmc-templates/files/templates/hmc.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ProviderTemplate metadata: name: hmc + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: hmc diff --git a/templates/provider/hmc-templates/files/templates/ingress-nginx.yaml b/templates/provider/hmc-templates/files/templates/ingress-nginx.yaml index 46cf8dde6..b3cc87e73 100644 --- a/templates/provider/hmc-templates/files/templates/ingress-nginx.yaml +++ b/templates/provider/hmc-templates/files/templates/ingress-nginx.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ServiceTemplate metadata: name: ingress-nginx + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: ingress-nginx diff --git a/templates/provider/hmc-templates/files/templates/k0smotron.yaml b/templates/provider/hmc-templates/files/templates/k0smotron.yaml index 98e12fb44..04d2ccb29 100644 --- a/templates/provider/hmc-templates/files/templates/k0smotron.yaml +++ b/templates/provider/hmc-templates/files/templates/k0smotron.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ProviderTemplate metadata: name: k0smotron + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: k0smotron diff --git a/templates/provider/hmc-templates/files/templates/kyverno.yaml b/templates/provider/hmc-templates/files/templates/kyverno.yaml index 27300d442..5202b033b 100644 --- a/templates/provider/hmc-templates/files/templates/kyverno.yaml +++ b/templates/provider/hmc-templates/files/templates/kyverno.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ServiceTemplate metadata: name: kyverno + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: kyverno diff --git a/templates/provider/hmc-templates/files/templates/projectsveltos.yaml b/templates/provider/hmc-templates/files/templates/projectsveltos.yaml index 390e620f4..b05a1ac82 100644 --- a/templates/provider/hmc-templates/files/templates/projectsveltos.yaml +++ b/templates/provider/hmc-templates/files/templates/projectsveltos.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ProviderTemplate metadata: name: projectsveltos + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: projectsveltos diff --git a/templates/provider/hmc-templates/files/templates/vsphere-hosted-cp.yaml b/templates/provider/hmc-templates/files/templates/vsphere-hosted-cp.yaml index 7efcbd5b1..a8d4450fc 100644 --- a/templates/provider/hmc-templates/files/templates/vsphere-hosted-cp.yaml +++ b/templates/provider/hmc-templates/files/templates/vsphere-hosted-cp.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ClusterTemplate metadata: name: vsphere-hosted-cp + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: vsphere-hosted-cp diff --git a/templates/provider/hmc-templates/files/templates/vsphere-standalone-cp.yaml b/templates/provider/hmc-templates/files/templates/vsphere-standalone-cp.yaml index a43b3d783..93da4650c 100644 --- a/templates/provider/hmc-templates/files/templates/vsphere-standalone-cp.yaml +++ b/templates/provider/hmc-templates/files/templates/vsphere-standalone-cp.yaml @@ -2,6 +2,8 @@ apiVersion: hmc.mirantis.com/v1alpha1 kind: ClusterTemplate metadata: name: vsphere-standalone-cp + annotations: + helm.sh/resource-policy: keep spec: helm: chartName: vsphere-standalone-cp From 796e8ebe22677d9ef642d8d7e42087a631e8d4d8 Mon Sep 17 00:00:00 2001 From: Andrei Pavlov Date: Tue, 1 Oct 2024 14:30:15 +0700 Subject: [PATCH 4/5] Don't create template if it already exists Signed-off-by: Andrei Pavlov --- templates/provider/hmc-templates/templates/templates.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/templates/provider/hmc-templates/templates/templates.yaml b/templates/provider/hmc-templates/templates/templates.yaml index 84e11e1f7..3a1a3466a 100644 --- a/templates/provider/hmc-templates/templates/templates.yaml +++ b/templates/provider/hmc-templates/templates/templates.yaml @@ -1,4 +1,10 @@ {{ range $path, $_ := .Files.Glob "files/templates/*.yaml" }} +{{- $content := $.Files.Get $path | fromYaml }} +{{- $apiVersion := $content.apiVersion }} +{{- $kind := $content.kind }} +{{- $name := $content.metadata.name }} +{{- if not (lookup $apiVersion $kind $.Release.Namespace $name) }} {{ $.Files.Get $path }} --- -{{ end }} +{{- end }} +{{- end }} From 7694a19b81a4e65690b5aec780210119a5d75095 Mon Sep 17 00:00:00 2001 From: Andrei Pavlov Date: Wed, 2 Oct 2024 10:34:47 +0700 Subject: [PATCH 5/5] Move TemplateManagement reconcile to Management controller Signed-off-by: Andrei Pavlov --- cmd/main.go | 22 +-- internal/controller/management_controller.go | 52 ++++++- internal/controller/release_controller.go | 146 +++++++------------ 3 files changed, 110 insertions(+), 110 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 6fcdaae02..99f9b27f0 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -219,10 +219,11 @@ func main() { os.Exit(1) } if err = (&controller.ManagementReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Config: mgr.GetConfig(), - SystemNamespace: currentNamespace, + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Config: mgr.GetConfig(), + SystemNamespace: currentNamespace, + CreateTemplateManagement: createTemplateManagement, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Management") os.Exit(1) @@ -254,13 +255,12 @@ func main() { } if err = (&controller.ReleaseReconciler{ - Client: mgr.GetClient(), - Config: mgr.GetConfig(), - CreateManagement: createManagement, - CreateTemplateManagement: createTemplateManagement, - CreateTemplates: createTemplates, - HMCTemplatesChartName: hmcTemplatesChartName, - SystemNamespace: currentNamespace, + Client: mgr.GetClient(), + Config: mgr.GetConfig(), + CreateManagement: createManagement, + CreateTemplates: createTemplates, + HMCTemplatesChartName: hmcTemplatesChartName, + SystemNamespace: currentNamespace, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Release") os.Exit(1) diff --git a/internal/controller/management_controller.go b/internal/controller/management_controller.go index 3e6f5bffd..dc4eb81f7 100644 --- a/internal/controller/management_controller.go +++ b/internal/controller/management_controller.go @@ -25,6 +25,7 @@ import ( sourcev1 "github.com/fluxcd/source-controller/api/v1" 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/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" @@ -41,9 +42,10 @@ import ( // ManagementReconciler reconciles a Management object type ManagementReconciler struct { client.Client - Scheme *runtime.Scheme - Config *rest.Config - SystemNamespace string + Scheme *runtime.Scheme + Config *rest.Config + SystemNamespace string + CreateTemplateManagement bool } func (r *ManagementReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -75,11 +77,17 @@ func (r *ManagementReconciler) Update(ctx context.Context, management *hmc.Manag finalizersUpdated := controllerutil.AddFinalizer(management, hmc.ManagementFinalizer) if finalizersUpdated { if err := r.Client.Update(ctx, management); err != nil { - return ctrl.Result{}, fmt.Errorf("failed to update Management %s: %w", management.Name, err) + l.Error(err, "Failed to update Management finalizers") + return ctrl.Result{}, err } return ctrl.Result{}, nil } + if err := r.ensureTemplateManagement(ctx, management); err != nil { + l.Error(err, "Failed to ensure TemplateManagement is created") + return ctrl.Result{}, err + } + release := &hmc.Release{} if err := r.Client.Get(ctx, client.ObjectKey{Name: management.Spec.Release}, release); err != nil { l.Error(err, "failed to get Release object") @@ -145,6 +153,42 @@ func (r *ManagementReconciler) Update(ctx context.Context, management *hmc.Manag return ctrl.Result{}, nil } +func (r *ManagementReconciler) ensureTemplateManagement(ctx context.Context, mgmt *hmc.Management) error { + l := ctrl.LoggerFrom(ctx) + if !r.CreateTemplateManagement { + return nil + } + l.Info("Ensuring TemplateManagement is created") + tmObj := &hmc.TemplateManagement{ + ObjectMeta: metav1.ObjectMeta{ + Name: hmc.TemplateManagementName, + OwnerReferences: []metav1.OwnerReference{ + { + APIVersion: hmc.GroupVersion.String(), + Kind: mgmt.Kind, + Name: mgmt.Name, + UID: mgmt.UID, + }, + }, + }, + } + err := r.Get(ctx, client.ObjectKey{ + Name: hmc.TemplateManagementName, + }, tmObj) + if err == nil { + return nil + } + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get %s TemplateManagement object: %w", hmc.TemplateManagementName, err) + } + err = r.Create(ctx, tmObj) + if err != nil { + return fmt.Errorf("failed to create %s TemplateManagement object: %w", hmc.TemplateManagementName, err) + } + l.Info("Successfully created TemplateManagement object") + return nil +} + func (r *ManagementReconciler) Delete(ctx context.Context, management *hmc.Management) (ctrl.Result, error) { l := ctrl.LoggerFrom(ctx) listOpts := &client.ListOptions{ diff --git a/internal/controller/release_controller.go b/internal/controller/release_controller.go index 66f1362f3..61675b8fd 100644 --- a/internal/controller/release_controller.go +++ b/internal/controller/release_controller.go @@ -51,9 +51,8 @@ type ReleaseReconciler struct { Config *rest.Config - CreateManagement bool - CreateTemplateManagement bool - CreateTemplates bool + CreateManagement bool + CreateTemplates bool HMCTemplatesChartName string SystemNamespace string @@ -71,16 +70,10 @@ func (r *ReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } if initialReconcile(req) { - mgmt, err := r.getOrCreateManagement(ctx) - if err != nil { + if err := r.ensureManagement(ctx); err != nil { l.Error(err, "failed to get or create Management object") return ctrl.Result{}, err } - err = r.ensureTemplateManagement(ctx, mgmt) - if err != nil { - l.Error(err, "failed to ensure default TemplateManagement object") - return ctrl.Result{}, err - } } return ctrl.Result{}, nil } @@ -89,8 +82,12 @@ func initialReconcile(req ctrl.Request) bool { return req.Name == "" } -func (r *ReleaseReconciler) getOrCreateManagement(ctx context.Context) (*hmc.Management, error) { +func (r *ReleaseReconciler) ensureManagement(ctx context.Context) error { l := ctrl.LoggerFrom(ctx) + if !r.CreateManagement { + return nil + } + l.Info("Ensuring Management is created") mgmtObj := &hmc.Management{ ObjectMeta: metav1.ObjectMeta{ Name: hmc.ManagementName, @@ -100,102 +97,61 @@ func (r *ReleaseReconciler) getOrCreateManagement(ctx context.Context) (*hmc.Man err := r.Get(ctx, client.ObjectKey{ Name: hmc.ManagementName, }, mgmtObj) + if err == nil { + return nil + } + if !apierrors.IsNotFound(err) { + return fmt.Errorf("failed to get %s Management object: %w", hmc.TemplateManagementName, err) + } + mgmtObj.Spec.Release, err = r.getCurrentReleaseName(ctx) if err != nil { - if !apierrors.IsNotFound(err) { - return nil, fmt.Errorf("failed to get %s Management object: %w", hmc.ManagementName, err) - } - if !r.CreateManagement { - return nil, nil - } - mgmtObj.Spec.Release, err = r.getCurrentReleaseName(ctx) - if err != nil { - return nil, err - } - - if err := mgmtObj.Spec.SetProvidersDefaults(); err != nil { - return nil, err - } - - getter := helm.NewMemoryRESTClientGetter(r.Config, r.RESTMapper()) - actionConfig := new(action.Configuration) - err = actionConfig.Init(getter, r.SystemNamespace, "secret", l.Info) - if err != nil { - return nil, err - } - - hmcConfig := make(chartutil.Values) - release, err := actionConfig.Releases.Last("hmc") - if err != nil { - if !errors.Is(err, driver.ErrReleaseNotFound) { - return nil, err - } - } else { - if len(release.Config) > 0 { - chartutil.CoalesceTables(hmcConfig, release.Config) - } - } + return err + } + if err := mgmtObj.Spec.SetProvidersDefaults(); err != nil { + return err + } + getter := helm.NewMemoryRESTClientGetter(r.Config, r.RESTMapper()) + actionConfig := new(action.Configuration) + err = actionConfig.Init(getter, r.SystemNamespace, "secret", l.Info) + if err != nil { + return err + } - // Initially set createManagement:false to automatically create Management object only once - chartutil.CoalesceTables(hmcConfig, map[string]any{ - "controller": map[string]any{ - "createManagement": false, - }, - }) - rawConfig, err := json.Marshal(hmcConfig) - if err != nil { - return nil, err - } - mgmtObj.Spec.Core = &hmc.Core{ - HMC: hmc.Component{ - Config: &apiextensionsv1.JSON{ - Raw: rawConfig, - }, - }, + hmcConfig := make(chartutil.Values) + release, err := actionConfig.Releases.Last("hmc") + if err != nil { + if !errors.Is(err, driver.ErrReleaseNotFound) { + return err } - - err = r.Create(ctx, mgmtObj) - if err != nil { - return nil, fmt.Errorf("failed to create %s Management object: %s", hmc.ManagementName, err) + } else { + if len(release.Config) > 0 { + chartutil.CoalesceTables(hmcConfig, release.Config) } - l.Info("Successfully created Management object with default configuration") } - return mgmtObj, nil -} -func (r *ReleaseReconciler) ensureTemplateManagement(ctx context.Context, mgmt *hmc.Management) error { - l := ctrl.LoggerFrom(ctx) - if !r.CreateTemplateManagement { - return nil - } - if mgmt == nil { - return fmt.Errorf("management object is not found") + // Initially set createManagement:false to automatically create Management object only once + chartutil.CoalesceTables(hmcConfig, map[string]any{ + "controller": map[string]any{ + "createManagement": false, + }, + }) + rawConfig, err := json.Marshal(hmcConfig) + if err != nil { + return err } - tmObj := &hmc.TemplateManagement{ - ObjectMeta: metav1.ObjectMeta{ - Name: hmc.TemplateManagementName, - OwnerReferences: []metav1.OwnerReference{ - { - APIVersion: hmc.GroupVersion.String(), - Kind: mgmt.Kind, - Name: mgmt.Name, - UID: mgmt.UID, - }, + mgmtObj.Spec.Core = &hmc.Core{ + HMC: hmc.Component{ + Config: &apiextensionsv1.JSON{ + Raw: rawConfig, }, }, } - err := r.Get(ctx, client.ObjectKey{ - Name: hmc.TemplateManagementName, - }, tmObj) + err = r.Create(ctx, mgmtObj) if err != nil { - if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to get %s TemplateManagement object: %w", hmc.TemplateManagementName, err) - } - err = r.Create(ctx, tmObj) - if err != nil { - return fmt.Errorf("failed to create %s TemplateManagement object: %w", hmc.TemplateManagementName, err) - } - l.Info("Successfully created TemplateManagement object") + return fmt.Errorf("failed to create %s Management object: %w", hmc.TemplateManagementName, err) } + + l.Info("Successfully created Management object with default configuration") return nil }