Skip to content

Commit

Permalink
WIP: BOP-85/87
Browse files Browse the repository at this point in the history
  • Loading branch information
tppolkow committed Nov 23, 2023
1 parent 0a1dc65 commit fd8079e
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 10 deletions.
30 changes: 30 additions & 0 deletions api/v1alpha1/addon_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,44 @@ type ManifestInfo struct {
URL string `json:"url"`
}

// StatusConditionType is a type of condition that may apply to a particular component.
type StatusConditionType string

const (
// ComponentAvailable indicates that the component is healthy.
ComponentAvailable StatusConditionType = "Available"

// ComponentProgressing means that the component is in the process of being installed or upgraded.
ComponentProgressing StatusConditionType = "Progressing"

// ComponentDegraded means the component is not operating as desired and user action is required.
ComponentDegraded StatusConditionType = "Degraded"

// ComponentReady indicates that the component is healthy and ready.it is identical to Available and used in Status conditions for CRs.
ComponentReady StatusConditionType = "Ready"
)

// AddonStatus defines the observed state of Addon
type AddonStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file

// The type of condition. May be Available, Progressing, or Degraded.
Type StatusConditionType `json:"type"`

// The timestamp representing the start time for the current status.
LastTransitionTime metav1.Time `json:"lastTransitionTime"`

// A brief reason explaining the condition.
Reason string `json:"reason,omitempty"`

// Optionally, a detailed message providing additional context.
Message string `json:"message,omitempty"`
}

//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.type",description="Whether the component is running and stable."

// Addon is the Schema for the addons API
type Addon struct {
Expand Down
3 changes: 2 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.

26 changes: 25 additions & 1 deletion config/crd/bases/boundless.mirantis.com_addons.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ spec:
singular: addon
scope: Namespaced
versions:
- name: v1alpha1
- additionalPrinterColumns:
- description: Whether the component is running and stable.
jsonPath: .status.type
name: Status
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: Addon is the Schema for the addons API
Expand Down Expand Up @@ -79,6 +84,25 @@ spec:
type: object
status:
description: AddonStatus defines the observed state of Addon
properties:
lastTransitionTime:
description: The timestamp representing the start time for the current
status.
format: date-time
type: string
message:
description: Optionally, a detailed message providing additional context.
type: string
reason:
description: A brief reason explaining the condition.
type: string
type:
description: The type of condition. May be Available, Progressing,
or Degraded.
type: string
required:
- lastTransitionTime
- type
type: object
type: object
served: true
Expand Down
7 changes: 7 additions & 0 deletions config/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ metadata:
creationTimestamp: null
name: manager-role
rules:
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- apiGroups:
- boundless.mirantis.com
resources:
Expand Down
48 changes: 45 additions & 3 deletions controllers/addon_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package controllers
import (
"context"
"fmt"

"github.com/go-logr/logr"
"github.com/mirantis/boundless-operator/pkg/event"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
Expand All @@ -27,12 +30,14 @@ const (
// AddonReconciler reconciles a Addon object
type AddonReconciler struct {
client.Client
Scheme *runtime.Scheme
Scheme *runtime.Scheme
Recorder record.EventRecorder
}

//+kubebuilder:rbac:groups=boundless.mirantis.com,resources=addons,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=boundless.mirantis.com,resources=addons/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=boundless.mirantis.com,resources=addons/finalizers,verbs=update
//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand Down Expand Up @@ -63,6 +68,11 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
return ctrl.Result{}, err
}

// only update the status to progressing if its not already set to not trigger infinite reconciliations
if instance.Status.Type == "" {
r.updateStatus(ctx, logger, instance, boundlessv1alpha1.ComponentProgressing, "Creating Addon")
}

switch instance.Spec.Kind {
case kindChart:
chart := helm.Chart{
Expand Down Expand Up @@ -94,6 +104,7 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
if err := hc.DeleteHelmChart(chart, instance.Spec.Namespace); err != nil {
// if fail to delete the helm chart here, return with error
// so that it can be retried
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeWarning, event.ReasonFailedDelete, "Failed to Delete Chart Addon %s/%s: %s", instance.Spec.Namespace, instance.Name, err)
return ctrl.Result{}, err
}

Expand All @@ -108,11 +119,15 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
return ctrl.Result{}, nil
}

logger.Info("Creating Addon HelmChart resource", "Name", chart.Name, "Version", chart.Version)
if err := hc.CreateHelmChart(chart, instance.Spec.Namespace); err != nil {
logger.Error(err, "failed to install addon", "Name", chart.Name, "Version", chart.Version)
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeWarning, event.ReasonFailedCreate, "Failed to Create Chart Addon %s/%s : %s", instance.Spec.Namespace, instance.Name, err)
r.updateStatus(ctx, logger, instance, boundlessv1alpha1.ComponentDegraded, "Failed to Create HelmChart")
return ctrl.Result{Requeue: true}, err
}
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeNormal, event.ReasonSuccessfulCreate, "Created Chart Addon %s/%s", instance.Spec.Namespace, instance.Name)
r.updateStatus(ctx, logger, instance, boundlessv1alpha1.ComponentAvailable, "Chart Addon Created")

