Skip to content
This repository has been archived by the owner on Apr 18, 2024. It is now read-only.

Commit

Permalink
Merge pull request #42 from flanksource/depends-on-attribute
Browse files Browse the repository at this point in the history
feat: add depends on attribute
  • Loading branch information
moshloop authored May 12, 2021
2 parents eeed9f6 + 09d244a commit d09cbe1
Show file tree
Hide file tree
Showing 10 changed files with 657 additions and 198 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ cover.out
/.bin
/.certs
/karina*

#intelij project files
.idea/
*.iml
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

# Image URL to use all building/pushing image targets
IMG ?= controller:latest
IMG ?= flanksource/template-operator:v1
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
CRD_OPTIONS ?= "crd:trivialVersions=true"
CRD_OPTIONS ?= "crd:trivialVersions=false"

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN))
Expand Down Expand Up @@ -83,7 +83,7 @@ ifeq (, $(shell which controller-gen))
CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\
cd $$CONTROLLER_GEN_TMP_DIR ;\
go mod init tmp ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.5 ;\
go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.0 ;\
rm -rf $$CONTROLLER_GEN_TMP_DIR ;\
}
CONTROLLER_GEN=$(GOBIN)/controller-gen
Expand Down
6 changes: 6 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

194 changes: 149 additions & 45 deletions config/deploy/crd.yml

Large diffs are not rendered by default.

194 changes: 149 additions & 45 deletions config/deploy/operator.yml

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions controllers/template_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package controllers

