Skip to content

Commit

Permalink
Allow disabling the reconciliation of in-cluster resources
Browse files Browse the repository at this point in the history
Introduce `kustomize.toolkit.fluxcd.io/reconcile` annotation. When set to `disabled`, the controller will no longer apply changes from source, nor it will prune the annotated resource.

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
  • Loading branch information
stefanprodan committed Nov 9, 2021
1 parent a38d14f commit 0ce7c12
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 8 deletions.
9 changes: 7 additions & 2 deletions controllers/kustomization_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,9 @@ func (r *KustomizationReconciler) apply(ctx context.Context, manager *ssa.Resour

applyOpts := ssa.DefaultApplyOptions()
applyOpts.Force = kustomization.Spec.Force
applyOpts.Exclusions = map[string]string{
fmt.Sprintf("%s/reconcile", kustomizev1.GroupVersion.Group): kustomizev1.DisabledValue,
}

// contains only CRDs and Namespaces
var stageOne []*unstructured.Unstructured
Expand Down Expand Up @@ -796,7 +799,8 @@ func (r *KustomizationReconciler) prune(ctx context.Context, manager *ssa.Resour
PropagationPolicy: metav1.DeletePropagationBackground,
Inclusions: manager.GetOwnerLabels(kustomization.Name, kustomization.Namespace),
Exclusions: map[string]string{
fmt.Sprintf("%s/prune", kustomizev1.GroupVersion.Group): kustomizev1.DisabledValue,
fmt.Sprintf("%s/prune", kustomizev1.GroupVersion.Group): kustomizev1.DisabledValue,
fmt.Sprintf("%s/reconcile", kustomizev1.GroupVersion.Group): kustomizev1.DisabledValue,
},
}

Expand Down Expand Up @@ -840,7 +844,8 @@ func (r *KustomizationReconciler) finalize(ctx context.Context, kustomization ku
PropagationPolicy: metav1.DeletePropagationBackground,
Inclusions: resourceManager.GetOwnerLabels(kustomization.Name, kustomization.Namespace),
Exclusions: map[string]string{
fmt.Sprintf("%s/prune", kustomizev1.GroupVersion.Group): kustomizev1.DisabledValue,
fmt.Sprintf("%s/prune", kustomizev1.GroupVersion.Group): kustomizev1.DisabledValue,
fmt.Sprintf("%s/reconcile", kustomizev1.GroupVersion.Group): kustomizev1.DisabledValue,
},
}

Expand Down
6 changes: 3 additions & 3 deletions controllers/kustomization_force_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,16 @@ package controllers
import (
"context"
"fmt"
"github.com/fluxcd/pkg/testserver"
corev1 "k8s.io/api/core/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
"testing"
"time"

kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down
274 changes: 274 additions & 0 deletions controllers/kustomization_inventory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
/*
Copyright 2021 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
"context"
"fmt"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/cli-utils/pkg/object"
"testing"
"time"

"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/testserver"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
. "github.com/onsi/gomega"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
)

func TestKustomizationReconciler_Inventory(t *testing.T) {
g := NewWithT(t)
id := "inv-" + randStringRunes(5)
revision := "v1.0.0"

err := createNamespace(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")

err = createKubeConfigSecret(id)
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")

manifests := func(name, data string) []testserver.File {
return []testserver.File{
{
Name: "config.yaml",
Body: fmt.Sprintf(`---
apiVersion: v1
kind: ConfigMap
metadata:
name: "%[1]s"
data:
key: "%[2]s"
---
apiVersion: v1
kind: Secret
metadata:
name: "%[1]s"
stringData:
key: "%[2]s"
`, name, data),
},
}
}

artifact, err := testServer.ArtifactFromFiles(manifests(id, id))
g.Expect(err).NotTo(HaveOccurred())

url := fmt.Sprintf("%s/%s", testServer.URL(), artifact)

repositoryName := types.NamespacedName{
Name: fmt.Sprintf("inv-%s", randStringRunes(5)),
Namespace: id,
}

err = applyGitRepository(repositoryName, url, revision, "")
g.Expect(err).NotTo(HaveOccurred())

kustomizationKey := types.NamespacedName{
Name: fmt.Sprintf("inv-%s", randStringRunes(5)),
Namespace: id,
}
kustomization := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: kustomizationKey.Name,
Namespace: kustomizationKey.Namespace,
},
Spec: kustomizev1.KustomizationSpec{
Interval: metav1.Duration{Duration: 2 * time.Minute},
Path: "./",
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
Name: "kubeconfig",
},
},
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Name: repositoryName.Name,
Namespace: repositoryName.Namespace,
Kind: sourcev1.GitRepositoryKind,
},
TargetNamespace: id,
Prune: true,
Timeout: &metav1.Duration{Duration: time.Second},
Wait: true,
},
}

g.Expect(k8sClient.Create(context.Background(), kustomization)).To(Succeed())

resultK := &kustomizev1.Kustomization{}

g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
ready := apimeta.IsStatusConditionTrue(resultK.Status.Conditions, meta.ReadyCondition)
return ready && resultK.Status.LastAppliedRevision == revision
}, timeout, time.Second).Should(BeTrue())

configMap := &corev1.ConfigMap{}
configMapName := types.NamespacedName{Name: id, Namespace: id}

secret := &corev1.Secret{
TypeMeta: metav1.TypeMeta{
Kind: "Secret",
APIVersion: "v1",
},
}
secretName := types.NamespacedName{Name: id, Namespace: id}

t.Run("creates resources", func(t *testing.T) {
g.Expect(k8sClient.Get(context.Background(), secretName, secret)).To(Succeed())
g.Expect(secret.Data["key"]).To(Equal([]byte(id)))

g.Expect(k8sClient.Get(context.Background(), configMapName, configMap)).To(Succeed())
g.Expect(configMap.Data["key"]).To(Equal(id))

g.Expect(resultK.Status.Inventory.Entries).Should(ConsistOf([]kustomizev1.ResourceRef{
{
ID: object.ObjMetadata{
Namespace: id,
Name: id,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Secret",
},
}.String(),
Version: "v1",
},
{
ID: object.ObjMetadata{
Namespace: id,
Name: id,
GroupKind: schema.GroupKind{
Group: "",
Kind: "ConfigMap",
},
}.String(),
Version: "v1",
},
}))
})

t.Run("ignores drift", func(t *testing.T) {
testRev := revision + "-1"
testVal := "test"

g.Expect(k8sClient.Get(context.Background(), configMapName, configMap)).To(Succeed())
configMapClone := configMap.DeepCopy()
configMapClone.Data["key"] = testVal
configMapClone.SetAnnotations(map[string]string{
fmt.Sprintf("%s/reconcile", kustomizev1.GroupVersion.Group): kustomizev1.DisabledValue,
})
g.Expect(k8sClient.Update(context.Background(), configMapClone)).To(Succeed())

err = applyGitRepository(repositoryName, url, testRev, "")
g.Expect(err).NotTo(HaveOccurred())

g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
ready := apimeta.IsStatusConditionTrue(resultK.Status.Conditions, meta.ReadyCondition)
return ready && resultK.Status.LastAppliedRevision == testRev
}, timeout, time.Second).Should(BeTrue())

g.Expect(k8sClient.Get(context.Background(), configMapName, configMap)).To(Succeed())
g.Expect(configMap.Data["key"]).To(Equal(testVal))
})

t.Run("corrects drift", func(t *testing.T) {
testRev := revision + "-2"

g.Expect(k8sClient.Get(context.Background(), configMapName, configMap)).To(Succeed())
configMapClone := configMap.DeepCopy()
configMapClone.SetAnnotations(map[string]string{
fmt.Sprintf("%s/reconcile", kustomizev1.GroupVersion.Group): "enabled",
})
g.Expect(k8sClient.Update(context.Background(), configMapClone)).To(Succeed())

err = applyGitRepository(repositoryName, url, testRev, "")
g.Expect(err).NotTo(HaveOccurred())

g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
ready := apimeta.IsStatusConditionTrue(resultK.Status.Conditions, meta.ReadyCondition)
return ready && resultK.Status.LastAppliedRevision == testRev
}, timeout, time.Second).Should(BeTrue())

g.Expect(k8sClient.Get(context.Background(), configMapName, configMap)).To(Succeed())
g.Expect(configMap.Data["key"]).To(Equal(id))
})

t.Run("renames resources", func(t *testing.T) {
testId := id + randStringRunes(5)
testRev := revision + "-3"

g.Expect(k8sClient.Get(context.Background(), configMapName, configMap)).To(Succeed())
configMapClone := configMap.DeepCopy()
configMapClone.SetAnnotations(map[string]string{
fmt.Sprintf("%s/reconcile", kustomizev1.GroupVersion.Group): kustomizev1.DisabledValue,
})
g.Expect(k8sClient.Update(context.Background(), configMapClone)).To(Succeed())

artifact, err := testServer.ArtifactFromFiles(manifests(testId, id))
g.Expect(err).NotTo(HaveOccurred())
url := fmt.Sprintf("%s/%s", testServer.URL(), artifact)
err = applyGitRepository(repositoryName, url, testRev, "")
g.Expect(err).NotTo(HaveOccurred())

g.Eventually(func() bool {
_ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK)
ready := apimeta.IsStatusConditionTrue(resultK.Status.Conditions, meta.ReadyCondition)
return ready && resultK.Status.LastAppliedRevision == testRev
}, timeout, time.Second).Should(BeTrue())

g.Expect(resultK.Status.Inventory.Entries).Should(ConsistOf([]kustomizev1.ResourceRef{
{
ID: object.ObjMetadata{
Namespace: id,
Name: testId,
GroupKind: schema.GroupKind{
Group: "",
Kind: "Secret",
},
}.String(),
Version: "v1",
},
{
ID: object.ObjMetadata{
Namespace: id,
Name: testId,
GroupKind: schema.GroupKind{
Group: "",
Kind: "ConfigMap",
},
}.String(),
Version: "v1",
},
}))

old := &corev1.Secret{}
err = k8sClient.Get(context.Background(), secretName, old)
g.Expect(apierrors.IsNotFound(err)).To(BeTrue())

g.Expect(k8sClient.Get(context.Background(), configMapName, configMap)).To(Succeed())
g.Expect(configMap.Data["key"]).To(Equal(id))
})
}
4 changes: 2 additions & 2 deletions controllers/kustomization_transformer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,18 @@ package controllers
import (
"context"
"fmt"
"github.com/fluxcd/pkg/apis/kustomize"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"strings"
"testing"
"time"

kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2"
"github.com/fluxcd/pkg/apis/kustomize"
"github.com/fluxcd/pkg/apis/meta"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
. "github.com/onsi/gomega"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
Expand Down
12 changes: 11 additions & 1 deletion docs/spec/v1beta2/kustomization.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ const (

On-demand execution example:

```bash
```sh
kubectl annotate --overwrite kustomization/podinfo reconcile.fluxcd.io/requestedAt="$(date +%s)"
```

Expand All @@ -334,6 +334,16 @@ kubectl get all --all-namespaces \
-l=kustomize.toolkit.fluxcd.io/namespace="<Kustomization namespace>"
```

You can configure the controller to ignore in-cluster resources by labeling or annotating them:

```sh
kubectl annotate service/podinfo kustomize.toolkit.fluxcd.io/reconcile=disabled
```

Note that when the `kustomize.toolkit.fluxcd.io/reconcile` annotation is set to `disabled`,
the controller will no longer apply changes from source, nor will it prune the resource.
To resume reconciliation, set the annotation to `enabled` or remove it.

## Garbage collection

To enable garbage collection, set `spec.prune` to `true`.
Expand Down

0 comments on commit 0ce7c12

Please sign in to comment.