From be7102bd42c306dc99473233f97282271e1427d6 Mon Sep 17 00:00:00 2001 From: Ahmad Nurus S Date: Wed, 1 Jan 2020 13:48:23 +0700 Subject: [PATCH] Add status for cr --- .../ghost.fossil.or.id_ghostapps_crd.yaml | 20 ++++++ pkg/apis/ghost/v1alpha1/ghostapp_types.go | 33 ++++++++- .../ghost/v1alpha1/zz_generated.openapi.go | 21 ++++++ .../ghostapp/ghostapp_controller.go | 71 +++++++++++++++++++ 4 files changed, 142 insertions(+), 3 deletions(-) diff --git a/deploy/crds/ghost.fossil.or.id_ghostapps_crd.yaml b/deploy/crds/ghost.fossil.or.id_ghostapps_crd.yaml index 35f2a11..6350631 100644 --- a/deploy/crds/ghost.fossil.or.id_ghostapps_crd.yaml +++ b/deploy/crds/ghost.fossil.or.id_ghostapps_crd.yaml @@ -3,6 +3,16 @@ kind: CustomResourceDefinition metadata: name: ghostapps.ghost.fossil.or.id spec: + additionalPrinterColumns: + - JSONPath: .status.replicas + name: replicas + type: string + - JSONPath: .status.phase + name: phase + type: string + - JSONPath: .metadata.creationTimestamp + name: age + type: date group: ghost.fossil.or.id names: kind: GhostApp @@ -113,6 +123,16 @@ spec: type: object status: description: GhostAppStatus defines the observed state of GhostApp + properties: + phase: + description: Represents the latest available observations of a ghostapp + current state. + type: string + reason: + type: string + replicas: + format: int32 + type: integer type: object type: object version: v1alpha1 diff --git a/pkg/apis/ghost/v1alpha1/ghostapp_types.go b/pkg/apis/ghost/v1alpha1/ghostapp_types.go index 89aa4a7..e7e7e81 100644 --- a/pkg/apis/ghost/v1alpha1/ghostapp_types.go +++ b/pkg/apis/ghost/v1alpha1/ghostapp_types.go @@ -89,12 +89,36 @@ type GhostAppSpec struct { Persistent GhostPersistentSpec `json:"persistent,omitempty"` } +// GhostAppPhaseType represents the current phase of GhostApp instances +// +k8s:openapi-gen=true +type GhostAppPhaseType string + +const ( + // GhostAppPhaseCreating indicates that the GhostApp is under provisioning + // +k8s:openapi-gen=true + GhostAppPhaseCreating GhostAppPhaseType = "Creating" + + // GhostAppPhaseRunning indicates that the GhostApp is ready and running + // +k8s:openapi-gen=true + GhostAppPhaseRunning GhostAppPhaseType = "Running" + + // GhostAppPhaseUpdating indicates that the GhostApp is under updating + // +k8s:openapi-gen=true + GhostAppPhaseUpdating GhostAppPhaseType = "Updating" + + // GhostAppPhaseFailure indicates that the GhostApp failed to be provisioned + // +k8s:openapi-gen=true + GhostAppPhaseFailure GhostAppPhaseType = "Failure" +) + // GhostAppStatus defines the observed state of GhostApp // +k8s:openapi-gen=true type GhostAppStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file - // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html + Replicas int32 `json:"replicas,omitempty"` + // Represents the latest available observations of a ghostapp current state. + Phase GhostAppPhaseType `json:"phase,omitempty"` + + Reason string `json:"reason,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -103,6 +127,9 @@ type GhostAppStatus struct { // +k8s:openapi-gen=true // +kubebuilder:subresource:status // +kubebuilder:resource:path=ghostapps,scope=Namespaced +// +kubebuilder:printcolumn:name="replicas",type="string",JSONPath=".status.replicas" +// +kubebuilder:printcolumn:name="phase",type="string",JSONPath=".status.phase" +// +kubebuilder:printcolumn:name="age",type="date",JSONPath=".metadata.creationTimestamp" type GhostApp struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` diff --git a/pkg/apis/ghost/v1alpha1/zz_generated.openapi.go b/pkg/apis/ghost/v1alpha1/zz_generated.openapi.go index 32e3e3e..cb9299c 100644 --- a/pkg/apis/ghost/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/ghost/v1alpha1/zz_generated.openapi.go @@ -108,6 +108,27 @@ func schema_pkg_apis_ghost_v1alpha1_GhostAppStatus(ref common.ReferenceCallback) SchemaProps: spec.SchemaProps{ Description: "GhostAppStatus defines the observed state of GhostApp", Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "replicas": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + "phase": { + SchemaProps: spec.SchemaProps{ + Description: "Represents the latest available observations of a ghostapp current state.", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, }, }, } diff --git a/pkg/controller/ghostapp/ghostapp_controller.go b/pkg/controller/ghostapp/ghostapp_controller.go index 9e059cd..c7dba5d 100644 --- a/pkg/controller/ghostapp/ghostapp_controller.go +++ b/pkg/controller/ghostapp/ghostapp_controller.go @@ -142,17 +142,32 @@ func (r *ReconcileGhostApp) Reconcile(request reconcile.Request) (reconcile.Resu reqLogger.Info("Creating a new ConfigMap for GhostApp Config", "ConfigMap.Namespace", configMap.GetNamespace(), "ConfigMap.Name", configMap.GetName()) // Set GhostApp instance as the owner and controller for this configmap if err := controllerutil.SetControllerReference(instance, configMap, r.scheme); err != nil { + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } // Create new configmap for ghostapp config if err := r.client.Create(context.TODO(), configMap); err != nil { reqLogger.Error(err, "Failed to create new ConfigMap for GhostApp Config", "ConfigMap.Namespace", configMap.GetNamespace(), "ConfigMap.Name", configMap.GetName()) + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } reqLogger.Info("Created a new ConfigMap for GhostApp Config", "ConfigMap.Namespace", configMap.GetNamespace(), "ConfigMap.Name", configMap.GetName()) return reconcile.Result{Requeue: true, RequeueAfter: 5 * time.Second}, nil } reqLogger.Error(err, "Failed to get ConfigMap") + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } @@ -169,17 +184,32 @@ func (r *ReconcileGhostApp) Reconcile(request reconcile.Request) (reconcile.Resu reqLogger.Info("Creating a new PersistentVolumeClaim for GhostApp Content", "PersistentVolumeClaim.Namespace", persistentVolumeClaim.GetNamespace(), "PersistentVolumeClaim.Name", persistentVolumeClaim.GetName()) // Set GhostApp instance as the owner and controller for this pvc if err := controllerutil.SetControllerReference(instance, persistentVolumeClaim, r.scheme); err != nil { + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } // Create new configmap for ghost config if err := r.client.Create(context.TODO(), persistentVolumeClaim); err != nil { reqLogger.Error(err, "Failed to create new PersistentVolumeClaim for GhostApp Content", "PersistentVolumeClaim.Namespace", persistentVolumeClaim.GetNamespace(), "PersistentVolumeClaim.Name", persistentVolumeClaim.GetName()) + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } reqLogger.Info("Created a new PersistentVolumeClaim for GhostApp Content", "PersistentVolumeClaim.Namespace", persistentVolumeClaim.GetNamespace(), "PersistentVolumeClaim.Name", persistentVolumeClaim.GetName()) return reconcile.Result{Requeue: true}, nil } reqLogger.Error(err, "Failed to get PersistentVolumeClaim") + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } } @@ -195,17 +225,32 @@ func (r *ReconcileGhostApp) Reconcile(request reconcile.Request) (reconcile.Resu reqLogger.Info("Creating a new Deployment", "Deployment.Namespace", newDeployment.GetNamespace(), "Deployment.Name", newDeployment.GetName()) // Set GhostApp instance as the owner and controller for this deployment if err := controllerutil.SetControllerReference(instance, newDeployment, r.scheme); err != nil { + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } // Create new deployment for instance if err := r.client.Create(context.TODO(), newDeployment); err != nil { reqLogger.Error(err, "Failed to create new Deployment", "Deployment.Namespace", newDeployment.GetNamespace(), "Deployment.Name", newDeployment.GetName()) + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } reqLogger.Info("Created a new Deployment", "Deployment.Namespace", newDeployment.GetNamespace(), "Deployment.Name", newDeployment.GetName()) return reconcile.Result{Requeue: true, RequeueAfter: 5 * time.Second}, nil } reqLogger.Error(err, "Failed to get Deployment") + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } @@ -226,7 +271,13 @@ func (r *ReconcileGhostApp) Reconcile(request reconcile.Request) (reconcile.Resu if !reflect.DeepEqual(currentDeployment, willUpdatedDeployment) { reqLogger.Info("Updating current Deployment", "Deployment.Namespace", willUpdatedDeployment.GetNamespace(), "Deployment.Name", willUpdatedDeployment.GetName()) + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseUpdating if err := r.client.Update(context.TODO(), willUpdatedDeployment); err != nil { + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } } @@ -240,11 +291,21 @@ func (r *ReconcileGhostApp) Reconcile(request reconcile.Request) (reconcile.Resu reqLogger.Info("Creating a new Service", "Service.Namespace", service.GetNamespace(), "Service.Name", service.GetName()) // Set GhostApp instance as the owner and controller for this service if err := controllerutil.SetControllerReference(instance, service, r.scheme); err != nil { + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } // Create new service for instance if err := r.client.Create(context.TODO(), service); err != nil { reqLogger.Error(err, "Failed to create new Service.", "Service.Namespace", service.GetNamespace(), "Service.Name", service.GetName()) + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } @@ -252,11 +313,21 @@ func (r *ReconcileGhostApp) Reconcile(request reconcile.Request) (reconcile.Resu return reconcile.Result{Requeue: true}, nil } reqLogger.Error(err, "Failed to get Service") + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseFailure + instance.Status.Reason = err.Error() + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, err } // All resource already exists - don't requeue reqLogger.Info("Skip reconcile: Resource already exists", "GhostApp.Namespace", instance.GetNamespace(), "GhostApp.Name", instance.GetName()) + instance.Status.Replicas = *instance.Spec.Replicas + instance.Status.Phase = ghostv1alpha1.GhostAppPhaseRunning + if err := r.client.Status().Update(context.TODO(), instance); err != nil { + return reconcile.Result{}, err + } return reconcile.Result{}, nil }