Skip to content

Commit

Permalink
Merge pull request K0rdent#303 from eromanova/template-management
Browse files Browse the repository at this point in the history
ClusterTemplate and ServiceTemplate distribution system
  • Loading branch information
Kshatrix authored Sep 20, 2024
2 parents c757bb2 + 50139eb commit 287d319
Show file tree
Hide file tree
Showing 17 changed files with 1,214 additions and 128 deletions.
11 changes: 7 additions & 4 deletions api/v1alpha1/templatemanagement_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,20 @@ type AccessRule struct {
ServiceTemplateChains []string `json:"serviceTemplateChains,omitempty"`
}

// +kubebuilder:validation:XValidation:rule="((has(self.stringSelector) ? 1 : 0) + (has(self.selector) ? 1 : 0) + (has(self.list) ? 1 : 0)) <= 1", message="only one of spec.targetNamespaces.selector or spec.targetNamespaces.stringSelector spec.targetNamespaces.list can be specified"
// +kubebuilder:validation:XValidation:rule="((has(self.stringSelector) ? 1 : 0) + (has(self.selector) ? 1 : 0) + (has(self.list) ? 1 : 0)) <= 1", message="only one of spec.targetNamespaces.selector or spec.targetNamespaces.stringSelector or spec.targetNamespaces.list can be specified"

// TargetNamespaces defines the list of namespaces or the label selector to select namespaces
type TargetNamespaces struct {
// StringSelector is a label query to select namespaces.
// Mutually exclusive with Selector.
// Mutually exclusive with Selector and List.
// +optional
StringSelector string `json:"stringSelector,omitempty"`
// Selector is a structured label query to select namespaces.
// Mutually exclusive with StringSelector.
// Mutually exclusive with StringSelector and List.
// +optional
Selector metav1.LabelSelector `json:"selector,omitempty"`
Selector *metav1.LabelSelector `json:"selector,omitempty"`
// List is the list of namespaces to select.
// Mutually exclusive with StringSelector and Selector.
// +optional
List []string `json:"list,omitempty"`
}
Expand All @@ -88,6 +89,8 @@ type TemplateManagementStatus struct {
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// Current reflects the applied access rules configuration.
Current []AccessRule `json:"current,omitempty"`
// Error is the error message occurred during the reconciliation (if any)
Error string `json:"error,omitempty"`
}

