-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add main management-controller logic
HMC-23
- Loading branch information
Showing
5 changed files
with
225 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,45 +18,153 @@ package controller | |
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/Mirantis/hmc/internal/helm" | ||
"helm.sh/helm/v3/pkg/action" | ||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" | ||
"k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/client-go/rest" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
"sigs.k8s.io/controller-runtime/pkg/log" | ||
|
||
hmcmirantiscomv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" | ||
hmc "github.com/Mirantis/hmc/api/v1alpha1" | ||
) | ||
|
||
// ManagementReconciler reconciles a Management object | ||
type ManagementReconciler struct { | ||
client.Client | ||
Scheme *runtime.Scheme | ||
Config *rest.Config | ||
} | ||
|
||
//+kubebuilder:rbac:groups=hmc.mirantis.com,resources=managements,verbs=get;list;watch;create;update;patch;delete | ||
//+kubebuilder:rbac:groups=hmc.mirantis.com,resources=managements/status,verbs=get;update;patch | ||
//+kubebuilder:rbac:groups=hmc.mirantis.com,resources=managements/finalizers,verbs=update | ||
|
||
// Reconcile is part of the main kubernetes reconciliation loop which aims to | ||
// move the current state of the cluster closer to the desired state. | ||
// TODO(user): Modify the Reconcile function to compare the state specified by | ||
// the Management object against the actual cluster state, and then | ||
// perform operations to make the cluster state reflect the state specified by | ||
// the user. | ||
// | ||
// For more details, check Reconcile and its Result here: | ||
// - https://pkg.go.dev/sigs.k8s.io/[email protected]/pkg/reconcile | ||
func (r *ManagementReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { | ||
_ = log.FromContext(ctx) | ||
l := log.FromContext(ctx).WithValues("ManagementController", req.NamespacedName) | ||
l.Info("Reconciling Management") | ||
|
||
// TODO(user): your logic here | ||
management := &hmc.Management{} | ||
if err := r.Get(ctx, req.NamespacedName, management); err != nil { | ||
if errors.IsNotFound(err) { | ||
l.Info("Management config not found, creating default configuration") | ||
err = r.ensureDefaultManagement(ctx) | ||
if err != nil { | ||
return ctrl.Result{}, err | ||
} | ||
return ctrl.Result{}, nil | ||
} | ||
l.Error(err, "Failed to get Management") | ||
return ctrl.Result{}, err | ||
} | ||
|
||
for _, component := range management.Spec.Components { | ||
template := &hmc.Template{} | ||
err := r.Get(ctx, types.NamespacedName{ | ||
Namespace: hmc.TemplatesNamespace, | ||
Name: component.Template, | ||
}, template) | ||
if err != nil { | ||
l.Error(err, "Failed to get Template", "namespace", hmc.TemplatesNamespace, "name", component.Template) | ||
continue | ||
} | ||
if !template.Status.Valid { | ||
errMsg := fmt.Sprintf("Template %s/%s is not marked as valid", hmc.TemplatesNamespace, component.Template) | ||
_ = r.updateStatus(ctx, management, component.Template, errMsg) | ||
return ctrl.Result{}, fmt.Errorf(errMsg) | ||
} | ||
|
||
// Applying defaults | ||
if component.Config == nil && template.Status.Config != nil { | ||
component.Config = &apiextensionsv1.JSON{Raw: template.Status.Config.Raw} | ||
} | ||
|
||
source, err := helm.GetSource(ctx, r.Client, template.Status.ChartRef) | ||
if err != nil { | ||
_ = r.updateStatus(ctx, management, component.Template, fmt.Sprintf("failed to get helm chart source: %s", err)) | ||
return ctrl.Result{}, err | ||
} | ||
l.Info("Downloading Helm chart") | ||
hcChart, err := helm.DownloadChartFromArtifact(ctx, source.GetArtifact()) | ||
if err != nil { | ||
_ = r.updateStatus(ctx, management, component.Template, fmt.Sprintf("failed to download helm chart: %s", err)) | ||
return ctrl.Result{}, err | ||
} | ||
|
||
l.Info("Initializing Helm client") | ||
getter := helm.NewMemoryRESTClientGetter(r.Config, r.RESTMapper()) | ||
actionConfig := new(action.Configuration) | ||
err = actionConfig.Init(getter, management.Namespace, "secret", l.Info) | ||
if err != nil { | ||
_ = r.updateStatus(ctx, management, component.Template, fmt.Sprintf("failed to initialize helm client: %s", err)) | ||
return ctrl.Result{}, err | ||
} | ||
|
||
l.Info("Validating Helm chart with provided values") | ||
values, err := component.HelmValues() | ||
if err != nil { | ||
_ = r.updateStatus(ctx, management, component.Template, fmt.Sprintf("failed to get helm values: %s", err)) | ||
return ctrl.Result{}, err | ||
} | ||
if err := helm.ValidateReleaseWithValues(ctx, actionConfig, management.Name, management.Namespace, values, hcChart); err != nil { | ||
_ = r.updateStatus(ctx, management, component.Template, fmt.Sprintf("failed to validate template with provided configuration: %s", err)) | ||
return ctrl.Result{}, err | ||
} | ||
|
||
ownerRef := metav1.OwnerReference{ | ||
APIVersion: hmc.GroupVersion.String(), | ||
Kind: hmc.DeploymentKind, | ||
Name: management.Name, | ||
UID: management.UID, | ||
} | ||
_, err = helm.ReconcileHelmRelease(ctx, r.Client, management.Name, management.Namespace, component.Config, | ||
ownerRef, template.Status.ChartRef, defaultReconcileInterval) | ||
if err != nil { | ||
return ctrl.Result{}, err | ||
} | ||
} | ||
return ctrl.Result{}, nil | ||
} | ||
|
||
func (r *ManagementReconciler) ensureDefaultManagement(ctx context.Context) error { | ||
management := &hmc.Management{} | ||
management.Name = hmc.ManagementName | ||
management.Namespace = hmc.ManagementNamespace | ||
management.Spec.SetDefaults() | ||
err := r.Create(ctx, management) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
func (r *ManagementReconciler) updateStatus(ctx context.Context, management *hmc.Management, componentName, err string) error { | ||
management.Status.ObservedGeneration = management.Generation | ||
components := management.Status.Components | ||
if components == nil { | ||
components = make(map[string]hmc.ComponentStatus) | ||
} | ||
components[componentName] = hmc.ComponentStatus{ | ||
Error: err, | ||
Success: err == "", | ||
} | ||
management.Status.Components = components | ||
|
||
if err := r.Status().Update(ctx, management); err != nil { | ||
return fmt.Errorf("failed to update status for Management %s/%s: %w", management.Namespace, management.Name, err) | ||
} | ||
return nil | ||
} | ||
|
||
// SetupWithManager sets up the controller with the Manager. | ||
func (r *ManagementReconciler) SetupWithManager(mgr ctrl.Manager) error { | ||
return ctrl.NewControllerManagedBy(mgr). | ||
For(&hmcmirantiscomv1alpha1.Management{}). | ||
For(&hmc.Management{}). | ||
Complete(r) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package helm | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
hcv2 "github.com/fluxcd/helm-controller/api/v2" | ||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
func ReconcileHelmRelease( | ||
ctx context.Context, | ||
cl client.Client, | ||
name string, | ||
namespace string, | ||
values *apiextensionsv1.JSON, | ||
ownerReference metav1.OwnerReference, | ||
chartRef *hcv2.CrossNamespaceSourceReference, | ||
reconcileInterval time.Duration, | ||
) (*hcv2.HelmRelease, error) { | ||
helmRelease := &hcv2.HelmRelease{ | ||
ObjectMeta: metav1.ObjectMeta{ | ||
Name: name, | ||
Namespace: namespace, | ||
}, | ||
} | ||
|
||
_, err := ctrl.CreateOrUpdate(ctx, cl, helmRelease, func() error { | ||
helmRelease.OwnerReferences = []metav1.OwnerReference{ownerReference} | ||
helmRelease.Spec = hcv2.HelmReleaseSpec{ | ||
ChartRef: chartRef, | ||
Interval: metav1.Duration{Duration: reconcileInterval}, | ||
ReleaseName: name, | ||
Values: values, | ||
} | ||
return nil | ||
}) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return helmRelease, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package helm | ||
|
||
import ( | ||
"context" | ||
|
||
"helm.sh/helm/v3/pkg/action" | ||
"helm.sh/helm/v3/pkg/chart" | ||
) | ||
|
||
func ValidateReleaseWithValues( | ||
ctx context.Context, | ||
actionConfig *action.Configuration, | ||
releaseNamespace string, | ||
releaseName string, | ||
values map[string]interface{}, | ||
hcChart *chart.Chart, | ||
) error { | ||
install := action.NewInstall(actionConfig) | ||
install.DryRun = true | ||
install.ReleaseName = releaseName | ||
install.Namespace = releaseNamespace | ||
install.ClientOnly = false | ||
|
||
_, err := install.RunWithContext(ctx, hcChart, values) | ||
if err != nil { | ||
return err | ||
} | ||
return nil | ||
} |