Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend helm chart configuration for Templates #699

Merged
merged 1 commit into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions api/v1alpha1/templates_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"strings"

helmcontrollerv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
)

Expand All @@ -30,27 +31,33 @@ const (
ChartAnnotationProviderName = "cluster.x-k8s.io/provider"

chartAnnoCAPIPrefix = "cluster.x-k8s.io/"

DefaultRepoName = "hmc-templates"
)

// +kubebuilder:validation:XValidation:rule="(has(self.chartName) && !has(self.chartRef)) || (!has(self.chartName) && has(self.chartRef))", message="either chartName or chartRef must be set"
var DefaultSourceRef = sourcev1.LocalHelmChartSourceReference{
Kind: sourcev1.HelmRepositoryKind,
Name: DefaultRepoName,
}

// +kubebuilder:validation:XValidation:rule="(has(self.chartSpec) && !has(self.chartRef)) || (!has(self.chartSpec) && has(self.chartRef))", message="either chartSpec or chartRef must be set"

// HelmSpec references a Helm chart representing the HMC template
type HelmSpec struct {
// ChartSpec defines the desired state of the HelmChart to be created by the controller
ChartSpec *sourcev1.HelmChartSpec `json:"chartSpec,omitempty"`

// ChartRef is a reference to a source controller resource containing the
// Helm chart representing the template.
ChartRef *helmcontrollerv2.CrossNamespaceSourceReference `json:"chartRef,omitempty"`
Kshatrix marked this conversation as resolved.
Show resolved Hide resolved
// ChartName is a name of a Helm chart representing the template in the HMC repository.
ChartName string `json:"chartName,omitempty"`
// ChartVersion is a version of a Helm chart representing the template in the HMC repository.
ChartVersion string `json:"chartVersion,omitempty"`
}

func (s *HelmSpec) String() string {
if s.ChartRef != nil {
return s.ChartRef.Namespace + "/" + s.ChartRef.Name + ", Kind=" + s.ChartRef.Kind
}

return s.ChartName + ": " + s.ChartVersion
return s.ChartSpec.Chart + ": " + s.ChartSpec.Version
}

// TemplateStatusCommon defines the observed state of Template common for all Template types
Expand Down
6 changes: 6 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

9 changes: 7 additions & 2 deletions hack/templates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,13 @@ metadata:
helm.sh/resource-policy: keep
spec:
helm:
chartName: $name
chartVersion: $version
chartSpec:
chart: $name
version: $version
interval: 10m0s
Kshatrix marked this conversation as resolved.
Show resolved Hide resolved
sourceRef:
kind: HelmRepository
name: hmc-templates
EOF

echo "Generated $TEMPLATES_OUTPUT_DIR/$name.yaml"
Expand Down
10 changes: 8 additions & 2 deletions internal/controller/managedcluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ func (r *ManagedClusterReconciler) updateCluster(ctx context.Context, mc *hmc.Ma
return ctrl.Result{}, fmt.Errorf("error setting identity values: %w", err)
}