func init() {
Expand Down
6 changes: 5 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 26 additions & 27 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,13 @@ import (
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
// to ensure that exec-entrypoint and run can make use of them.

_ "k8s.io/client-go/plugin/pkg/client/auth"

hcv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/dynamic"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
_ "k8s.io/client-go/plugin/pkg/client/auth"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/healthz"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
Expand Down Expand Up @@ -171,12 +170,18 @@ func main() {

currentNamespace := utils.CurrentNamespace()

templateReconciler := controller.TemplateReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
SystemNamespace: currentNamespace,
DefaultRegistryURL: defaultRegistryURL,
DefaultRepoType: determinedRepositoryType,
RegistryCredentialsSecret: registryCredentialsSecret,
InsecureRegistry: insecureRegistry,
}

if err = (&controller.ClusterTemplateReconciler{
TemplateReconciler: controller.TemplateReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
SystemNamespace: currentNamespace,
},
TemplateReconciler: templateReconciler,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ClusterTemplate")
os.Exit(1)
Expand All @@ -189,21 +194,13 @@ func main() {
}

if err = (&controller.ServiceTemplateReconciler{
TemplateReconciler: controller.TemplateReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
SystemNamespace: currentNamespace,
},
TemplateReconciler: templateReconciler,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ServiceTemplate")
os.Exit(1)
}
if err = (&controller.ProviderTemplateReconciler{
TemplateReconciler: controller.TemplateReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
SystemNamespace: currentNamespace,
},
TemplateReconciler: templateReconciler,
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ProviderTemplate")
os.Exit(1)
Expand Down Expand Up @@ -237,16 +234,12 @@ func main() {
os.Exit(1)
}
if err = mgr.Add(&controller.Poller{
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
CreateManagement: createManagement,
CreateTemplates: createTemplates,
DefaultRegistryURL: defaultRegistryURL,
DefaultRepoType: determinedRepositoryType,
RegistryCredentialsSecret: registryCredentialsSecret,
InsecureRegistry: insecureRegistry,
HMCTemplatesChartName: hmcTemplatesChartName,
SystemNamespace: currentNamespace,
Client: mgr.GetClient(),
Config: mgr.GetConfig(),
CreateManagement: createManagement,
CreateTemplates: createTemplates,
HMCTemplatesChartName: hmcTemplatesChartName,
SystemNamespace: currentNamespace,
}); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "ReleaseController")
os.Exit(1)
Expand Down Expand Up @@ -284,6 +277,12 @@ func main() {
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")
os.Exit(1)
}
if err := (&hmcwebhook.ClusterTemplateValidator{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "ClusterTemplate")
os.Exit(1)
Expand Down
56 changes: 3 additions & 53 deletions internal/controller/release_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"time"

hcv2 "github.com/fluxcd/helm-controller/api/v2"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
Expand Down Expand Up @@ -57,16 +56,8 @@ type Poller struct {
CreateManagement bool
CreateTemplates bool

// DefaultRepoType is the type specified by default in HelmRepository
// objects. Valid types are 'default' for http/https repositories, and
// 'oci' for OCI repositories. The RepositoryType is set in main based on
// the URI scheme of the DefaultRegistryURL.
DefaultRepoType string
DefaultRegistryURL string
RegistryCredentialsSecret string
InsecureRegistry bool
HMCTemplatesChartName string
SystemNamespace string
HMCTemplatesChartName string
SystemNamespace string
}

func (p *Poller) Start(ctx context.Context) error {
Expand All @@ -92,12 +83,7 @@ func (p *Poller) Tick(ctx context.Context) error {
l.Info("Poll is run")
defer l.Info("Poll is finished")

err := p.reconcileDefaultHelmRepo(ctx)
if err != nil {
l.Error(err, "failed to reconcile default HelmRepository")
return err
}
err = p.reconcileHMCTemplates(ctx)
err := p.reconcileHMCTemplates(ctx)
if err != nil {
l.Error(err, "failed to reconcile HMC Templates")
return err
Expand Down Expand Up @@ -176,42 +162,6 @@ func (p *Poller) ensureManagement(ctx context.Context) error {
return nil
}

func (p *Poller) reconcileDefaultHelmRepo(ctx context.Context) error {
l := log.FromContext(ctx)
helmRepo := &sourcev1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{
Name: defaultRepoName,
Namespace: p.SystemNamespace,
},
}
operation, err := ctrl.CreateOrUpdate(ctx, p.Client, helmRepo, func() error {
if helmRepo.Labels == nil {
helmRepo.Labels = make(map[string]string)
}

helmRepo.Labels[hmc.HMCManagedLabelKey] = hmc.HMCManagedLabelValue
helmRepo.Spec = sourcev1.HelmRepositorySpec{
Type: p.DefaultRepoType,
URL: p.DefaultRegistryURL,
Interval: metav1.Duration{Duration: helm.DefaultReconcileInterval},
Insecure: p.InsecureRegistry,
}
if p.RegistryCredentialsSecret != "" {
helmRepo.Spec.SecretRef = &meta.LocalObjectReference{
Name: p.RegistryCredentialsSecret,
}
}
return nil
})
if err != nil {
return err
}
if operation == controllerutil.OperationResultCreated || operation == controllerutil.OperationResultUpdated {
l.Info(fmt.Sprintf("Successfully %s %s/%s HelmRepository", operation, p.SystemNamespace, defaultRepoName))
}
return nil
}

func (p *Poller) reconcileHMCTemplates(ctx context.Context) error {
l := log.FromContext(ctx)
if !p.CreateTemplates {
Expand Down
5 changes: 4 additions & 1 deletion internal/controller/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ var _ = BeforeSuite(func() {
err = (&hmcwebhook.ManagementValidator{}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = hmcwebhook.SetupTemplateIndex(ctx, mgr)
err = (&hmcwebhook.TemplateManagementValidator{}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = (&hmcwebhook.ClusterTemplateValidator{}).SetupWebhookWithManager(mgr)
Expand All @@ -153,6 +153,9 @@ var _ = BeforeSuite(func() {
err = (&hmcwebhook.ProviderTemplateValidator{}).SetupWebhookWithManager(mgr)
Expect(err).NotTo(HaveOccurred())

err = hmcwebhook.SetupTemplateIndex(ctx, mgr)
Expect(err).NotTo(HaveOccurred())

go func() {
defer GinkgoRecover()
err = mgr.Start(ctx)
Expand Down
75 changes: 68 additions & 7 deletions internal/controller/template_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

helmcontrollerv2 "github.com/fluxcd/helm-controller/api/v2"
v2 "github.com/fluxcd/helm-controller/api/v2"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
"helm.sh/helm/v3/pkg/chart"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
Expand All @@ -31,6 +32,7 @@ import (
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"

hmc "github.com/Mirantis/hmc/api/v1alpha1"
Expand All @@ -44,8 +46,18 @@ const (
// TemplateReconciler reconciles a Template object
type TemplateReconciler struct {
client.Client
Scheme *runtime.Scheme
SystemNamespace string
Scheme *runtime.Scheme
SystemNamespace string

// DefaultRepoType is the type specified by default in HelmRepository
// objects. Valid types are 'default' for http/https repositories, and
// 'oci' for OCI repositories. The RepositoryType is set in main based on
// the URI scheme of the DefaultRegistryURL.
DefaultRepoType string
DefaultRegistryURL string
RegistryCredentialsSecret string
InsecureRegistry bool

downloadHelmChartFunc func(context.Context, *sourcev1.Artifact) (*chart.Chart, error)
}

Expand Down Expand Up @@ -138,6 +150,13 @@ func (r *TemplateReconciler) ReconcileTemplate(ctx context.Context, template Tem
l.Error(err, "invalid helm chart reference")
return ctrl.Result{}, err
}
if template.GetNamespace() == r.SystemNamespace || !templateManagedByHMC(template) {
err := r.reconcileDefaultHelmRepository(ctx, template.GetNamespace())
if err != nil {
l.Error(err, "Failed to reconcile default HelmRepository", "namespace", template.GetNamespace())
return ctrl.Result{}, err
}
}
l.Info("Reconciling helm-controller objects ")
hcChart, err = r.reconcileHelmChart(ctx, template)
if err != nil {
Expand Down Expand Up @@ -204,6 +223,14 @@ func (r *TemplateReconciler) ReconcileTemplate(ctx context.Context, template Tem
return ctrl.Result{}, r.updateStatus(ctx, template, "")
}

func templateManagedByHMC(template Template) bool {
labels := template.GetLabels()
if labels == nil {
return false
}
return labels[hmc.HMCManagedLabelKey] == hmc.HMCManagedLabelValue
}

func (r *TemplateReconciler) parseChartMetadata(template Template, chart *chart.Chart) error {
if chart.Metadata == nil {
return fmt.Errorf("chart metadata is empty")
Expand Down Expand Up @@ -251,13 +278,47 @@ func (r *TemplateReconciler) updateStatus(ctx context.Context, template Template
return nil
}

func (r *TemplateReconciler) reconcileHelmChart(ctx context.Context, template Template) (*sourcev1.HelmChart, error) {
spec := template.GetSpec()
if spec.Helm.ChartRef != nil {
// HelmChart is not managed by the controller
return nil, nil
func (r *TemplateReconciler) reconcileDefaultHelmRepository(ctx context.Context, namespace string) error {
l := log.FromContext(ctx)
if namespace == "" {
namespace = r.SystemNamespace
}
helmRepo := &sourcev1.HelmRepository{
ObjectMeta: metav1.ObjectMeta{
Name: defaultRepoName,
Namespace: namespace,
},
}
operation, err := ctrl.CreateOrUpdate(ctx, r.Client, helmRepo, func() error {
if helmRepo.Labels == nil {
helmRepo.Labels = make(map[string]string)
}

helmRepo.Labels[hmc.HMCManagedLabelKey] = hmc.HMCManagedLabelValue
helmRepo.Spec = sourcev1.HelmRepositorySpec{
Type: r.DefaultRepoType,
URL: r.DefaultRegistryURL,
Interval: metav1.Duration{Duration: helm.DefaultReconcileInterval},
Insecure: r.InsecureRegistry,
}
if r.RegistryCredentialsSecret != "" {
helmRepo.Spec.SecretRef = &meta.LocalObjectReference{
Name: r.RegistryCredentialsSecret,
}
}
return nil
})
if err != nil {
return err
}
if operation == controllerutil.OperationResultCreated || operation == controllerutil.OperationResultUpdated {
l.Info(fmt.Sprintf("Successfully %s %s/%s HelmRepository", operation, namespace, defaultRepoName))
}
return nil
}

func (r *TemplateReconciler) reconcileHelmChart(ctx context.Context, template Template) (*sourcev1.HelmChart, error) {
spec := template.GetSpec()
namespace := template.GetNamespace()
if namespace == "" {
namespace = r.SystemNamespace
Expand Down
Loading

0 comments on commit 287d319

Please sign in to comment.