import (
"context"

"github.com/flanksource/kommons"
templatev1 "github.com/flanksource/template-operator/api/v1"
"github.com/flanksource/template-operator/k8s"
Expand Down Expand Up @@ -94,12 +93,13 @@ func (r *TemplateReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
incFailed(name)
return reconcile.Result{}, err
}
if err := tm.Run(ctx, template); err != nil {
result, err := tm.Run(ctx, template)
if err != nil {
incFailed(name)
return reconcile.Result{}, err
}
incSuccess(name)
return ctrl.Result{}, nil
return result, nil
}

func (r *TemplateReconciler) SetupWithManager(mgr ctrl.Manager) error {
Expand Down
144 changes: 105 additions & 39 deletions k8s/template_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import (
"context"
"encoding/json"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"regexp"
ctrl "sigs.k8s.io/controller-runtime"
"sort"
"strconv"
"strings"
"text/template"
"time"

"github.com/flanksource/kommons"
"github.com/flanksource/kommons/ktemplate"
Expand Down Expand Up @@ -154,11 +157,11 @@ func (tm *TemplateManager) selectResources(ctx context.Context, selector *templa
return sources, nil
}

func (tm *TemplateManager) Run(ctx context.Context, template *templatev1.Template) error {
func (tm *TemplateManager) Run(ctx context.Context, template *templatev1.Template) (result ctrl.Result, err error) {
tm.Log.Info("Reconciling", "template", template.Name)
sources, err := tm.selectResources(ctx, &template.Spec.Source)
if err != nil {
return err
return
}
tm.Log.Info("Found resources for template", "template", template.Name, "count", len(sources))

Expand All @@ -170,71 +173,79 @@ func (tm *TemplateManager) Run(ctx context.Context, template *templatev1.Templat
target, err = tm.PatchApplier.Apply(target, patch, PatchTypeYaml)
if err != nil {
tm.Events.Eventf(&source, v1.EventTypeWarning, "Failed", "Failed to apply patch")
return err
return
}
}
for _, patch := range template.Spec.JsonPatches {
target, err = tm.PatchApplier.Apply(target, patch.Patch, PatchTypeJSON)
if err != nil {
tm.Events.Eventf(&source, v1.EventTypeWarning, "Failed", "Failed to apply patch")
return err
return
}
}
if len(template.Spec.JsonPatches) > 0 || len(template.Spec.Patches) > 0 {
target = markApplied(template, target)
stripAnnotations(target)
if err := tm.Client.ApplyUnstructured(source.GetNamespace(), target); err != nil {
tm.Events.Eventf(&source, v1.EventTypeWarning, "Failed", "Failed to apply object")
return err
return result, err
}
}
}

isSourceReady := true

for _, item := range template.Spec.Resources {
objs, err := tm.getObjects(item.Raw, target.Object)
objs, err := tm.getObjectsFromResources(template.Spec.Resources, *target)
if err != nil {
return result, err
}
for _, obj := range objs {
ready, msg, err, rslt := tm.checkDependentObjects(&obj, objs)
if err != nil {
tm.Events.Eventf(&source, v1.EventTypeWarning, "Failed", "Failed to get objects")
return err
tm.Events.Eventf(&source, v1.EventTypeWarning, "Failed", "Failed to check dependent objects")
return result, err
}
if !ready {
result = rslt
tm.Log.V(2).Info("Skipping object", "kind", obj.GetKind(), "namespace", obj.GetNamespace(), "name", obj.GetName(), "obj", obj)
tm.Log.V(2).Info("Dependent object not ready", "message", msg)
continue
}

for _, obj := range objs {
// cross-namespace owner references are not allowed, so we create an annotation for tracking purposes only
if source.GetNamespace() == obj.GetNamespace() {
obj.SetOwnerReferences([]metav1.OwnerReference{{APIVersion: source.GetAPIVersion(), Kind: source.GetKind(), Name: source.GetName(), UID: source.GetUID()}})
} else {
crossNamespaceOwner(obj, source)
}
// cross-namespace owner references are not allowed, so we create an annotation for tracking purposes only
if source.GetNamespace() == obj.GetNamespace() {
obj.SetOwnerReferences([]metav1.OwnerReference{{APIVersion: source.GetAPIVersion(), Kind: source.GetKind(), Name: source.GetName(), UID: source.GetUID()}})
} else {
crossNamespaceOwner(&obj, source)
}

stripAnnotations(obj)
stripAnnotations(&obj)

if tm.Log.V(2).Enabled() {
tm.Log.V(2).Info("Applying", "kind", obj.GetKind(), "namespace", obj.GetNamespace(), "name", obj.GetName(), "obj", obj)
} else {
tm.Log.Info("Applying", "kind", obj.GetKind(), "namespace", obj.GetNamespace(), "name", obj.GetName())
}
if err := tm.Client.ApplyUnstructured(obj.GetNamespace(), obj); err != nil {
tm.Events.Eventf(&source, v1.EventTypeWarning, "Failed", "Failed to apply new resource kind=%s name=%s err=%v", obj.GetKind(), obj.GetName(), err)
return err
}
if tm.Log.V(2).Enabled() {
tm.Log.V(2).Info("Applying", "kind", obj.GetKind(), "namespace", obj.GetNamespace(), "name", obj.GetName(), "obj", obj)
} else {
tm.Log.Info("Applying", "kind", obj.GetKind(), "namespace", obj.GetNamespace(), "name", obj.GetName())
}
if err := tm.Client.ApplyUnstructured(obj.GetNamespace(), &obj); err != nil {
tm.Events.Eventf(&source, v1.EventTypeWarning, "Failed", "Failed to apply new resource kind=%s name=%s err=%v", obj.GetKind(), obj.GetName(), err)
return result, err
}

if isReady, msg, err := tm.isResourceReady(obj); err != nil {
return errors.Wrap(err, "failed to check if resource is ready")
} else if !isReady {
tm.Log.Info("resource is not ready", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace(), "message", msg)
isSourceReady = false
} else {
tm.Log.V(2).Info("resource is ready", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace(), "message", msg)
}
if isReady, msg, err := tm.isResourceReady(&obj); err != nil {
return result, errors.Wrap(err, "failed to check if resource is ready")
} else if !isReady {
tm.Log.V(2).Info("resource is not ready", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace(), "message", msg)
isSourceReady = false
} else {
tm.Log.V(2).Info("resource is ready", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace(), "message", msg)
}
}

if template.Spec.CopyToNamespaces != nil {
namespaces, err := tm.getNamespaces(ctx, *template.Spec.CopyToNamespaces)
if err != nil {
tm.Events.Eventf(&source, v1.EventTypeWarning, "Failed", "Failed to get namespaces")
return errors.Wrap(err, "failed to get namespaces")
return result, errors.Wrap(err, "failed to get namespaces")
}

for _, namespace := range namespaces {
Expand All @@ -253,11 +264,11 @@ func (tm *TemplateManager) Run(ctx context.Context, template *templatev1.Templat

if err := tm.Client.ApplyUnstructured(newResource.GetNamespace(), newResource); err != nil {
tm.Events.Eventf(&source, v1.EventTypeWarning, "Failed", "Failed to copy to namespace %s", namespace)
return err
return result, err
}

if isReady, msg, err := tm.isResourceReady(newResource); err != nil {
return errors.Wrap(err, "failed to check if resource is ready")
return result, errors.Wrap(err, "failed to check if resource is ready")
} else if !isReady {
tm.Log.Info("resource is not ready", "kind", newResource.GetKind(), "name", newResource.GetName(), "namespace", newResource.GetNamespace(), "message", msg)
isSourceReady = false
Expand All @@ -275,7 +286,7 @@ func (tm *TemplateManager) Run(ctx context.Context, template *templatev1.Templat
tm.Log.Error(err, "failed to set condition on resource", "kind", source.GetKind(), "name", source.GetName(), "namespace", source.GetNamespace(), "conditionValue", conditionValue)
}
}
return nil
return
}

func (tm *TemplateManager) Template(data []byte, vars interface{}) ([]byte, error) {
Expand Down Expand Up @@ -370,7 +381,6 @@ func (tm *TemplateManager) getObjects(rawItem []byte, target map[string]interfac
if err != nil {
return nil, errors.Wrap(err, "failed to template resources")
}

objs, err := kommons.GetUnstructuredObjects(data)
if err != nil {
return nil, errors.Wrap(err, "failed to get unstructured objects")
Expand Down Expand Up @@ -561,3 +571,59 @@ func alreadyApplied(template *templatev1.Template, item unstructured.Unstructure
func mkAnnotation(template *templatev1.Template) string {
return fmt.Sprintf(alreadyAppliedAnnotation, template.Namespace, template.Name)
}

// Returns object from the list of objects if the id is found and nil in case id is not found
func getObjectFromId(id string, objs []unstructured.Unstructured) (*unstructured.Unstructured, error) {
for _, obj := range objs {
if obj.Object["id"] != nil {
if obj.Object["id"].(string) == id {
return &obj, nil
}
}
}
return nil, fmt.Errorf("No object found with id: %v", id)
}

// Returns []string with IDs in case a object depends on any other object
func getDependsOnIds(obj *unstructured.Unstructured) []string {
if obj.Object["depends"] != nil {
depends := obj.Object["depends"].([]interface{})
s := make([]string, len(depends))
for i, v := range depends {
s[i] = fmt.Sprint(v)
}
return s
}
return nil
}

// checks if all the dependent obj are ready
func (tm *TemplateManager) checkDependentObjects(obj *unstructured.Unstructured, objs []unstructured.Unstructured) (bool, string, error, ctrl.Result) {
if obj.Object["depends"] != nil {
ids := getDependsOnIds(obj)
for _, id := range ids {
dependObj, err := getObjectFromId(id, objs)
if err != nil {
return false, "", err, ctrl.Result{}
}
if ready, msg, err := tm.isResourceReady(dependObj); !ready || err != nil {
return false, msg, err, ctrl.Result{Requeue: true, RequeueAfter: 2 * time.Minute}
}
}
}
return true, "object is ready", nil, ctrl.Result{}
}

func (tm *TemplateManager) getObjectsFromResources(resources []runtime.RawExtension, target unstructured.Unstructured) ([]unstructured.Unstructured, error) {
var objs []unstructured.Unstructured
for _, item := range resources {
obj, err := tm.getObjects(item.Raw, target.Object)
if err != nil {
return nil, err
}
for _, ob := range obj {
objs = append(objs, *ob)
}
}
return objs, nil
}
Loading

0 comments on commit d09cbe1

Please sign in to comment.