Skip to content

Commit

Permalink
Merge pull request k0rdent#407 from eromanova/template-chain-controller
Browse files Browse the repository at this point in the history
Template chain controller
  • Loading branch information
Kshatrix authored Oct 1, 2024
2 parents 0e7867b + b3bb2ba commit d58a547
Show file tree
Hide file tree
Showing 17 changed files with 902 additions and 701 deletions.
2 changes: 2 additions & 0 deletions api/v1alpha1/clustertemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const ClusterTemplateKind = "ClusterTemplate"

// ClusterTemplateSpec defines the desired state of ClusterTemplate
type ClusterTemplateSpec struct {
TemplateSpecCommon `json:",inline"`
Expand Down
15 changes: 14 additions & 1 deletion api/v1alpha1/clustertemplatechain_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,21 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const ClusterTemplateChainKind = "ClusterTemplateChain"

func (*ClusterTemplateChain) Kind() string {
return ClusterTemplateChainKind
}

func (*ClusterTemplateChain) TemplateKind() string {
return ClusterTemplateKind
}

func (t *ClusterTemplateChain) GetSpec() *TemplateChainSpec {
return &t.Spec
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster

// ClusterTemplateChain is the Schema for the clustertemplatechains API
type ClusterTemplateChain struct {
Expand Down
2 changes: 2 additions & 0 deletions api/v1alpha1/servicetemplate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const ServiceTemplateKind = "ServiceTemplate"

// ServiceTemplateSpec defines the desired state of ServiceTemplate
type ServiceTemplateSpec struct {
TemplateSpecCommon `json:",inline"`
Expand Down
15 changes: 14 additions & 1 deletion api/v1alpha1/servicetemplatechain_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,21 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const ServiceTemplateChainKind = "ServiceTemplateChain"

func (*ServiceTemplateChain) Kind() string {
return ServiceTemplateChainKind
}

func (*ServiceTemplateChain) TemplateKind() string {
return ServiceTemplateKind
}

func (t *ServiceTemplateChain) GetSpec() *TemplateChainSpec {
return &t.Spec
}

// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster

// ServiceTemplateChain is the Schema for the servicetemplatechains API
type ServiceTemplateChain struct {
Expand Down
87 changes: 56 additions & 31 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,24 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "TemplateManagement")
os.Exit(1)
}

templateChainReconciler := controller.TemplateChainReconciler{
Client: mgr.GetClient(),
SystemNamespace: currentNamespace,
}
if err = (&controller.ClusterTemplateChainReconciler{
TemplateChainReconciler: templateChainReconciler,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ClusterTemplateChain")
os.Exit(1)
}
if err = (&controller.ServiceTemplateChainReconciler{
TemplateChainReconciler: templateChainReconciler,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ServiceTemplateChain")
os.Exit(1)
}

if err = mgr.Add(&controller.Poller{
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
Expand Down Expand Up @@ -276,37 +294,8 @@ func main() {
}

if enableWebhook {
if err := (&hmcwebhook.ManagedClusterValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ManagedCluster")
os.Exit(1)
}
if err := (&hmcwebhook.ManagementValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "Management")
os.Exit(1)
}
if err := (&hmcwebhook.TemplateManagementValidator{
SystemNamespace: currentNamespace,
}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "TemplateManagement")
}
if err := (&hmcwebhook.ClusterTemplateChainValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ClusterTemplateChain")
os.Exit(1)
}
if err := (&hmcwebhook.ServiceTemplateChainValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ServiceTemplateChain")
os.Exit(1)
}
if err := (&hmcwebhook.ClusterTemplateValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ClusterTemplate")
os.Exit(1)
}
if err := (&hmcwebhook.ServiceTemplateValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ServiceTemplate")
os.Exit(1)
}
if err := (&hmcwebhook.ProviderTemplateValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ProviderTemplate")
if err := setupWebhooks(mgr, currentNamespace); err != nil {
setupLog.Error(err, "failed to setup webhooks")
os.Exit(1)
}
}
Expand All @@ -317,3 +306,39 @@ func main() {
os.Exit(1)
}
}

func setupWebhooks(mgr ctrl.Manager, currentNamespace string) error {
if err := (&hmcwebhook.ManagedClusterValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ManagedCluster")
return err
}
if err := (&hmcwebhook.ManagementValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "Management")
return err
}
if err := (&hmcwebhook.TemplateManagementValidator{SystemNamespace: currentNamespace}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "TemplateManagement")
return err
}
if err := (&hmcwebhook.ClusterTemplateChainValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ClusterTemplateChain")
return err
}
if err := (&hmcwebhook.ServiceTemplateChainValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ServiceTemplateChain")
return err
}
if err := (&hmcwebhook.ClusterTemplateValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ClusterTemplate")
return err
}
if err := (&hmcwebhook.ServiceTemplateValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ServiceTemplate")
return err
}
if err := (&hmcwebhook.ProviderTemplateValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ProviderTemplate")
return err
}
return nil
}
222 changes: 222 additions & 0 deletions internal/controller/templatechain_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// 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 controller

import (
"context"
"errors"
"fmt"

helmcontrollerv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"

hmc "github.com/Mirantis/hmc/api/v1alpha1"
)

const HMCManagedByChainLabelKey = "hmc.mirantis.com/managed-by-chain"

// TemplateChainReconciler reconciles a TemplateChain object
type TemplateChainReconciler struct {
client.Client
SystemNamespace string
}

type ClusterTemplateChainReconciler struct {
TemplateChainReconciler
}

type ServiceTemplateChainReconciler struct {
TemplateChainReconciler
}

// TemplateChain is the interface defining a list of methods to interact with templatechains
type TemplateChain interface {
client.Object
Kind() string
TemplateKind() string
GetSpec() *hmc.TemplateChainSpec
}

func (r *ClusterTemplateChainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx).WithValues("ClusterTemplateChainController", req.NamespacedName)
l.Info("Reconciling ClusterTemplateChain")

clusterTemplateChain := &hmc.ClusterTemplateChain{}
err := r.Get(ctx, req.NamespacedName, clusterTemplateChain)
if err != nil {
if apierrors.IsNotFound(err) {
l.Info("ClusterTemplateChain not found, ignoring since object must be deleted")
return ctrl.Result{}, nil
}
l.Error(err, "Failed to get ClusterTemplateChain")
return ctrl.Result{}, err
}
return r.ReconcileTemplateChain(ctx, clusterTemplateChain)
}

func (r *ServiceTemplateChainReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
l := log.FromContext(ctx).WithValues("ServiceTemplateChainReconciler", req.NamespacedName)
l.Info("Reconciling ServiceTemplateChain")

serviceTemplateChain := &hmc.ServiceTemplateChain{}
err := r.Get(ctx, req.NamespacedName, serviceTemplateChain)
if err != nil {
if apierrors.IsNotFound(err) {
l.Info("ServiceTemplateChain not found, ignoring since object must be deleted")
return ctrl.Result{}, nil
}
l.Error(err, "Failed to get ServiceTemplateChain")
return ctrl.Result{}, err
}
return r.ReconcileTemplateChain(ctx, serviceTemplateChain)
}

func (r *TemplateChainReconciler) ReconcileTemplateChain(ctx context.Context, templateChain TemplateChain) (ctrl.Result, error) {
l := log.FromContext(ctx)

systemTemplates, managedTemplates, err := getCurrentTemplates(ctx, r.Client, templateChain.TemplateKind(), r.SystemNamespace, templateChain.GetNamespace(), templateChain.GetName())
if err != nil {
return ctrl.Result{}, fmt.Errorf("failed to get current templates: %v", err)
}

var errs error

keepTemplate := make(map[string]bool)
for _, supportedTemplate := range templateChain.GetSpec().SupportedTemplates {
meta := metav1.ObjectMeta{
Name: supportedTemplate.Name,
Namespace: templateChain.GetNamespace(),
Labels: map[string]string{
hmc.HMCManagedLabelKey: hmc.HMCManagedLabelValue,
HMCManagedByChainLabelKey: templateChain.GetName(),
},
}
keepTemplate[supportedTemplate.Name] = true

source, found := systemTemplates[supportedTemplate.Name]
if !found {
errs = errors.Join(errs, fmt.Errorf("source %s %s/%s is not found", templateChain.TemplateKind(), r.SystemNamespace, supportedTemplate.Name))
continue
}

templateSpec := hmc.TemplateSpecCommon{
Helm: hmc.HelmSpec{
ChartRef: &helmcontrollerv2.CrossNamespaceSourceReference{
Kind: sourcev1.HelmChartKind,
Name: source.GetSpec().Helm.ChartName,
Namespace: r.SystemNamespace,
},
},
}

var target client.Object
switch templateChain.Kind() {
case hmc.ClusterTemplateChainKind:
target = &hmc.ClusterTemplate{ObjectMeta: meta, Spec: hmc.ClusterTemplateSpec{
TemplateSpecCommon: templateSpec,
}}
case hmc.ServiceTemplateChainKind:
target = &hmc.ServiceTemplate{ObjectMeta: meta, Spec: hmc.ServiceTemplateSpec{
TemplateSpecCommon: templateSpec,
}}
default:
return ctrl.Result{}, fmt.Errorf("invalid TemplateChain kind. Supported kinds are %s and %s", hmc.ClusterTemplateChainKind, hmc.ServiceTemplateChainKind)
}
err := r.Create(ctx, target)
if err == nil {
l.Info(fmt.Sprintf("%s was successfully created", templateChain.TemplateKind()), "namespace", templateChain.GetNamespace(), "name", supportedTemplate)
continue
}
if !apierrors.IsAlreadyExists(err) {
errs = errors.Join(errs, err)
}
}
for _, template := range managedTemplates {
if !keepTemplate[template.GetName()] {
l.Info(fmt.Sprintf("Deleting %s", templateChain.TemplateKind()), "namespace", templateChain.GetNamespace(), "name", template.GetName())
err := r.Delete(ctx, template)
if err == nil {
l.Info(fmt.Sprintf("%s was deleted", templateChain.TemplateKind()), "namespace", templateChain.GetNamespace(), "name", template.GetName())
continue
}
if !apierrors.IsNotFound(err) {
errs = errors.Join(errs, err)
}
}
}
return ctrl.Result{}, nil
}

func getCurrentTemplates(ctx context.Context, cl client.Client, templateKind, systemNamespace, targetNamespace, templateChainName string) (map[string]Template, []Template, error) {
var templates []Template

switch templateKind {
case hmc.ClusterTemplateKind:
ctList := &hmc.ClusterTemplateList{}
err := cl.List(ctx, ctList)
if err != nil {
return nil, nil, err
}
for _, template := range ctList.Items {
templates = append(templates, &template)
}
case hmc.ServiceTemplateKind:
stList := &hmc.ServiceTemplateList{}
err := cl.List(ctx, stList)
if err != nil {
return nil, nil, err
}
for _, template := range stList.Items {
templates = append(templates, &template)
}
default:
return nil, nil, fmt.Errorf("invalid Template kind. Supported kinds are %s and %s", hmc.ClusterTemplateKind, hmc.ServiceTemplateKind)
}
systemTemplates := make(map[string]Template)
var managedTemplates []Template

for _, template := range templates {
if template.GetNamespace() == systemNamespace {
systemTemplates[template.GetName()] = template
continue
}
labels := template.GetLabels()
if template.GetNamespace() == targetNamespace &&
labels[hmc.HMCManagedLabelKey] == "true" &&
labels[HMCManagedByChainLabelKey] == templateChainName {
managedTemplates = append(managedTemplates, template)
}
}
return systemTemplates, managedTemplates, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *ClusterTemplateChainReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&hmc.ClusterTemplateChain{}).
Complete(r)
}

// SetupWithManager sets up the controller with the Manager.
func (r *ServiceTemplateChainReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&hmc.ServiceTemplateChain{}).
Complete(r)
}
Loading

0 comments on commit d58a547

Please sign in to comment.