case kindManifest:
mc := manifest.NewManifestController(r.Client, logger)

Expand All @@ -133,6 +148,8 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
if err := mc.DeleteManifest(BoundlessNamespace, instance.Spec.Name, instance.Spec.Manifest.URL); err != nil {
// if fail to delete the manifest here, return with error
// so that it can be retried
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeWarning, event.ReasonFailedDelete, "Failed to Delete Manifest Addon %s/%s : %s", instance.Spec.Namespace, instance.Name, err)
r.updateStatus(ctx, logger, instance, boundlessv1alpha1.ComponentDegraded, "Failed to Cleanup Manifest")
return ctrl.Result{}, err
}

Expand All @@ -150,9 +167,14 @@ func (r *AddonReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
err = mc.CreateManifest(BoundlessNamespace, instance.Spec.Name, instance.Spec.Manifest.URL)
if err != nil {
logger.Error(err, "failed to install addon via manifest", "URL", instance.Spec.Manifest.URL)
r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeWarning, event.ReasonFailedCreate, "Failed to Create Manifest Addon %s/%s : %s", instance.Spec.Namespace, instance.Name, err)
r.updateStatus(ctx, logger, instance, boundlessv1alpha1.ComponentDegraded, "Failed to Create Manifest")
return ctrl.Result{Requeue: true}, err
}

r.Recorder.AnnotatedEventf(instance, map[string]string{event.AddonAnnotationKey: instance.Name}, event.TypeNormal, event.ReasonSuccessfulCreate, "Created Manifest Addon %s/%s", instance.Spec.Namespace, instance.Name)
r.updateStatus(ctx, logger, instance, boundlessv1alpha1.ComponentAvailable, "Manifest Addon Created")

default:
logger.Info("Unknown AddOn kind", "Kind", instance.Spec.Kind)
return ctrl.Result{Requeue: false}, fmt.Errorf("Unknown AddOn Kind: %w", err)
Expand All @@ -168,3 +190,23 @@ func (r *AddonReconciler) SetupWithManager(mgr ctrl.Manager) error {
For(&boundlessv1alpha1.Addon{}).
Complete(r)
}

func (r *AddonReconciler) updateStatus(ctx context.Context, logger logr.Logger, addon *boundlessv1alpha1.Addon, statusType boundlessv1alpha1.StatusConditionType, reason string) {
if addon.Status.Type == statusType && addon.Status.Reason == reason {
// avoid infinite reconciliation loops
logger.Info("No updates to status needed")
return
}

addon.Status.Type = statusType
addon.Status.Reason = reason
addon.Status.LastTransitionTime = metav1.Now()

logger.Info("Updating status for addon", "Name", addon.Name)
err := r.Status().Update(ctx, addon)
if err != nil {
// just log the error for now - don't stop reconciliation because we failed to update the status
logger.Error(err, "Failed to update status for addon", "Name", addon.Name)
}

}
13 changes: 12 additions & 1 deletion controllers/manifest_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package controllers

