Add changes to Armada Charts CRD

Allow tracking of helm status, resource wait and tests.

Change-Id: I9e4de81272c7471f326679d5c4b4427cbf836797
Signed-off-by: Ruslan Aliev <raliev@mirantis.com>
This commit is contained in:
Ruslan Aliev 2024-02-08 15:47:11 -06:00
parent d557f99c8f
commit 0f3ef2a6c9
3 changed files with 115 additions and 32 deletions

View File

@ -172,10 +172,19 @@ type ArmadaChartStatus struct {
// +optional
UpgradeFailures int64 `json:"upgradeFailures,omitempty"`
// HelmStatus describes the status of helm release
// +optional
HelmStatus string `json:"helmStatus,omitempty"`
// Tested is the bool value whether the Helm Release was successfully
// tested or not.
// +optional
Tested bool `json:"tested,omitempty"`
// WaitCompleted is the bool value whether the Helm Release resources were
// waited for or not.
// +optional
WaitCompleted bool `json:"waitCompleted,omitempty"`
}
// ArmadaChartProgressing resets any failures and registers progress toward
@ -191,7 +200,9 @@ func ArmadaChartProgressing(ac ArmadaChart) ArmadaChart {
}
apimeta.SetStatusCondition(ac.GetStatusConditions(), newCondition)
resetFailureCounts(&ac)
resetWaitCompleted(&ac)
resetTested(&ac)
ac.Status.HelmStatus = "Unknown"
return ac
}
@ -238,6 +249,14 @@ func setTested(hr *ArmadaChart) {
hr.Status.Tested = true
}
func resetWaitCompleted(hr *ArmadaChart) {
hr.Status.WaitCompleted = false
}
func setWaitCompleted(hr *ArmadaChart) {
hr.Status.WaitCompleted = true
}
func NewForConfigOrDie(c *rest.Config) *rest.RESTClient {
cs, err := NewForConfig(c)
if err != nil {
@ -280,6 +299,13 @@ func setConfigDefaults(config *rest.Config) error {
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Namespace",type=string,JSONPath=`.metadata.namespace`
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="Ready",type=boolean,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:printcolumn:name="Message",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].message`,priority=10
// +kubebuilder:printcolumn:name="Helm Status",type=string,JSONPath=`.status.helmStatus`
// +kubebuilder:printcolumn:name="Wait Completed",type=boolean,JSONPath=`.status.waitCompleted`
// +kubebuilder:printcolumn:name="Tested",type=boolean,JSONPath=`.status.tested`
// ArmadaChart is the Schema for the armadacharts API
type ArmadaChart struct {

View File

@ -14,7 +14,30 @@ spec:
singular: armadachart
scope: Namespaced
versions:
- name: v1
- additionalPrinterColumns:
- jsonPath: .metadata.namespace
name: Namespace
type: string
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: boolean
- jsonPath: .status.conditions[?(@.type=="Ready")].message
name: Message
priority: 10
type: string
- jsonPath: .status.helmStatus
name: Helm Status
type: string
- jsonPath: .status.waitCompleted
name: Wait Completed
type: boolean
- jsonPath: .status.tested
name: Tested
type: boolean
name: v1
schema:
openAPIV3Schema:
description: ArmadaChart is the Schema for the armadacharts API
@ -200,6 +223,9 @@ spec:
description: HelmChart is the namespaced name of the HelmChart resource
created by the controller for the ArmadaChart.
type: string
helmStatus:
description: HelmStatus describes the status of helm release
type: string
installFailures:
description: InstallFailures is the install failure count against
the latest desired state. It is reset after a successful reconciliation.
@ -234,6 +260,10 @@ spec:
the latest desired state. It is reset after a successful reconciliation.
format: int64
type: integer
waitCompleted:
description: WaitCompleted is the bool value whether the Helm Release
resources were waited for or not.
type: boolean
type: object
type: object
served: true

View File

@ -129,10 +129,10 @@ func (r *ArmadaChartReconciler) reconcile(ctx context.Context, ac armadav1.Armad
// Prepare values
var vals map[string]interface{}
if ac.Spec.Values != nil {
var vals_err error
vals, vals_err = chartutil.ReadValues(ac.Spec.Values.Raw)
if vals_err != nil {
return armadav1.ArmadaChartNotReady(ac, "InitFailed", vals_err.Error()), ctrl.Result{}, vals_err
var valsErr error
vals, valsErr = chartutil.ReadValues(ac.Spec.Values.Raw)
if valsErr != nil {
return armadav1.ArmadaChartNotReady(ac, "InitFailed", valsErr.Error()), ctrl.Result{}, valsErr
}
}
// Load chart from artifact
@ -154,6 +154,10 @@ func (r *ArmadaChartReconciler) reconcileChart(ctx context.Context,
if err != nil {
return armadav1.ArmadaChartNotReady(ac, "InitFailed", err.Error()), err
}
restCfg, err := gettr.ToRESTConfig()
if err != nil {
return armadav1.ArmadaChartNotReady(ac, "InitFailed", err.Error()), err
}
run, err := runner.NewRunner(gettr, ac.Namespace, log)
if err != nil {
@ -167,24 +171,19 @@ func (r *ArmadaChartReconciler) reconcileChart(ctx context.Context,
return armadav1.ArmadaChartNotReady(ac, "GetLastReleaseFailed", "failed to get last release revision"), err
}
testRel := func() (armadav1.ArmadaChart, error) {
if ac.Spec.Test.Enabled && !ac.Status.Tested {
log.Info("performing tests")
rel, err = run.Test(ac)
if err != nil {
return armadav1.ArmadaChartNotReady(ac, "TestFailed", err.Error()), err
}
}
return armadav1.ArmadaChartReady(ac), err
}
if rel == nil {
log.Info("helm install has started")
rel, err = run.Install(ctx, ac, chrt, vals)
} else {
ac.Status.HelmStatus = string(rel.Info.Status)
if updateStatusErr := r.patchStatus(ctx, &ac); updateStatusErr != nil {
log.Error(updateStatusErr, "unable to update status after helm status update")
return armadav1.ArmadaChartNotReady(ac, "UpdateStatusFailed", updateStatusErr.Error()), updateStatusErr
}
if rel.Info.Status == release.StatusDeployed && !isUpdateRequired(ctx, rel, chrt, vals) {
log.Info("no updates found, skipping upgrade")
return testRel()
return r.finalizeRelease(ctx, run, restCfg, ac)
}
if rel.Info.Status.IsPending() {
@ -221,25 +220,36 @@ func (r *ArmadaChartReconciler) reconcileChart(ctx context.Context,
err = fmt.Errorf("failed to install/upgrade helm release: %s", err.Error())
return armadav1.ArmadaChartNotReady(ac, "InstallUpgradeFailed", err.Error()), err
}
if ac.Spec.Wait.Timeout > 0 && len(ac.Spec.Wait.Labels) > 0 {
log.Info("preparing to wait resources")
resCfg, err := gettr.ToRESTConfig()
if err != nil {
return armadav1.ArmadaChartNotReady(ac, "WaitFailed", err.Error()), err
}
err = r.waitRelease(ctx, resCfg, ac)
if err != nil {
return armadav1.ArmadaChartNotReady(ac, "WaitFailed", err.Error()), err
}
ac.Status.HelmStatus = string(rel.Info.Status)
if err := r.patchStatus(ctx, &ac); err != nil {
log.Error(err, "unable to update armadachart status")
}
return testRel()
return r.finalizeRelease(ctx, run, restCfg, ac)
}
func (r *ArmadaChartReconciler) waitRelease(ctx context.Context, restCfg *rest.Config, hr armadav1.ArmadaChart) error {
func (r *ArmadaChartReconciler) finalizeRelease(ctx context.Context, run *runner.Runner, restCfg *rest.Config, hr armadav1.ArmadaChart) (armadav1.ArmadaChart, error) {
hr, err := r.waitRelease(ctx, restCfg, hr)
if err != nil {
return hr, err
}
return r.testRelease(ctx, run, hr)
}
func (r *ArmadaChartReconciler) waitRelease(ctx context.Context, restCfg *rest.Config, hr armadav1.ArmadaChart) (armadav1.ArmadaChart, error) {
log := ctrl.LoggerFrom(ctx)
if hr.Status.WaitCompleted {
log.Info("wait has been already completed")
return hr, nil
}
if hr.Spec.Wait.Timeout == 0 {
log.Info("No Chart timeout specified, using default 900s")
hr.Spec.Wait.Timeout = 900
}
if hr.Spec.Wait.ArmadaChartWaitResources == nil {
log.Info(fmt.Sprintf("there are no explicitly defined resources to wait: %s, using default ones", hr.Name))
hr.Spec.Wait.ArmadaChartWaitResources = []armadav1.ArmadaChartWaitResource{{Type: "job"}, {Type: "pod"}}
@ -288,12 +298,28 @@ func (r *ArmadaChartReconciler) waitRelease(ctx context.Context, restCfg *rest.C
}
err := opts.Wait(ctx)
if err != nil {
return err
return hr, err
}
}
log.Info("all resources are ready")
return nil
hr.Status.WaitCompleted = true
if err := r.patchStatus(ctx, &hr); err != nil {
return hr, err
}
return hr, nil
}
func (r *ArmadaChartReconciler) testRelease(ctx context.Context, run *runner.Runner, ac armadav1.ArmadaChart) (armadav1.ArmadaChart, error) {
log := ctrl.LoggerFrom(ctx)
if ac.Spec.Test.Enabled && !ac.Status.Tested {
log.Info("performing tests")
if _, err := run.Test(ac); err != nil {
return armadav1.ArmadaChartNotReady(ac, "TestFailed", err.Error()), err
}
}
return armadav1.ArmadaChartReady(ac), nil
}
// loadHelmChart attempts to download the artifact from the provided source,
@ -372,6 +398,7 @@ func (r *ArmadaChartReconciler) reconcileDelete(ctx context.Context, ac *armadav
// Remove our finalizer from the list and update it.
controllerutil.RemoveFinalizer(ac, armadav1.ArmadaChartFinalizer)
ac.Status.HelmStatus = string(release.StatusUninstalled)
if err := r.Update(ctx, ac); err != nil {
return ctrl.Result{}, err
}