Skip to content

Commit

Permalink
feat(controller): downstream templating (#46)
Browse files Browse the repository at this point in the history
- [x] Changes covered by unit and/or e2e tests;
- [x] Documentation and examples updated;

## What this PR does / why we need it:
<!--
What code changes are made?
What problem does this PR addresses, or what feature this PR adds?
-->
<!-- REPLACE WITH CONTENT -->

## Which issue(s) this PR resolves:
<!--
Usage: `Resolves #<issue number>`, or `Resolves <link to the issue>`.
If PR is about `failing-tests`, please post the related tests in a
comment and do not use `Resolves`
-->
Resolves #13

Signed-off-by: Mateusz Urbanek <mateusz.urbanek.98@gmail.com>
  • Loading branch information
shanduur authored Dec 30, 2024
1 parent 8921801 commit 27b9c85
Show file tree
Hide file tree
Showing 9 changed files with 349 additions and 47 deletions.
34 changes: 18 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Image URL to use all building/pushing image targets
IMG ?= ghcr.io/anza-labs/scribe:main
PLATFORM ?= linux/$(shell go env GOARCH)
REPOSITORY ?= localhost:5005
TAG ?= dev-$(shell git describe --match='' --always --abbrev=6 --dirty)
IMG ?= $(REPOSITORY)/scribe:$(TAG)
PLATFORM ?= linux/$(shell go env GOARCH)
CHAINSAW_ARGS ?=

# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
Expand Down Expand Up @@ -108,11 +110,11 @@ endif

.PHONY: cluster
cluster: kind ctlptl
$(CTLPTL) apply -f hack/kind.yaml
@PATH=${LOCALBIN}:$(PATH) $(CTLPTL) apply -f hack/kind.yaml

.PHONY: cluster-reset
cluster-reset: kind ctlptl
$(CTLPTL) delete -f hack/kind.yaml
@PATH=${LOCALBIN}:$(PATH) $(CTLPTL) delete -f hack/kind.yaml

.PHONY: deploy
deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
Expand All @@ -131,21 +133,21 @@ $(LOCALBIN):
mkdir -p $(LOCALBIN)

## Tool Binaries
KUBECTL ?= kubectl
CHAINSAW ?= $(LOCALBIN)/chainsaw
KUBECTL ?= kubectl
CHAINSAW ?= $(LOCALBIN)/chainsaw
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen
CTLPTL ?= $(LOCALBIN)/ctlptl
KIND ?= $(LOCALBIN)/kind
KUSTOMIZE ?= $(LOCALBIN)/kustomize
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
CTLPTL ?= $(LOCALBIN)/ctlptl
KIND ?= $(LOCALBIN)/kind
KUSTOMIZE ?= $(LOCALBIN)/kustomize
GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint

## Tool Versions
CHAINSAW_VERSION ?= $(shell grep 'github.com/kyverno/chainsaw ' ./go.mod | cut -d ' ' -f 2)
CONTROLLER_TOOLS_VERSION ?= $(shell grep 'sigs.k8s.io/controller-tools ' ./go.mod | cut -d ' ' -f 2)
CTLPTL_VERSION ?= $(shell grep 'github.com/tilt-dev/ctlptl ' ./go.mod | cut -d ' ' -f 2)
GOLANGCI_LINT_VERSION ?= $(shell grep 'github.com/golangci/golangci-lint ' ./go.mod | cut -d ' ' -f 2)
KIND_VERSION ?= $(shell grep 'sigs.k8s.io/kind ' ./go.mod | cut -d ' ' -f 2)
KUSTOMIZE_VERSION ?= $(shell grep 'sigs.k8s.io/kustomize/kustomize/v5 ' ./go.mod | cut -d ' ' -f 2)
CHAINSAW_VERSION ?= $(shell grep 'github.com/kyverno/chainsaw ' ./go.mod | cut -d ' ' -f 2)
CONTROLLER_TOOLS_VERSION ?= $(shell grep 'sigs.k8s.io/controller-tools ' ./go.mod | cut -d ' ' -f 2)
CTLPTL_VERSION ?= $(shell grep 'github.com/tilt-dev/ctlptl ' ./go.mod | cut -d ' ' -f 2)
GOLANGCI_LINT_VERSION ?= $(shell grep 'github.com/golangci/golangci-lint ' ./go.mod | cut -d ' ' -f 2)
KIND_VERSION ?= $(shell grep 'sigs.k8s.io/kind ' ./go.mod | cut -d ' ' -f 2)
KUSTOMIZE_VERSION ?= $(shell grep 'sigs.k8s.io/kustomize/kustomize/v5 ' ./go.mod | cut -d ' ' -f 2)

.PHONY: chainsaw
chainsaw: $(CHAINSAW)-$(CHAINSAW_VERSION) ## Download chainsaw locally if necessary.
Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ metadata:
This ensures all resources in the `reloader-example` namespace will inherit the specified annotation, allowing for seamless automation and consistency across deployments.

In addition to propagating static annotations, Scribe also supports annotation templating. This allows for more dynamic and flexible annotations, where values are injected based on the metadata of the observed resources.

For example, you could set a template annotation that injects the resource's name into the annotation, like so:

```yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: reloader-example
annotations:
scribe.anza-labs.dev/annotations: |
object.name={{ .metadata.name }}
```

## Installation

[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/anza-labs)](https://artifacthub.io/packages/search?repo=anza-labs)
Expand Down
4 changes: 2 additions & 2 deletions config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: controller
newName: localhost:5005/manager
newTag: e2e
newName: localhost:5005/scribe
newTag: dev-892180-dirty
1 change: 1 addition & 0 deletions config/manager/manager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ spec:
- command:
- /manager
args:
- --v=4
- --leader-elect
- --health-probe-bind-address=:8081
- --config-path=/etc/scribe/config.yaml
Expand Down
43 changes: 39 additions & 4 deletions internal/controller/namespacescope.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ limitations under the License.
package controller

import (
"bytes"
"context"
"errors"
"fmt"
"maps"
"slices"
"strings"
"text/template"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -38,20 +41,36 @@ const (
lastAppliedAnnotations = "scribe.anza-labs.dev/last-applied-annotations"
)

var ErrSkipReconciliation = errors.New("skip reconciliation")

// lister is an interface that defines the listObjects method which returns a list of namespaced names.
type lister interface {
type getLister interface {
Get(context.Context, client.ObjectKey, client.Object, ...client.GetOption) error
listObjects(context.Context, string) ([]types.NamespacedName, error)
}

// mapFunc returns a function that triggers a reconcile request based on the provided lister.
// It logs the namespace details and returns reconcile requests for each object in the namespace.
func mapFunc(l lister) func(ctx context.Context, obj client.Object) []reconcile.Request {
func mapFunc(l getLister) func(ctx context.Context, obj client.Object) []reconcile.Request {
return func(ctx context.Context, obj client.Object) []reconcile.Request {
ns := &corev1.Namespace{}

log := log.FromContext(ctx,
"group_version_kind", (&corev1.Namespace{}).GroupVersionKind(),
"group_version_kind", ns.GroupVersionKind(),
"namespaced_name", klog.KObj(obj),
)

err := l.Get(ctx, types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()}, ns)
if err != nil {
log.V(0).Error(err, "Unable to get namespace to trigger reconcile")
return nil
}

if _, ok := ns.Annotations[annotations]; !ok {
log.V(3).Info("Skipping unmanaged namespace")
return nil
}

namespace := obj.GetName()

nns, err := l.listObjects(ctx, namespace)
Expand Down Expand Up @@ -93,16 +112,31 @@ func NewNamespaceScope(c client.Client, ns string) *NamespaceScope {
func (ss *NamespaceScope) UpdateAnnotations(
ctx context.Context,
objAnnotations map[string]string,
object map[string]any,
) (map[string]string, error) {
ns := &corev1.Namespace{}

if err := ss.Get(ctx, ss.namespace, ns); err != nil {
return nil, fmt.Errorf("unable to get namespace: %w", err)
}

tpl, err := template.New("").Parse(ns.Annotations[annotations])
if err != nil {
return nil, fmt.Errorf("failed to parse template: %w", err)
}

buf := new(bytes.Buffer)
err = tpl.Execute(buf, object)
if err != nil {
return nil, fmt.Errorf("failed to execute template: %w", err)
}

// Retrieve expected and last-applied annotations
expected := unmarshalAnnotations(ns.Annotations[annotations])
expected := unmarshalAnnotations(buf.String())
lastApplied := unmarshalAnnotations(objAnnotations[lastAppliedAnnotations])
if len(expected) == 0 && len(lastApplied) == 0 {
return nil, ErrSkipReconciliation
}

// Calculate the resulting annotations
results := make(map[string]string)
Expand All @@ -123,6 +157,7 @@ func (ss *NamespaceScope) UpdateAnnotations(
final := make(map[string]string)
maps.Copy(final, results)
delete(results, lastAppliedAnnotations)

final[lastAppliedAnnotations] = marshalAnnotations(results)

return final, nil
Expand Down
Loading

0 comments on commit 27b9c85

Please sign in to comment.