Skip to content

Commit

Permalink
chore: migrate controllers to SSA
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgb committed Feb 2, 2024
1 parent a1e6de0 commit f8e9d36
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 118 deletions.
61 changes: 21 additions & 40 deletions controllers/namespace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@ import (
"context"
"fmt"
"path"
"reflect"

accuratev2alpha1 "github.com/cybozu-go/accurate/api/accurate/v2alpha1"
"github.com/cybozu-go/accurate/pkg/constants"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/types"
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
"k8s.io/client-go/util/workqueue"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down Expand Up @@ -85,18 +84,17 @@ func (r *NamespaceReconciler) reconcile(ctx context.Context, ns *corev1.Namespac
}

func (r *NamespaceReconciler) propagateMeta(ctx context.Context, ns, parent *corev1.Namespace) error {
orig := ns.DeepCopy()
labels := make(map[string]string)
annotations := make(map[string]string)

for k, v := range parent.Labels {
if ok := r.matchLabelKey(k); ok {
ns.Labels[k] = v
labels[k] = v
}
}
for k, v := range parent.Annotations {
if ok := r.matchAnnotationKey(k); ok {
if ns.Annotations == nil {
ns.Annotations = make(map[string]string)
}
ns.Annotations[k] = v
annotations[k] = v
}
}

Expand All @@ -110,24 +108,26 @@ func (r *NamespaceReconciler) propagateMeta(ctx context.Context, ns, parent *cor
} else {
for k, v := range subNS.Spec.Labels {
if ok := r.matchSubNamespaceLabelKey(k); ok {
ns.Labels[k] = v
labels[k] = v
}
}
for k, v := range subNS.Spec.Annotations {
if ok := r.matchSubNamespaceAnnotationKey(k); ok {
if ns.Annotations == nil {
ns.Annotations = make(map[string]string)
}
ns.Annotations[k] = v
annotations[k] = v
}
}
}
}

if !reflect.DeepEqual(ns.ObjectMeta, orig.ObjectMeta) {
if err := r.Update(ctx, ns); err != nil {
return fmt.Errorf("failed to propagate labels/annotations for namespace %s: %w", ns.Name, err)
}
ac := corev1ac.Namespace(ns.Name).
WithLabels(labels).
WithAnnotations(annotations)
ns, p, err := newNamespacePatch(ac)
if err != nil {
return err
}
if err := r.Patch(ctx, ns, p, fieldOwner, client.ForceOwnership); err != nil {
return fmt.Errorf("failed to propagate labels/annotations for namespace %s: %w", ns.Name, err)
}
return nil
}
Expand Down Expand Up @@ -231,31 +231,12 @@ func (r *NamespaceReconciler) propagateUpdate(ctx context.Context, res *unstruct
logger := log.FromContext(ctx)
gvk := res.GroupVersionKind()

c := &unstructured.Unstructured{}
c.SetGroupVersionKind(gvk)
err := r.Get(ctx, client.ObjectKey{Namespace: ns, Name: res.GetName()}, c)
if err != nil {
if !apierrors.IsNotFound(err) {
return err
}
if err := r.Create(ctx, cloneResource(res, ns)); err != nil {
return err
}
logger.Info("created a resource", "namespace", ns, "name", res.GetName(), "gvk", gvk.String())
return nil
}

c2 := cloneResource(res, ns)
if equality.Semantic.Equalities.DeepDerivative(c2, c) {
return nil
}

c2.SetResourceVersion(c.GetResourceVersion())
if err := r.Update(ctx, c2); err != nil {
return err
clone := cloneResource(res, ns)
if err := r.Patch(ctx, clone, applyPatch{clone}, fieldOwner, client.ForceOwnership); err != nil {
return fmt.Errorf("failed to apply %s/%s: %w", clone.GetNamespace(), clone.GetName(), err)
}

logger.Info("updated a resource", "namespace", ns, "name", res.GetName(), "gvk", gvk.String())
logger.Info("applied a resource", "namespace", ns, "name", res.GetName(), "gvk", gvk.String())
return nil
}

Expand Down
20 changes: 15 additions & 5 deletions controllers/namespace_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ var _ = Describe("Namespace controller", func() {

Eventually(komega.Object(ns1)).Should(HaveField("Labels", HaveKeyWithValue("foo.bar/baz", "123")))

By("removing a label of template namespace")
Expect(komega.Update(tmpl, func() {
delete(tmpl.Labels, "foo.bar/baz")
})()).To(Succeed())

Eventually(komega.Object(ns1)).Should(HaveField("Labels", Not(HaveKey("foo.bar/baz"))))

tmpl2 := &corev1.Namespace{}
tmpl2.Name = "tmpl2"
tmpl2.Labels = map[string]string{
Expand Down Expand Up @@ -229,6 +236,12 @@ var _ = Describe("Namespace controller", func() {
Eventually(komega.Get(pSec2)).Should(Succeed())

Eventually(komega.Object(ns1)).Should(HaveField("Labels", HaveKeyWithValue("team", "maneki")))
// assert that labels/annotations from previous template are removed
Expect(ns1.Labels).To(Not(HaveKey("foo.bar/baz")))
Expect(ns1.Labels).To(Not(HaveKey("memo")))
Expect(ns1.Annotations).To(Not(HaveKey("foo.bar/zot")))
Expect(ns1.Annotations).To(Not(HaveKey("memo")))
Expect(ns1.Annotations).To(Not(HaveKey("team")))

By("unsetting the template")
Expect(komega.Update(ns1, func() {
Expand Down Expand Up @@ -390,12 +403,9 @@ var _ = Describe("Namespace controller", func() {

By("deleting an annotation in root namespace")
Expect(komega.Update(root, func() {
delete(root.Labels, "baz.glob/c")
delete(root.Annotations, "baz.glob/c")
})()).To(Succeed())
// Cleaning up obsolete labels/annotations from sub-namespaces is currently unsupported
// See https://github.com/cybozu-go/accurate/issues/98
Consistently(komega.Object(sub1)).Should(HaveField("Annotations", HaveKey("baz.glob/c")))
//Eventually(komega.Object(sub1)).Should(HaveField("Annotations", Not(HaveKey("baz.glob/c"))))
Eventually(komega.Object(sub1)).Should(HaveField("Annotations", Not(HaveKey("baz.glob/c"))))

By("changing the parent of sub2")
root2 := &corev1.Namespace{}
Expand Down
47 changes: 11 additions & 36 deletions controllers/propagate.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import (
"github.com/cybozu-go/accurate/pkg/constants"
"github.com/cybozu-go/accurate/pkg/feature"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
Expand Down Expand Up @@ -265,18 +263,16 @@ func (r *PropagateController) propagateCreate(ctx context.Context, obj *unstruct

func (r *PropagateController) propagateUpdate(ctx context.Context, obj, parent *unstructured.Unstructured) error {
logger := log.FromContext(ctx)
name := obj.GetName()

if parent != nil {
clone := cloneResource(parent, obj.GetNamespace())
if !equality.Semantic.DeepDerivative(clone, obj) {
clone.SetResourceVersion(obj.GetResourceVersion())
if err := r.Update(ctx, clone); err != nil {
return fmt.Errorf("failed to update: %w", err)
}
logger.Info("updated", "from", parent.GetNamespace())
return nil
if err := r.Patch(ctx, clone, applyPatch{clone}, fieldOwner, client.ForceOwnership); err != nil {
return fmt.Errorf("failed to apply %s/%s: %w", clone.GetNamespace(), clone.GetName(), err)
}
logger.Info("applied", "from", parent.GetNamespace())

// update obj after apply from parent to prepare it for child propagation below
obj = clone
}

// propagate to child namespaces, if any.
Expand All @@ -286,33 +282,11 @@ func (r *PropagateController) propagateUpdate(ctx context.Context, obj, parent *
}

for _, child := range children.Items {
cres := r.res.DeepCopy()
err := r.Get(ctx, client.ObjectKey{Namespace: child.Name, Name: name}, cres)
if err != nil {
if !apierrors.IsNotFound(err) {
return fmt.Errorf("failed to lookup %s/%s: %w", child.Name, name, err)
}

clone := cloneResource(obj, child.Name)
if err := r.Create(ctx, clone); err != nil {
return fmt.Errorf("failed to create %s/%s: %w", child.Name, name, err)
}

logger.Info("created a child resource", "subnamespace", child.Name)
continue
}

clone := cloneResource(obj, child.Name)
if equality.Semantic.DeepDerivative(clone, cres) {
continue
}

clone.SetResourceVersion(cres.GetResourceVersion())
if err := r.Update(ctx, clone); err != nil {
return fmt.Errorf("failed to update %s/%s: %w", child.Name, name, err)
if err := r.Patch(ctx, clone, applyPatch{clone}, fieldOwner, client.ForceOwnership); err != nil {
return fmt.Errorf("failed to apply %s/%s: %w", clone.GetNamespace(), clone.GetName(), err)
}

logger.Info("updated a child resource", "subnamespace", child.Name)
logger.Info("applied a child resource", "subnamespace", child.Name)
}

return nil
Expand All @@ -328,7 +302,8 @@ func (r *PropagateController) checkController(ctx context.Context, obj *unstruct

logger := log.FromContext(ctx)
owner := &unstructured.Unstructured{}
owner.SetGroupVersionKind(schema.FromAPIVersionAndKind(cref.APIVersion, cref.Kind))
owner.SetAPIVersion(cref.APIVersion)
owner.SetKind(cref.Kind)
if err := r.reader.Get(ctx, client.ObjectKey{Namespace: obj.GetNamespace(), Name: cref.Name}, owner); err != nil {
if apierrors.IsNotFound(err) {
logger.Info("the controller object is not found", "gvk", owner.GroupVersionKind().String(), "owner", cref.Name)
Expand Down
44 changes: 44 additions & 0 deletions controllers/ssa_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package controllers

import (
"encoding/json"

accuratev2alpha1 "github.com/cybozu-go/accurate/api/accurate/v2alpha1"
accuratev2alpha1ac "github.com/cybozu-go/accurate/internal/applyconfigurations/accurate/v2alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
corev1ac "k8s.io/client-go/applyconfigurations/core/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

const (
fieldOwner client.FieldOwner = "accurate-controller"
)

func newSubNamespacePatch(ac *accuratev2alpha1ac.SubNamespaceApplyConfiguration) (*accuratev2alpha1.SubNamespace, client.Patch, error) {
sn := &accuratev2alpha1.SubNamespace{}
sn.Name = *ac.Name
sn.Namespace = *ac.Namespace

return sn, applyPatch{ac}, nil
}

func newNamespacePatch(ac *corev1ac.NamespaceApplyConfiguration) (*corev1.Namespace, client.Patch, error) {
ns := &corev1.Namespace{}
ns.Name = *ac.Name

return ns, applyPatch{ac}, nil
}

type applyPatch struct {
// must use any type until apply configurations implements a common interface
patch any
}

func (p applyPatch) Type() types.PatchType {
return types.ApplyPatchType
}

func (p applyPatch) Data(_ client.Object) ([]byte, error) {
return json.Marshal(p.patch)
}
41 changes: 5 additions & 36 deletions controllers/subnamespace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package controllers

import (
"context"
"encoding/json"
"fmt"
"time"

Expand All @@ -25,10 +24,6 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

const (
fieldOwner client.FieldOwner = "accurate-controller"
)

// SubNamespaceReconciler reconciles a SubNamespace object
type SubNamespaceReconciler struct {
client.Client
Expand Down Expand Up @@ -140,7 +135,11 @@ func (r *SubNamespaceReconciler) reconcileNS(ctx context.Context, sn *accuratev2
)
}

return r.patchSubNamespaceStatus(ctx, ac)
sn, p, err := newSubNamespacePatch(ac)
if err != nil {
return err
}
return r.Status().Patch(ctx, sn, p, fieldOwner, client.ForceOwnership)
}

// SetupWithManager sets up the controller with the Manager.
Expand Down Expand Up @@ -184,33 +183,3 @@ func newStatusCondition(existingConditions []metav1.Condition, newCondition meta

return newCondition
}

func (r *SubNamespaceReconciler) patchSubNamespaceStatus(ctx context.Context, ac *accuratev2alpha1ac.SubNamespaceApplyConfiguration) error {
sn := &accuratev2alpha1.SubNamespace{
ObjectMeta: metav1.ObjectMeta{
Name: *ac.Name,
Namespace: *ac.Namespace,
},
}

encodedPatch, err := json.Marshal(ac)
if err != nil {
return err
}

return r.Status().Patch(ctx, sn, applyPatch{encodedPatch}, fieldOwner, client.ForceOwnership)
}

type applyPatch struct {
patch []byte
}

var _ client.Patch = applyPatch{}

func (p applyPatch) Type() types.PatchType {
return types.ApplyPatchType
}

func (p applyPatch) Data(_ client.Object) ([]byte, error) {
return p.patch, nil
}
2 changes: 1 addition & 1 deletion docs/subnamespaces.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ metadata:
team: foo
```

Accurate only propagates labels/annotations that have been configured in that respect via the `labelKeys` and `annotationKeys` parameters in `config.yaml`. This prevents the propagation of labels/annotations that were not meant to do so. Accurate currently does not delete previously propagated labels when deleted from the parent namespace to prevent unintended deletions. Users are expected to manually delete labels/annotations that are no longer needed.
Accurate only propagates labels/annotations that have been configured in that respect via the `labelKeys` and `annotationKeys` parameters in `config.yaml`. This prevents the propagation of labels/annotations that were not meant to do so.

### Preparing resources for tenant users

Expand Down

0 comments on commit f8e9d36

Please sign in to comment.