From 0668f960bab152491293d62f13c25395495b8f9c Mon Sep 17 00:00:00 2001 From: raffaelespazzoli Date: Thu, 30 May 2019 15:35:34 -0500 Subject: [PATCH] finished integration --- cmd/manager/main.go | 3 +- .../eunomia_v1alpha1_gitopsconfig_crd.yaml | 11 ++ .../eunomia/v1alpha1/gitopsconfig_types.go | 10 ++ .../eunomia/v1alpha1/zz_generated.deepcopy.go | 3 +- .../eunomia/v1alpha1/zz_generated.openapi.go | 23 ++- .../gitopsconfig/gitopsconfig_controller.go | 152 ++++++------------ pkg/{util => controller/gitopsconfig}/util.go | 56 ++----- .../gitopsconfig}/util_test.go | 2 +- 8 files changed, 109 insertions(+), 151 deletions(-) rename pkg/{util => controller/gitopsconfig}/util.go (63%) rename pkg/{util => controller/gitopsconfig}/util_test.go (99%) diff --git a/cmd/manager/main.go b/cmd/manager/main.go index 960a4134..a5baccc5 100644 --- a/cmd/manager/main.go +++ b/cmd/manager/main.go @@ -28,7 +28,6 @@ import ( "github.com/KohlsTechnology/eunomia/pkg/controller" "github.com/KohlsTechnology/eunomia/pkg/controller/gitopsconfig" "github.com/KohlsTechnology/eunomia/pkg/handler" - "github.com/KohlsTechnology/eunomia/pkg/util" "github.com/operator-framework/operator-sdk/pkg/k8sutil" "github.com/operator-framework/operator-sdk/pkg/leader" @@ -95,7 +94,7 @@ func main() { log.Info("Error: CRONJOB_TEMPLATE must be set") os.Exit(1) } - util.InitializeTemplates(jt, cjt) + gitopsconfig.InitializeTemplates(jt, cjt) log.Info("Templates initialized correctly") // Get a config to talk to the apiserver diff --git a/deploy/crds/eunomia_v1alpha1_gitopsconfig_crd.yaml b/deploy/crds/eunomia_v1alpha1_gitopsconfig_crd.yaml index 098de0e5..a4cc2657 100644 --- a/deploy/crds/eunomia_v1alpha1_gitopsconfig_crd.yaml +++ b/deploy/crds/eunomia_v1alpha1_gitopsconfig_crd.yaml @@ -117,6 +117,17 @@ spec: type: array type: object status: + properties: + lastUpdate: + format: date-time + type: string + reason: + type: string + status: + enum: + - Success + - Failure + type: string type: object version: v1alpha1 versions: diff --git a/pkg/apis/eunomia/v1alpha1/gitopsconfig_types.go b/pkg/apis/eunomia/v1alpha1/gitopsconfig_types.go index 6d6412e9..b4c96edd 100644 --- a/pkg/apis/eunomia/v1alpha1/gitopsconfig_types.go +++ b/pkg/apis/eunomia/v1alpha1/gitopsconfig_types.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "github.com/redhat-cop/operator-utils/pkg/util/apis" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -62,6 +63,15 @@ type GitOpsConfigStatus 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.kubebuilder.io/beyond_basics/generating_crd.html + apis.ReconcileStatus `json:",inline"` +} + +func (m *GitOpsConfig) GetReconcileStatus() apis.ReconcileStatus { + return m.Status.ReconcileStatus +} + +func (m *GitOpsConfig) SetReconcileStatus(reconcileStatus apis.ReconcileStatus) { + m.Status.ReconcileStatus = reconcileStatus } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/eunomia/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/eunomia/v1alpha1/zz_generated.deepcopy.go index 81becb5d..84e22013 100644 --- a/pkg/apis/eunomia/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/eunomia/v1alpha1/zz_generated.deepcopy.go @@ -30,7 +30,7 @@ func (in *GitOpsConfig) DeepCopyInto(out *GitOpsConfig) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -111,6 +111,7 @@ func (in *GitOpsConfigSpec) DeepCopy() *GitOpsConfigSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitOpsConfigStatus) DeepCopyInto(out *GitOpsConfigStatus) { *out = *in + in.ReconcileStatus.DeepCopyInto(&out.ReconcileStatus) return } diff --git a/pkg/apis/eunomia/v1alpha1/zz_generated.openapi.go b/pkg/apis/eunomia/v1alpha1/zz_generated.openapi.go index c988cd2a..fef6ca15 100644 --- a/pkg/apis/eunomia/v1alpha1/zz_generated.openapi.go +++ b/pkg/apis/eunomia/v1alpha1/zz_generated.openapi.go @@ -134,9 +134,28 @@ func schema_pkg_apis_eunomia_v1alpha1_GitOpsConfigStatus(ref common.ReferenceCal Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ Description: "GitOpsConfigStatus defines the observed state of GitOpsConfig", - Properties: map[string]spec.Schema{}, + Properties: map[string]spec.Schema{ + "status": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "lastUpdate": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, }, }, - Dependencies: []string{}, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } diff --git a/pkg/controller/gitopsconfig/gitopsconfig_controller.go b/pkg/controller/gitopsconfig/gitopsconfig_controller.go index bbc83274..535a5e63 100644 --- a/pkg/controller/gitopsconfig/gitopsconfig_controller.go +++ b/pkg/controller/gitopsconfig/gitopsconfig_controller.go @@ -22,17 +22,15 @@ import ( "k8s.io/apimachinery/pkg/labels" + "text/template" "time" gitopsv1alpha1 "github.com/KohlsTechnology/eunomia/pkg/apis/eunomia/v1alpha1" - util "github.com/KohlsTechnology/eunomia/pkg/util" opsutil "github.com/redhat-cop/operator-utils/pkg/util" batchv1 "k8s.io/api/batch/v1" - batchv1beta1 "k8s.io/api/batch/v1beta1" corev1 "k8s.io/api/core/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" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" @@ -46,10 +44,12 @@ import ( ) var log = logf.Log.WithName("controller_gitopsconfig") +var jobTemplate *template.Template +var cronJobTemplate *template.Template const initLabel string = "gitopsconfig.eunomia.kohls.io/initialized" const kubeGitopsFinalizer string = "eunomia-finalizer" -const contrllerName = "gitopsconfig_controller" +const controllerName = "gitopsconfig_controller" // PushEvents channel on which we get the github webhook push events var PushEvents = make(chan event.GenericEvent) @@ -131,7 +131,7 @@ func (r *ReconcileGitOpsConfig) Reconcile(request reconcile.Request) (reconcile. // Fetch the GitOpsConfig instance instance := &gitopsv1alpha1.GitOpsConfig{} - err := r.client.Get(context.TODO(), request.NamespacedName, instance) + err := r.GetClient().Get(context.TODO(), request.NamespacedName, instance) if err != nil { if errors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. @@ -164,115 +164,78 @@ func (r *ReconcileGitOpsConfig) Reconcile(request reconcile.Request) (reconcile. return r.manageDeletion(instance) } - reqLogger.Info("Instance is initialized", "instance", instance.GetName()) if ContainsTrigger(instance, "Periodic") { reqLogger.Info("Instance has a periodic trigger, creating/updating cronjob", "instance", instance.GetName()) - _, err = r.createCronJob(instance) + err = r.createCronJob(instance) if err != nil { - reqLogger.Error(err, "error creating the cronjob, continuing...") + r.ManageError(instance, err) } } if ContainsTrigger(instance, "Change") || ContainsTrigger(instance, "Webhook") { reqLogger.Info("Instance has a change or Webhook trigger, creating job", "instance", instance.GetName()) - _, err = r.CreateJob("create", instance) + err = r.CreateJob("create", instance) if err != nil { - reqLogger.Error(err, "error creating the job, continuing...") + r.ManageError(instance, err) } } - return reconcile.Result{}, err -} - -// ContainsTrigger returns true if the passed instance contains the given trigger -func ContainsTrigger(instance *gitopsv1alpha1.GitOpsConfig, triggeType string) bool { - for _, trigger := range instance.Spec.Triggers { - if trigger.Type == triggeType { - return true - } - } - return false + return r.ManageSuccess(instance) } // CreateJob creates a new gitops job for the passed instance -func (r *ReconcileGitOpsConfig) CreateJob(jobtype string, instance *gitopsv1alpha1.GitOpsConfig) (reconcile.Result, error) { +func (r *ReconcileGitOpsConfig) CreateJob(jobtype string, instance *gitopsv1alpha1.GitOpsConfig) error { //TODO add logic to ignore if another job was created sooner than x (5 minutes?) time and it is still running. - mergedata := util.JobMergeData{ + mergedata := JobMergeData{ Config: *instance, Action: jobtype, } - job, err := util.CreateJob(mergedata) + job, err := opsutil.ProcessTemplate(mergedata, jobTemplate) if err != nil { log.Error(err, "unable to create job manifest from merge data", "mergedata", mergedata) - return reconcile.Result{}, err + return err } - err = controllerutil.SetControllerReference(instance, &job, r.scheme) + err = controllerutil.SetControllerReference(instance, job, r.GetScheme()) if err != nil { log.Error(err, "unable to the owner for job", "job", job) - return reconcile.Result{}, err + return err } - log.Info("Creating a new Job", "job.Namespace", job.Namespace, "job.Name", job.Name) - err = r.client.Create(context.TODO(), &job) + log.Info("Creating a new Job", "job.Namespace", job.GetNamespace(), "job.Name", job.GetName()) + err = r.GetClient().Create(context.TODO(), job) if err != nil { log.Error(err, "unable to create the job", "job", job) - return reconcile.Result{}, err + return err } - return reconcile.Result{}, nil + return nil } -func (r *ReconcileGitOpsConfig) createCronJob(instance *gitopsv1alpha1.GitOpsConfig) (reconcile.Result, error) { - mergedata := util.JobMergeData{ +func (r *ReconcileGitOpsConfig) createCronJob(instance *gitopsv1alpha1.GitOpsConfig) error { + mergedata := JobMergeData{ Config: *instance, Action: "create", } - var update bool - - cronjob, err := util.CreateCronJob(mergedata) + cronjob, err := opsutil.ProcessTemplate(mergedata, cronJobTemplate) if err != nil { log.Error(err, "unable to create cronjob manifest from merge data", "mergedata", mergedata) - return reconcile.Result{}, err - } - - pCronjob := batchv1beta1.CronJob{} - err = r.client.Get(context.TODO(), types.NamespacedName{Name: cronjob.GetName(), Namespace: cronjob.GetNamespace()}, &pCronjob) - if err != nil { - if errors.IsNotFound(err) { - update = false - } else { - // Error reading the object - requeue the request. - return reconcile.Result{}, err - } - } else { - update = true - } - - err = controllerutil.SetControllerReference(instance, &cronjob, r.scheme) - if err != nil { - log.Error(err, "unable to the owner for cronjob", "cronjob", cronjob) - return reconcile.Result{}, err - } - log.Info("Creating/updating CronJob", "cronjob.Namespace", cronjob.Namespace, "cronjob.Name", cronjob.Name) - if update { - err = r.client.Update(context.TODO(), &cronjob) - } else { - err = r.client.Create(context.TODO(), &cronjob) + return err } + err = r.CreateOrUpdateResource(instance, "", cronjob) if err != nil { log.Error(err, "unable to create/update the cronjob", "cronjob", cronjob) - return reconcile.Result{}, err + return err } - return reconcile.Result{}, nil + return nil } // GetAllGitOpsConfig retrieves all the gitops config in the cluster func (r *ReconcileGitOpsConfig) GetAllGitOpsConfig() (gitopsv1alpha1.GitOpsConfigList, error) { instanceList := &gitopsv1alpha1.GitOpsConfigList{} - err := r.client.List(context.TODO(), &client.ListOptions{}, instanceList) + err := r.GetClient().List(context.TODO(), &client.ListOptions{}, instanceList) if err != nil { log.Error(err, "unable to get the list of GitOpsCionfig") return *instanceList, err @@ -291,7 +254,7 @@ func (r *ReconcileGitOpsConfig) manageDeletion(instance *gitopsv1alpha1.GitOpsCo return reconcile.Result{}, err } // looking up all delete jobs - err = r.client.List(context.TODO(), &client.ListOptions{ + err = r.GetClient().List(context.TODO(), &client.ListOptions{ Namespace: instance.GetNamespace(), LabelSelector: selector, }, jobList) @@ -303,14 +266,14 @@ func (r *ReconcileGitOpsConfig) manageDeletion(instance *gitopsv1alpha1.GitOpsCo //filtering by those that are might have been created by this gitopsconfig // TODO better filter by owner reference for _, job := range jobList.Items { - if isOwner(instance, &job) && job.GetLabels()["action"] == "delete" { + if opsutil.IsOwner(instance, &job) && job.GetLabels()["action"] == "delete" { applicableJobList = append(applicableJobList, job) } } if len(applicableJobList) == 0 { // to avoid a deadlock situation let's check that the namespace in which we are is not being deleted ns := &corev1.Namespace{} - err := r.client.Get(context.TODO(), types.NamespacedName{ + err := r.GetClient().Get(context.TODO(), types.NamespacedName{ Name: instance.GetNamespace(), }, ns) if err != nil { @@ -320,15 +283,15 @@ func (r *ReconcileGitOpsConfig) manageDeletion(instance *gitopsv1alpha1.GitOpsCo if !ns.ObjectMeta.DeletionTimestamp.IsZero() { //namespace is being deleted // the best we can do in this situation is to let the instance be deleted and hope that this instance was creating objects only in this namespace - opsutil.RemoveFinalizer(instance,kubeGitopsFinalizer) - if err := r.client.Update(context.TODO(), instance); err != nil { + opsutil.RemoveFinalizer(instance, kubeGitopsFinalizer) + if err := r.GetClient().Update(context.TODO(), instance); err != nil { log.Error(err, "unable to create update instace to remove finalizers") return reconcile.Result{}, err } return reconcile.Result{}, nil } log.Info("Launching delete job for instance", "instance", instance.GetName()) - _, err = r.CreateJob("delete", instance) + err = r.CreateJob("delete", instance) if err != nil { log.Error(err, "unable to create deletion job") return reconcile.Result{}, err @@ -342,8 +305,8 @@ func (r *ReconcileGitOpsConfig) manageDeletion(instance *gitopsv1alpha1.GitOpsCo //There should be only one pending job job := applicableJobList[0] if job.Status.Succeeded > 0 { - opsutil.RemoveFinalizer(instance,kubeGitopsFinalizer) - if err := r.client.Update(context.TODO(), instance); err != nil { + opsutil.RemoveFinalizer(instance, kubeGitopsFinalizer) + if err := r.GetClient().Update(context.TODO(), instance); err != nil { log.Error(err, "unable to create update instace to remove finalizers") return reconcile.Result{}, err } @@ -359,19 +322,6 @@ func (r *ReconcileGitOpsConfig) manageDeletion(instance *gitopsv1alpha1.GitOpsCo return reconcile.Result{}, nil } -func isOwner(owner, owned metav1.Object) bool { - runtimeObj, ok := (owner).(runtime.Object) - if !ok { - return false - } - for _, ownerRef := range owned.GetOwnerReferences() { - if ownerRef.Name == owner.GetName() && ownerRef.UID == owner.GetUID() && ownerRef.Kind == runtimeObj.GetObjectKind().GroupVersionKind().Kind { - return true - } - } - return false -} - func (r *ReconcileGitOpsConfig) IsValid(obj metav1.Object) (bool, error) { instance, ok := obj.(*gitopsv1alpha1.GitOpsConfig) if !ok { @@ -385,54 +335,54 @@ func (r *ReconcileGitOpsConfig) IsValid(obj metav1.Object) (bool, error) { } func (r *ReconcileGitOpsConfig) IsInitialized(obj metav1.Object) bool { - instance, ok := obj.(*examplev1alpha1.MyCRD) + instance, ok := obj.(*gitopsv1alpha1.GitOpsConfig) if !ok { return false } - bool initialzied=true + var initialized = true if instance.Spec.TemplateSource.Ref == "" { instance.Spec.TemplateSource.Ref = "master" - initialized=false + initialized = false } if instance.Spec.TemplateSource.ContextDir == "" { instance.Spec.TemplateSource.ContextDir = "." - initialized=false + initialized = false } if instance.Spec.ParameterSource.URI == "" { instance.Spec.ParameterSource.URI = instance.Spec.TemplateSource.URI - initialized=false + initialized = false } if instance.Spec.ParameterSource.Ref == "" { instance.Spec.ParameterSource.Ref = "master" - initialized=false + initialized = false } if instance.Spec.ParameterSource.ContextDir == "" { instance.Spec.ParameterSource.ContextDir = "." - initialized=false + initialized = false } if instance.Spec.ServiceAccountRef == "" { instance.Spec.ServiceAccountRef = "default" - initialized=false + initialized = false } if instance.Spec.ResourceHandlingMode == "" { instance.Spec.ResourceHandlingMode = "CreateOrMerge" - initialized=false + initialized = false } if instance.Spec.ResourceDeletionMode == "" { instance.Spec.ResourceDeletionMode = "Delete" - initialized=false + initialized = false } - if !opsutil.HasFinalizer(instance.ObjectMeta.Finalizers, kubeGitopsFinalizer) && instance.Spec.ResourceDeletionMode != "Retain" { - opsutil.AddFinalizer(instance,kubeGitopsFinalizer) - initialized=false + if !opsutil.HasFinalizer(instance, kubeGitopsFinalizer) && instance.Spec.ResourceDeletionMode != "Retain" { + opsutil.AddFinalizer(instance, kubeGitopsFinalizer) + initialized = false } - return initialized -} \ No newline at end of file + return initialized +} diff --git a/pkg/util/util.go b/pkg/controller/gitopsconfig/util.go similarity index 63% rename from pkg/util/util.go rename to pkg/controller/gitopsconfig/util.go index 24c45b0e..dbd264a0 100644 --- a/pkg/util/util.go +++ b/pkg/controller/gitopsconfig/util.go @@ -14,25 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package gitopsconfig import ( - "bytes" "io/ioutil" "text/template" "github.com/KohlsTechnology/eunomia/pkg/apis/eunomia/v1alpha1" + gitopsv1alpha1 "github.com/KohlsTechnology/eunomia/pkg/apis/eunomia/v1alpha1" "github.com/dchest/uniuri" - "github.com/ghodss/yaml" - batch "k8s.io/api/batch/v1" - batchv1beta1 "k8s.io/api/batch/v1beta1" - logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" ) -var jobTemplate *template.Template -var cronJobTemplate *template.Template -var log = logf.Log.WithName("util") - // JobMergeData is the structs that will be used to merge with the job template type JobMergeData struct { Config v1alpha1.GitOpsConfig `json:"config,omitempty"` @@ -41,6 +33,16 @@ type JobMergeData struct { Action string `json:"action,omitempty"` } +// ContainsTrigger returns true if the passed instance contains the given trigger +func ContainsTrigger(instance *gitopsv1alpha1.GitOpsConfig, triggeType string) bool { + for _, trigger := range instance.Spec.Triggers { + if trigger.Type == triggeType { + return true + } + } + return false +} + // InitializeTemplates initializes the temolates needed by this controller, it must be called at controller boot time func InitializeTemplates(jobTempateFileName string, cronJobTemplateFilename string) error { text, err := ioutil.ReadFile(jobTempateFileName) @@ -83,37 +85,3 @@ func InitializeTemplates(jobTempateFileName string, cronJobTemplateFilename stri } return nil } - -// CreateJob returns a Job type from a template merge data -func CreateJob(jobmergedata JobMergeData) (batch.Job, error) { - job := batch.Job{} - var b bytes.Buffer - err := jobTemplate.Execute(&b, &jobmergedata) - if err != nil { - log.Error(err, "Error executing template") - return job, err - } - err = yaml.Unmarshal(b.Bytes(), &job) - if err != nil { - log.Error(err, "Error unmashalling the job manifest", "manifest", string(b.Bytes())) - return job, err - } - return job, err -} - -// CreateCronJob returns a Job type from a template merge data -func CreateCronJob(jobmergedata JobMergeData) (batchv1beta1.CronJob, error) { - cronjob := batchv1beta1.CronJob{} - var b bytes.Buffer - err := cronJobTemplate.Execute(&b, &jobmergedata) - if err != nil { - log.Error(err, "Error executing template") - return cronjob, err - } - err = yaml.Unmarshal(b.Bytes(), &cronjob) - if err != nil { - log.Error(err, "Error unmashalling the job manifest", "manifest", string(b.Bytes())) - return cronjob, err - } - return cronjob, err -} diff --git a/pkg/util/util_test.go b/pkg/controller/gitopsconfig/util_test.go similarity index 99% rename from pkg/util/util_test.go rename to pkg/controller/gitopsconfig/util_test.go index 2f6ccab0..ef295f2c 100644 --- a/pkg/util/util_test.go +++ b/pkg/controller/gitopsconfig/util_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package gitopsconfig import ( "bytes"