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 #72 from flanksource/issue-66-git-repository
Browse files Browse the repository at this point in the history
feat: add resourcesTemplate, optimize duck typing
  • Loading branch information
moshloop authored Oct 1, 2021
2 parents 99feeff + 7ebb503 commit c6c8c2e
Show file tree
Hide file tree
Showing 16 changed files with 592 additions and 106 deletions.
4 changes: 4 additions & 0 deletions api/v1/template_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ type TemplateSpec struct {
// +optional
Resources []runtime.RawExtension `json:"resources,omitempty"`

// Resources template is a template of resources to be created for each source object found
// +optional
ResourcesTemplate string `json:"resourcesTemplate,omitempty"`

// Patches is list of strategic merge patches to apply to to the targets
// Must specify at least resources or patches or both
// +optional
Expand Down
3 changes: 3 additions & 0 deletions config/crd/bases/templating.flanksource.com_templates.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ spec:
type: object
x-kubernetes-preserve-unknown-fields: true
type: array
resourcesTemplate:
description: Resources template is a template of resources to be created for each source object found
type: string
source:
description: Source selects objects on which to use as a templating object
properties:
Expand Down
4 changes: 4 additions & 0 deletions config/deploy/crd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,10 @@ spec:
type: object
x-kubernetes-preserve-unknown-fields: true
type: array
resourcesTemplate:
description: Resources template is a template of resources to be created
for each source object found
type: string
source:
description: Source selects objects on which to use as a templating
object
Expand Down
4 changes: 4 additions & 0 deletions config/deploy/operator.yml
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,10 @@ spec:
type: object
x-kubernetes-preserve-unknown-fields: true
type: array
resourcesTemplate:
description: Resources template is a template of resources to be created
for each source object found
type: string
source:
description: Source selects objects on which to use as a templating
object
Expand Down
137 changes: 119 additions & 18 deletions controllers/rest_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,29 @@ package controllers

import (
"context"
"encoding/json"
"reflect"
"strings"
"time"

"github.com/flanksource/commons/utils"
templatev1 "github.com/flanksource/template-operator/api/v1"
"github.com/flanksource/template-operator/k8s"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/metrics"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const (
objectModifiedError = "the object has been modified; please apply your changes to the latest version and try again"
)

var (
RESTDeleteFinalizer = "termination.flanksource.com/protect"
)
Expand Down Expand Up @@ -70,9 +81,11 @@ type RESTReconciler struct {
// +kubebuilder:rbac:groups="*",resources="*",verbs="*"

func (r *RESTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("rest", req.NamespacedName)
log := r.Log.WithValues("rest", req.NamespacedName, "requestID", utils.RandomString(10))
name := req.NamespacedName.String()

log.V(2).Info("Started reconciling")

rest := &templatev1.REST{}
if err := r.ControllerClient.Get(ctx, req.NamespacedName, rest); err != nil {
if kerrors.IsNotFound(err) {
Expand Down Expand Up @@ -107,52 +120,140 @@ func (r *RESTReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.
}

if rest.ObjectMeta.DeletionTimestamp != nil {
log.V(2).Info("Object marked as deleted")
if err = tm.Delete(ctx, rest); err != nil {
return reconcile.Result{}, err
}
finalizers := []string{}
for _, finalizer := range rest.ObjectMeta.Finalizers {
if finalizer != RESTDeleteFinalizer {
finalizers = append(finalizers, finalizer)
}
}
rest.ObjectMeta.Finalizers = finalizers
setRestStatus(rest)
if err := r.ControllerClient.Update(ctx, rest); err != nil {
log.Error(err, "failed to remove finalizer from object")
if err := r.removeFinalizers(rest); err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}

if !hasFinalizer {
log.V(2).Info("Setting finalizer")
rest.ObjectMeta.Finalizers = append(rest.ObjectMeta.Finalizers, RESTDeleteFinalizer)
if err := r.ControllerClient.Update(ctx, rest); err != nil {
log.Error(err, "failed to add finalizer to object")
return ctrl.Result{}, err
}
log.V(2).Info("Finalizer set, exiting reconcile")

return ctrl.Result{}, nil
}

err = tm.Update(ctx, rest)
statusUpdates, err := tm.Update(ctx, rest)
if err != nil {
log.Error(err, "Failed to run update REST")
incRESTFailed(name)
return reconcile.Result{}, err
}

if !reflect.DeepEqual(rest.Status, oldStatus) {
setRestStatus(rest)
if err := r.ControllerClient.Status().Update(ctx, rest); err != nil {
log.Error(err, "failed to update status")
return ctrl.Result{}, err
}
if err := r.updateStatus(ctx, rest, statusUpdates, oldStatus); err != nil {
return reconcile.Result{}, err
}

incRESTSuccess(name)
log.V(2).Info("Finished reconciling", "generation", rest.ObjectMeta.Generation)
return ctrl.Result{}, nil
}

func (r *RESTReconciler) updateStatus(ctx context.Context, rest *templatev1.REST, statusUpdates, oldStatus map[string]string) error {
backoff := wait.Backoff{
Duration: 50 * time.Millisecond,
Factor: 1.5,
Jitter: 2,
Steps: 10,
Cap: 5 * time.Second,
}
var err error

r.addStatusUpdates(rest, statusUpdates)

if reflect.DeepEqual(rest.Status, oldStatus) {
r.Log.V(2).Info("REST status did not change, skipping")
return nil
}

setRestStatus(rest)

js, _ := json.Marshal(rest.Status)
js2, _ := json.Marshal(oldStatus)
r.Log.V(2).Info("Checking:", "status", string(js), "oldStatus", string(js2))

for backoff.Steps > 0 {
js, err := json.Marshal(statusUpdates)
r.Log.V(2).Info("Updating status: setting", "statusUpdates", string(js), "err", err)
if err = r.ControllerClient.Status().Update(ctx, rest); err == nil {
return nil
}
sleepDuration := backoff.Step()
r.Log.Info("update status failed, sleeping", "duration", sleepDuration, "err", err)
time.Sleep(sleepDuration)
if strings.Contains(err.Error(), objectModifiedError) {
if err := r.ControllerClient.Get(context.Background(), types.NamespacedName{Name: rest.Name}, rest); err != nil {
return errors.Wrap(err, "failed to refetch object")
}
r.addStatusUpdates(rest, statusUpdates)
if reflect.DeepEqual(rest.Status, oldStatus) {
return nil
}
setRestStatus(rest)
}
}

return err
}

func (r *RESTReconciler) removeFinalizers(rest *templatev1.REST) error {
backoff := wait.Backoff{
Duration: 50 * time.Millisecond,
Factor: 1.5,
Jitter: 2,
Steps: 10,
Cap: 5 * time.Second,
}
var err error

rest.ObjectMeta.Finalizers = r.removeFinalizer(rest)

for backoff.Steps > 0 {
if err = r.ControllerClient.Update(context.Background(), rest); err == nil {
return nil
}
sleepDuration := backoff.Step()
r.Log.Info("remove finalizers failed, sleeping", "duration", sleepDuration, "err", err)
time.Sleep(sleepDuration)
if strings.Contains(err.Error(), objectModifiedError) {
if err := r.ControllerClient.Get(context.Background(), types.NamespacedName{Name: rest.Name}, rest); err != nil {
return errors.Wrap(err, "failed to refetch object")
}
rest.ObjectMeta.Finalizers = r.removeFinalizer(rest)
}
}

return nil
}

func (r *RESTReconciler) removeFinalizer(rest *templatev1.REST) []string {
finalizers := []string{}
for _, finalizer := range rest.ObjectMeta.Finalizers {
if finalizer != RESTDeleteFinalizer {
finalizers = append(finalizers, finalizer)
}
}
return finalizers
}

func (r *RESTReconciler) addStatusUpdates(rest *templatev1.REST, statusUpdates map[string]string) {
if rest.Status == nil {
rest.Status = map[string]string{}
}
for k, v := range statusUpdates {
rest.Status[k] = v
}
}

func (r *RESTReconciler) SetupWithManager(mgr ctrl.Manager) error {
r.ControllerClient = mgr.GetClient()
r.Events = mgr.GetEventRecorderFor("template-operator")
Expand Down
41 changes: 41 additions & 0 deletions example-public-apis.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
name: public-apis
namespace: default
spec:
interval: 5m
url: https://github.com/public-apis/public-apis
ref:
branch: master
---
apiVersion: templating.flanksource.com/v1
kind: Template
metadata:
name: git-repository
spec:
source:
gitRepository:
namespace: default
name: public-apis
glob: "/README.md"
resourcesTemplate: |
{{- range $table := (.content | parseMarkdownTables) }}
{{- range $row := $table.Rows }}
apiVersion: canaries.flanksource.com/v1
kind: Canary
metadata:
name: {{ (index $row 0) | strings.Slug | strings.ReplaceAll "_" "-" }}
namespace: default
labels:
app: public-apis
spec:
interval: 60
http:
- description: {{ index $row 1 | strings.ReplaceAll "\"" "'" | strings.ReplaceAll ":" "" }}
endpoint: "{{ index $row 0 }}"
responseCodes: [200, 201, 202, 301, 302]
thresholdMillis: 2000
---
{{- end }}
{{- end }}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ go 1.13

require (
github.com/flanksource/commons v1.5.6
github.com/flanksource/kommons v0.20.1
github.com/flanksource/kommons v0.23.0
github.com/go-logr/logr v0.3.0
github.com/go-logr/zapr v0.3.0 // indirect
github.com/go-openapi/jsonpointer v0.19.3
github.com/go-openapi/spec v0.19.3
github.com/gobwas/glob v0.2.3 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/onsi/ginkgo v1.14.1
github.com/onsi/gomega v1.10.2
github.com/pkg/errors v0.9.1
Expand Down
Loading

0 comments on commit c6c8c2e

Please sign in to comment.