hr, _, err := helm.ReconcileHelmRelease(ctx, r.Client, mc.Name, mc.Namespace, helm.ReconcileHelmReleaseOpts{
hrReconcileOpts := helm.ReconcileHelmReleaseOpts{
Values: helmValues,
OwnerReference: &metav1.OwnerReference{
APIVersion: hmc.GroupVersion.String(),
Expand All @@ -311,7 +311,13 @@ func (r *ManagedClusterReconciler) updateCluster(ctx context.Context, mc *hmc.Ma
UID: mc.UID,
},
ChartRef: clusterTpl.Status.ChartRef,
})
}
reconcileInterval := clusterTpl.Spec.Helm.ChartSpec.Interval.Duration
if reconcileInterval != 0 {
hrReconcileOpts.ReconcileInterval = &reconcileInterval
}

hr, _, err := helm.ReconcileHelmRelease(ctx, r.Client, mc.Name, mc.Namespace, hrReconcileOpts)
if err != nil {
apimeta.SetStatusCondition(mc.GetConditions(), metav1.Condition{
Type: hmc.HelmReleaseReadyCondition,
Expand Down
10 changes: 8 additions & 2 deletions internal/controller/management_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,19 @@ func (r *ManagementReconciler) Update(ctx context.Context, management *hmc.Manag
continue
}

if _, _, err := helm.ReconcileHelmRelease(ctx, r.Client, component.helmReleaseName, r.SystemNamespace, helm.ReconcileHelmReleaseOpts{
hrReconcileOpts := helm.ReconcileHelmReleaseOpts{
Values: component.Config,
ChartRef: template.Status.ChartRef,
DependsOn: component.dependsOn,
TargetNamespace: component.targetNamespace,
CreateNamespace: component.createNamespace,
}); err != nil {
}
reconcileInterval := template.Spec.Helm.ChartSpec.Interval.Duration
if reconcileInterval != 0 {
hrReconcileOpts.ReconcileInterval = &reconcileInterval
}

if _, _, err := helm.ReconcileHelmRelease(ctx, r.Client, component.helmReleaseName, r.SystemNamespace, hrReconcileOpts); err != nil {
errMsg := fmt.Sprintf("Failed to reconcile HelmRelease %s/%s: %s", r.SystemNamespace, component.helmReleaseName, err)
updateComponentsStatus(statusAccumulator, component, nil, errMsg)
errs = errors.Join(errs, errors.New(errMsg))
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/management_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,10 @@ var _ = Describe("Management Controller", func() {
},
Spec: hmcmirantiscomv1alpha1.ProviderTemplateSpec{
Helm: hmcmirantiscomv1alpha1.HelmSpec{
ChartName: "required-chart",
ChartVersion: "required-version",
ChartSpec: &sourcev1.HelmChartSpec{
Chart: "required-chart",
Version: "required-version",
},
},
},
}
Expand Down
6 changes: 4 additions & 2 deletions internal/controller/multiclusterservice_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,10 @@ var _ = Describe("MultiClusterService Controller", func() {
},
Spec: hmc.ServiceTemplateSpec{
Helm: hmc.HelmSpec{
ChartName: helmChartName,
ChartVersion: helmChartVersion,
ChartSpec: &sourcev1.HelmChartSpec{
Chart: helmChartName,
Version: helmChartVersion,
},
},
},
}
Expand Down
13 changes: 5 additions & 8 deletions internal/controller/release_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func (r *ReleaseReconciler) reconcileHMCTemplates(ctx context.Context, releaseNa
if releaseName == "" {
releaseName = utils.ReleaseNameFromVersion(build.Version)
releaseVersion = build.Version
err := helm.ReconcileHelmRepository(ctx, r.Client, defaultRepoName, r.SystemNamespace, r.DefaultRegistryConfig.HelmRepositorySpec())
err := helm.ReconcileHelmRepository(ctx, r.Client, hmc.DefaultRepoName, r.SystemNamespace, r.DefaultRegistryConfig.HelmRepositorySpec())
Kshatrix marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
l.Error(err, "Failed to reconcile default HelmRepository", "namespace", r.SystemNamespace)
return err
Expand Down Expand Up @@ -280,13 +280,10 @@ func (r *ReleaseReconciler) reconcileHMCTemplates(ctx context.Context, releaseNa
}
helmChart.Labels[hmc.HMCManagedLabelKey] = hmc.HMCManagedLabelValue
helmChart.Spec = sourcev1.HelmChartSpec{
Chart: r.HMCTemplatesChartName,
Version: releaseVersion,
SourceRef: sourcev1.LocalHelmChartSourceReference{
Kind: sourcev1.HelmRepositoryKind,
Name: defaultRepoName,
},
Interval: metav1.Duration{Duration: helm.DefaultReconcileInterval},
Chart: r.HMCTemplatesChartName,
Version: releaseVersion,
SourceRef: hmc.DefaultSourceRef,
Interval: metav1.Duration{Duration: helm.DefaultReconcileInterval},
}
return nil
})
Expand Down
32 changes: 6 additions & 26 deletions internal/controller/template_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,7 @@ import (
"github.com/Mirantis/hmc/internal/utils"
)

const (
defaultRepoName = "hmc-templates"

defaultRequeueTime = 1 * time.Minute
)
const defaultRequeueTime = 1 * time.Minute

// TemplateReconciler reconciles a *Template object
type TemplateReconciler struct {
Expand Down Expand Up @@ -184,8 +180,8 @@ func (r *TemplateReconciler) ReconcileTemplate(ctx context.Context, template tem
return ctrl.Result{}, err
}
} else {
if helmSpec.ChartName == "" {
err := errors.New("neither chartName nor chartRef is set")
if helmSpec.ChartSpec == nil {
err := errors.New("neither chartSpec nor chartRef is set")
l.Error(err, "invalid helm chart reference")
return ctrl.Result{}, err
}
Expand All @@ -194,7 +190,7 @@ func (r *TemplateReconciler) ReconcileTemplate(ctx context.Context, template tem
if namespace == "" {
namespace = r.SystemNamespace
}
err := helm.ReconcileHelmRepository(ctx, r.Client, defaultRepoName, namespace, r.DefaultRegistryConfig.HelmRepositorySpec())
err := helm.ReconcileHelmRepository(ctx, r.Client, hmc.DefaultRepoName, namespace, r.DefaultRegistryConfig.HelmRepositorySpec())
if err != nil {
l.Error(err, "Failed to reconcile default HelmRepository")
return ctrl.Result{}, err
Expand Down Expand Up @@ -314,25 +310,9 @@ func (r *TemplateReconciler) reconcileHelmChart(ctx context.Context, template te
}

helmChart.Labels[hmc.HMCManagedLabelKey] = hmc.HMCManagedLabelValue
helmChart.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: hmc.GroupVersion.String(),
Kind: template.GetObjectKind().GroupVersionKind().Kind,
Name: template.GetName(),
UID: template.GetUID(),
},
}

helmChart.Spec = sourcev1.HelmChartSpec{
Chart: helmSpec.ChartName,
Version: helmSpec.ChartVersion,
SourceRef: sourcev1.LocalHelmChartSourceReference{
Kind: sourcev1.HelmRepositoryKind,
Name: defaultRepoName,
},
Interval: metav1.Duration{Duration: helm.DefaultReconcileInterval},
}
utils.AddOwnerReference(helmChart, template)

helmChart.Spec = *helmSpec.ChartSpec
return nil
})

Expand Down
3 changes: 2 additions & 1 deletion internal/controller/templatechain_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"time"

helmcontrollerv2 "github.com/fluxcd/helm-controller/api/v2"
sourcev1 "github.com/fluxcd/source-controller/api/v1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -52,7 +53,7 @@ var _ = Describe("Template Chain Controller", func() {
}

chartName := "test"
templateHelmSpec := hmcmirantiscomv1alpha1.HelmSpec{ChartName: chartName}
templateHelmSpec := hmcmirantiscomv1alpha1.HelmSpec{ChartSpec: &sourcev1.HelmChartSpec{Chart: chartName}}
chartRef := &helmcontrollerv2.CrossNamespaceSourceReference{
Kind: "HelmChart",
Namespace: utils.DefaultSystemNamespace,
Expand Down
2 changes: 1 addition & 1 deletion internal/telemetry/tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (t *Tracker) trackManagedClusterHeartbeat(ctx context.Context) error {
string(managedCluster.UID),
clusterID,
managedCluster.Spec.Template,
template.Spec.Helm.ChartVersion,
template.Spec.Helm.ChartSpec.Version,
template.Status.Providers,
)
if err != nil {
Expand Down
35 changes: 32 additions & 3 deletions internal/webhook/template_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

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

var errTemplateDeletionForbidden = errors.New("template deletion is forbidden")
Expand Down Expand Up @@ -95,7 +96,12 @@ func (v *ClusterTemplateValidator) ValidateDelete(ctx context.Context, obj runti
}

// Default implements webhook.Defaulter so a webhook will be registered for the type.
func (*ClusterTemplateValidator) Default(context.Context, runtime.Object) error {
func (*ClusterTemplateValidator) Default(_ context.Context, obj runtime.Object) error {
template, ok := obj.(*v1alpha1.ClusterTemplate)
if !ok {
return apierrors.NewBadRequest(fmt.Sprintf("expected ClusterTemplate but got a %T", obj))
}
setHelmChartDefaults(template.GetHelmSpec())
return nil
}

Expand Down Expand Up @@ -168,7 +174,12 @@ func (v *ServiceTemplateValidator) ValidateDelete(ctx context.Context, obj runti
}

// Default implements webhook.Defaulter so a webhook will be registered for the type.
func (*ServiceTemplateValidator) Default(_ context.Context, _ runtime.Object) error {
func (*ServiceTemplateValidator) Default(_ context.Context, obj runtime.Object) error {
template, ok := obj.(*v1alpha1.ServiceTemplate)
if !ok {
return apierrors.NewBadRequest(fmt.Sprintf("expected ServiceTemplate but got a %T", obj))
}
setHelmChartDefaults(template.GetHelmSpec())
return nil
}

Expand Down Expand Up @@ -229,7 +240,12 @@ func (v *ProviderTemplateValidator) ValidateDelete(ctx context.Context, obj runt
}

// Default implements webhook.Defaulter so a webhook will be registered for the type.
func (*ProviderTemplateValidator) Default(_ context.Context, _ runtime.Object) error {
func (*ProviderTemplateValidator) Default(_ context.Context, obj runtime.Object) error {
template, ok := obj.(*v1alpha1.ProviderTemplate)
if !ok {
return apierrors.NewBadRequest(fmt.Sprintf("expected ProviderTemplate but got a %T", obj))
}
setHelmChartDefaults(template.GetHelmSpec())
return nil
}

Expand Down Expand Up @@ -267,3 +283,16 @@ func getOwnersWithKind(template client.Object, kind string) []string {
}
return owners
}

func setHelmChartDefaults(helmSpec *v1alpha1.HelmSpec) {
if helmSpec == nil || helmSpec.ChartSpec == nil {
return
}
chartSpec := helmSpec.ChartSpec
if chartSpec.SourceRef.Name == "" && chartSpec.SourceRef.Kind == "" {
chartSpec.SourceRef = v1alpha1.DefaultSourceRef
}
if chartSpec.Interval.Duration == 0 {
chartSpec.Interval.Duration = helm.DefaultReconcileInterval
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ metadata:
helm.sh/resource-policy: keep
spec:
helm:
chartName: aws-eks
chartVersion: 0.0.2
chartSpec:
chart: aws-eks
version: 0.0.2
interval: 10m0s
sourceRef:
kind: HelmRepository
name: hmc-templates
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ metadata:
helm.sh/resource-policy: keep
spec:
helm:
chartName: aws-hosted-cp
chartVersion: 0.0.3
chartSpec:
chart: aws-hosted-cp
version: 0.0.3
interval: 10m0s
sourceRef:
kind: HelmRepository
name: hmc-templates
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ metadata:
helm.sh/resource-policy: keep
spec:
helm:
chartName: aws-standalone-cp
chartVersion: 0.0.3
chartSpec:
chart: aws-standalone-cp
version: 0.0.3
interval: 10m0s
sourceRef:
kind: HelmRepository
name: hmc-templates
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ metadata:
helm.sh/resource-policy: keep
spec:
helm:
chartName: azure-hosted-cp
chartVersion: 0.0.3
chartSpec:
chart: azure-hosted-cp
version: 0.0.3
interval: 10m0s
sourceRef:
kind: HelmRepository
name: hmc-templates
Loading
Loading