import (
"context"
"github.com/mirantis/boundless-operator/pkg/event"
"io"
"k8s.io/client-go/tools/record"
"net/http"
"strings"
"time"
Expand Down Expand Up @@ -32,7 +34,8 @@ import (
// ManifestReconciler reconciles a Manifest object
type ManifestReconciler struct {
client.Client
Scheme *runtime.Scheme
Scheme *runtime.Scheme
Recorder record.EventRecorder
}

// The checkSum map stores the checksum for each manifest.
Expand All @@ -43,6 +46,7 @@ var checkSum = make(map[string]string)
//+kubebuilder:rbac:groups=boundless.mirantis.com,resources=manifests,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=boundless.mirantis.com,resources=manifests/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=boundless.mirantis.com,resources=manifests/finalizers,verbs=update
//+kubebuilder:rbac:groups="",resources=events,verbs=create;patch

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
Expand Down Expand Up @@ -97,6 +101,7 @@ func (r *ManifestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c

if err := r.DeleteManifestObjects(req, ctx); err != nil {
logger.Error(err, "failed to remove finalizer")
r.Recorder.AnnotatedEventf(existing, map[string]string{event.AddonAnnotationKey: existing.Name}, event.TypeWarning, event.ReasonFailedCreate, "Failed to Delete Manifest %s/%s", existing.Namespace, existing.Name)
return ctrl.Result{}, err
}

Expand Down Expand Up @@ -140,6 +145,7 @@ func (r *ManifestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
resp, err := client.Get(existing.Spec.Url)
if err != nil {
logger.Error(err, "failed to fetch manifest file content for url: %s", existing.Spec.Url)
r.Recorder.AnnotatedEventf(existing, map[string]string{event.AddonAnnotationKey: existing.Name}, event.TypeWarning, event.ReasonFailedCreate, "Failed to Create Manifest %s/%s : %s", existing.Namespace, existing.Name, err.Error())
return ctrl.Result{}, err
}

Expand All @@ -150,19 +156,24 @@ func (r *ManifestReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
bodyBytes, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error(err, "failed to read http response body")
r.Recorder.AnnotatedEventf(existing, map[string]string{event.AddonAnnotationKey: existing.Name}, event.TypeWarning, event.ReasonFailedCreate, "Failed to Create Manifest %s/%s : %s", existing.Namespace, existing.Name, err.Error())
return ctrl.Result{}, err
}

} else {
logger.Error(err, "failure in http get request", "ResponseCode", resp.StatusCode)
r.Recorder.AnnotatedEventf(existing, map[string]string{event.AddonAnnotationKey: existing.Name}, event.TypeWarning, event.ReasonFailedCreate, "Failed to Create Manifest %s/%s : failure in http get request response code : %d", existing.Namespace, existing.Name, resp.StatusCode)
return ctrl.Result{}, err
}
err = r.CreateManifestObjects(req, bodyBytes, logger, ctx)
if err != nil {
logger.Error(err, "failed to create manifest objects", "ResponseCode", resp.StatusCode)
r.Recorder.AnnotatedEventf(existing, map[string]string{event.AddonAnnotationKey: existing.Name}, event.TypeWarning, event.ReasonFailedCreate, "Failed to Create Manifest %s/%s : %s", existing.Namespace, existing.Name, err)
return ctrl.Result{}, err
}

r.Recorder.AnnotatedEventf(existing, map[string]string{event.AddonAnnotationKey: existing.Name}, event.TypeNormal, event.ReasonSuccessfulCreate, "Created Manifest %s/%s", existing.Namespace, existing.Name)

return ctrl.Result{}, nil
}

Expand Down
10 changes: 6 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,9 @@ func main() {
}

if err = (&controllers.AddonReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("addon controller"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Addon")
os.Exit(1)
Expand All @@ -100,8 +101,9 @@ func main() {
os.Exit(1)
}
if err = (&controllers.ManifestReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Recorder: mgr.GetEventRecorderFor("manifest controller"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Manifest")
os.Exit(1)
Expand Down
15 changes: 15 additions & 0 deletions pkg/event/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package event

const AddonAnnotationKey = "Addon"

const ReasonSuccessfulCreate = "SuccessfulCreate"
const ReasonSuccessfulDelete = "SuccessfulDelete"
const ReasonFailedCreate = "FailedCreate"
const ReasonFailedDelete = "FailedDelete"

const TypeWarning = "Warning"
const TypeNormal = "Normal"

func createEvent() {

}

0 comments on commit fd8079e

Please sign in to comment.