From d0ff0bfe35546ac715f7e0fc35ce8f7cf7bcc518 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Fri, 24 Jan 2025 08:06:43 +0000 Subject: [PATCH] [RFC-0009] Add CEL custom healthchecks Signed-off-by: Matheus Pimenta --- api/go.mod | 2 +- api/go.sum | 4 +- api/v1/kustomization_types.go | 5 ++ api/v1/zz_generated.deepcopy.go | 5 ++ ...mize.toolkit.fluxcd.io_kustomizations.yaml | 35 ++++++++ docs/api/v1/kustomize.md | 30 +++++++ docs/spec/v1/kustomizations.md | 61 +++++++++++++ go.mod | 7 +- go.sum | 14 ++- .../controller/kustomization_controller.go | 31 +++++-- .../controller/kustomization_fuzzer_test.go | 1 + .../controller/kustomization_wait_test.go | 85 +++++++++++++++++++ internal/controller/suite_test.go | 1 + main.go | 1 + 14 files changed, 268 insertions(+), 14 deletions(-) diff --git a/api/go.mod b/api/go.mod index 65bb9357..8d729c03 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,7 +3,7 @@ module github.com/fluxcd/kustomize-controller/api go 1.23.0 require ( - github.com/fluxcd/pkg/apis/kustomize v1.8.0 + github.com/fluxcd/pkg/apis/kustomize v1.8.1-0.20250123112748-c55030369b58 github.com/fluxcd/pkg/apis/meta v1.9.0 k8s.io/apiextensions-apiserver v0.32.0 k8s.io/apimachinery v0.32.0 diff --git a/api/go.sum b/api/go.sum index f94d2b82..553688c2 100644 --- a/api/go.sum +++ b/api/go.sum @@ -3,8 +3,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fluxcd/pkg/apis/kustomize v1.8.0 h1:HH6YRa3SMS72KK4cUyb9m5sK/dZH+Eti1qhjWDCgwKg= -github.com/fluxcd/pkg/apis/kustomize v1.8.0/go.mod h1:QCKIFj1ocdndaWSkrLs5JKvdGNYyTzQX1ZB3lYTwma0= +github.com/fluxcd/pkg/apis/kustomize v1.8.1-0.20250123112748-c55030369b58 h1:zhTfTbXKv+R+f1gwu2ekEu0b1Q5CjinCtj1prVEVbVo= +github.com/fluxcd/pkg/apis/kustomize v1.8.1-0.20250123112748-c55030369b58/go.mod h1:QCKIFj1ocdndaWSkrLs5JKvdGNYyTzQX1ZB3lYTwma0= github.com/fluxcd/pkg/apis/meta v1.9.0 h1:wPgm7bWNJZ/ImS5GqikOxt362IgLPFBG73dZ27uWRiQ= github.com/fluxcd/pkg/apis/meta v1.9.0/go.mod h1:pMea8eEZcsFSI7ngRnTHFtDZk2CEZGgtrueNgI6Iu70= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= diff --git a/api/v1/kustomization_types.go b/api/v1/kustomization_types.go index 8d54113a..3ec92635 100644 --- a/api/v1/kustomization_types.go +++ b/api/v1/kustomization_types.go @@ -179,6 +179,11 @@ type KustomizationSpec struct { // Components specifies relative paths to specifications of other Components. // +optional Components []string `json:"components,omitempty"` + + // HealthCheckExprs is a list of healthcheck expressions for evaluating the + // health of custom resources using Common Expression Language (CEL). + // +optional + HealthCheckExprs []kustomize.CustomHealthCheck `json:"healthCheckExprs,omitempty"` } // CommonMetadata defines the common labels and annotations. diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index b270e309..a4ccb2ff 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -212,6 +212,11 @@ func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.HealthCheckExprs != nil { + in, out := &in.HealthCheckExprs, &out.HealthCheckExprs + *out = make([]kustomize.CustomHealthCheck, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new KustomizationSpec. diff --git a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml index a47520a8..969764f7 100644 --- a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml +++ b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml @@ -136,6 +136,41 @@ spec: Force instructs the controller to recreate resources when patching fails due to an immutable field change. type: boolean + healthCheckExprs: + description: |- + HealthCheckExprs is a list of healthcheck expressions for evaluating the + health of custom resources using Common Expression Language (CEL). + items: + description: CustomHealthCheck defines the health check for custom + resources. + properties: + apiVersion: + description: APIVersion of the custom resource under evaluation. + type: string + current: + description: |- + Current is the CEL expression that determines if the status + of the custom resource has reached the desired state. + type: string + failed: + description: |- + Failed is the CEL expression that determines if the status + of the custom resource has failed to reach the desired state. + type: string + inProgress: + description: |- + InProgress is the CEL expression that determines if the status + of the custom resource has not yet reached the desired state. + type: string + kind: + description: Kind of the custom resource under evaluation. + type: string + required: + - apiVersion + - current + - kind + type: object + type: array healthChecks: description: A list of resources to be included in the health assessment. items: diff --git a/docs/api/v1/kustomize.md b/docs/api/v1/kustomize.md index 25e0e636..955f3f33 100644 --- a/docs/api/v1/kustomize.md +++ b/docs/api/v1/kustomize.md @@ -395,6 +395,21 @@ resources. When enabled, the HealthChecks are ignored. Defaults to false.

Components specifies relative paths to specifications of other Components.

+ + +healthCheckExprs
+ + +[]github.com/fluxcd/pkg/apis/kustomize.CustomHealthCheck + + + + +(Optional) +

HealthCheckExprs is a list of healthcheck expressions for evaluating the +health of custom resources using Common Expression Language (CEL).

+ + @@ -918,6 +933,21 @@ resources. When enabled, the HealthChecks are ignored. Defaults to false.

Components specifies relative paths to specifications of other Components.

+ + +healthCheckExprs
+ + +[]github.com/fluxcd/pkg/apis/kustomize.CustomHealthCheck + + + + +(Optional) +

HealthCheckExprs is a list of healthcheck expressions for evaluating the +health of custom resources using Common Expression Language (CEL).

+ + diff --git a/docs/spec/v1/kustomizations.md b/docs/spec/v1/kustomizations.md index 03ccb158..d65442bd 100644 --- a/docs/spec/v1/kustomizations.md +++ b/docs/spec/v1/kustomizations.md @@ -339,6 +339,67 @@ spec: If all the HelmRelease objects are successfully installed or upgraded, then the Kustomization will be marked as ready. +### Health check expressions + +`.spec.healthCheckExprs` can be used to define custom logic for performing +health checks on custom resources. This is done through Common Expression +Language (CEL) expressions. This field accepts a list of objects with the +following fields: + +- `apiVersion`: The API version of the custom resource. Required. +- `kind`: The kind of the custom resource. Required. +- `current`: A required CEL expression that returns `true` if the resource is ready. +- `inProgress`: An optional CEL expression that returns `true` if the resource + is still being reconciled. +- `failed`: An optional CEL expression that returns `true` if the resource + failed to reconcile. + +The controller will evaluate the expressions in the following order: + +1. `inProgress` if specified +2. `failed` if specified +3. `current` + +The first expression that evaluates to `true` will determine the health +status of the custom resource. + +For example, to define a set of health check expressions for the `SealedSecret` +custom resource: + +```yaml +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: sealed-secrets + namespace: flux-system +spec: + interval: 5m + path: ./path/to/sealed/secrets + prune: true + sourceRef: + kind: GitRepository + name: flux-system + timeout: 1m + wait: true # Tells the controller to wait for all resources to be ready by performing health checks. + healthCheckExprs: + - apiVersion: bitnami.com/v1alpha1 + kind: SealedSecret + inProgress: has(status.observedGeneration) && status.observedGeneration != metadata.generation + failed: status.conditions.filter(e, e.type == 'Synced').all(e, e.status == 'False') + current: status.conditions.filter(e, e.type == 'Synced').all(e, e.status == 'True') +``` + +A common error is writing expressions that reference fields that do not +exist in the custom resource. This will cause the controller to wait +for the resource to be ready until the timeout is reached. To avoid this, +make sure your CEL expressions are correct. The +[CEL Playground](https://playcel.undistro.io/) is a useful resource for +this task. The input passed to each expression is the custom resource +object itself. + +It's worth checking if [the library](/flux/cheatsheets/cel-healthchecks/) +has expressions for the custom resources you are using. + ### Wait `.spec.wait` is an optional boolean field to perform health checks for __all__ diff --git a/go.mod b/go.mod index e90db310..0eeaf1b3 100644 --- a/go.mod +++ b/go.mod @@ -20,11 +20,11 @@ require ( github.com/fluxcd/kustomize-controller/api v1.4.0 github.com/fluxcd/pkg/apis/acl v0.5.0 github.com/fluxcd/pkg/apis/event v0.15.0 - github.com/fluxcd/pkg/apis/kustomize v1.8.0 + github.com/fluxcd/pkg/apis/kustomize v1.8.1-0.20250123112748-c55030369b58 github.com/fluxcd/pkg/apis/meta v1.9.0 github.com/fluxcd/pkg/http/fetch v0.14.0 github.com/fluxcd/pkg/kustomize v1.15.0 - github.com/fluxcd/pkg/runtime v0.52.0 + github.com/fluxcd/pkg/runtime v0.52.1-0.20250124073354-c595d8c1eb90 github.com/fluxcd/pkg/ssa v0.43.0 github.com/fluxcd/pkg/tar v0.10.0 github.com/fluxcd/pkg/testserver v0.9.0 @@ -78,6 +78,7 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/aws/aws-sdk-go-v2 v1.32.6 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect github.com/aws/aws-sdk-go-v2/config v1.28.6 // indirect @@ -141,6 +142,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect + github.com/google/cel-go v0.22.0 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -199,6 +201,7 @@ require ( github.com/ryanuber/go-glob v1.0.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cobra v1.8.1 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect github.com/urfave/cli v1.22.16 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect diff --git a/go.sum b/go.sum index 895d5814..02738bb2 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,8 @@ github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEV github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= @@ -183,8 +185,8 @@ github.com/fluxcd/pkg/apis/acl v0.5.0 h1:+ykKezgerKUlZwSYFUy03lPMOIAyWlqvMNNLIWW github.com/fluxcd/pkg/apis/acl v0.5.0/go.mod h1:IVDZx3MAoDWjlLrJHMF9Z27huFuXAEQlnbWw0M6EcTs= github.com/fluxcd/pkg/apis/event v0.15.0 h1:k1suqIfVxnhEeKlGkvlHAbOYXjY8wRixT/OZcIuakqA= github.com/fluxcd/pkg/apis/event v0.15.0/go.mod h1:aRK2AONnjjSNW61B6Iy3SW4YHozACntnJeGm3fFqDqA= -github.com/fluxcd/pkg/apis/kustomize v1.8.0 h1:HH6YRa3SMS72KK4cUyb9m5sK/dZH+Eti1qhjWDCgwKg= -github.com/fluxcd/pkg/apis/kustomize v1.8.0/go.mod h1:QCKIFj1ocdndaWSkrLs5JKvdGNYyTzQX1ZB3lYTwma0= +github.com/fluxcd/pkg/apis/kustomize v1.8.1-0.20250123112748-c55030369b58 h1:zhTfTbXKv+R+f1gwu2ekEu0b1Q5CjinCtj1prVEVbVo= +github.com/fluxcd/pkg/apis/kustomize v1.8.1-0.20250123112748-c55030369b58/go.mod h1:QCKIFj1ocdndaWSkrLs5JKvdGNYyTzQX1ZB3lYTwma0= github.com/fluxcd/pkg/apis/meta v1.9.0 h1:wPgm7bWNJZ/ImS5GqikOxt362IgLPFBG73dZ27uWRiQ= github.com/fluxcd/pkg/apis/meta v1.9.0/go.mod h1:pMea8eEZcsFSI7ngRnTHFtDZk2CEZGgtrueNgI6Iu70= github.com/fluxcd/pkg/envsubst v1.3.0 h1:84Ain+8EBvyzu6y0FsKRwNsvaSiKuqhTqeh/4yoGFFU= @@ -193,8 +195,8 @@ github.com/fluxcd/pkg/http/fetch v0.14.0 h1:65iI38Vrl21v0YxT8IFCj/63I9/l43b7dPLa github.com/fluxcd/pkg/http/fetch v0.14.0/go.mod h1:/Ir27MZbgG11yN/npQwF32+oIETeJ+QdceoaxvvkLzQ= github.com/fluxcd/pkg/kustomize v1.15.0 h1:lII4FW9EJl0rI20dk+Glg5C2JZhP343FBov7HwW+SQo= github.com/fluxcd/pkg/kustomize v1.15.0/go.mod h1:e2SGi7cl28c9cnBVZ8YV8HAS4VBgUsiM6HMqv/AHJWQ= -github.com/fluxcd/pkg/runtime v0.52.0 h1:bgYgXURy94MNbHzthJ8SRZJxuZB1X4atj/LLeR6AgDk= -github.com/fluxcd/pkg/runtime v0.52.0/go.mod h1:66sowtjeLubCmwBTDC+2t41xgjs2eRlNzaWbPWN2nhk= +github.com/fluxcd/pkg/runtime v0.52.1-0.20250124073354-c595d8c1eb90 h1:awrUaChnkZkBSkP3osz3oDB2mL7NiDg7knUvR83lVfk= +github.com/fluxcd/pkg/runtime v0.52.1-0.20250124073354-c595d8c1eb90/go.mod h1:t67yJDmlO8PKVZ1AZsnUNUpL9Jguhnq2UBHKKbmM5oU= github.com/fluxcd/pkg/sourceignore v0.10.0 h1:z5Bhh0G990uLbwjKNj7SzYqbGkicpGcXxF/Z4ZSVB64= github.com/fluxcd/pkg/sourceignore v0.10.0/go.mod h1:d1d9hcFxf+grda6JL3k+mC09nVTtBb9kJVzQn6J77B0= github.com/fluxcd/pkg/ssa v0.43.0 h1:XmADD3C0erYZayKfGI0WTsMlW9TtS4bp5gy4Axo1dcA= @@ -264,6 +266,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.22.0 h1:b3FJZxpiv1vTMo2/5RDUqAHPxkT8mmMfJIrq1llbf7g= +github.com/google/cel-go v0.22.0/go.mod h1:BuznPXXfQDpXKWQ9sPW3TzlAJN5zzFe+i9tIs0yC4s8= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -430,6 +434,8 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/internal/controller/kustomization_controller.go b/internal/controller/kustomization_controller.go index 9a5f7f01..896c1ff6 100644 --- a/internal/controller/kustomization_controller.go +++ b/internal/controller/kustomization_controller.go @@ -56,6 +56,7 @@ import ( "github.com/fluxcd/pkg/http/fetch" generator "github.com/fluxcd/pkg/kustomize" "github.com/fluxcd/pkg/runtime/acl" + "github.com/fluxcd/pkg/runtime/cel" runtimeClient "github.com/fluxcd/pkg/runtime/client" "github.com/fluxcd/pkg/runtime/conditions" runtimeCtrl "github.com/fluxcd/pkg/runtime/controller" @@ -89,6 +90,7 @@ type KustomizationReconciler struct { artifactFetchRetries int requeueDependency time.Duration + Mapper apimeta.RESTMapper APIReader client.Reader StatusPoller *polling.StatusPoller PollingOpts polling.Options @@ -362,10 +364,11 @@ func (r *KustomizationReconciler) reconcile( } // Configure the Kubernetes client for impersonation. + statusPoller, pollingOpts := r.getPollerAndOptions(obj) impersonation := runtimeClient.NewImpersonator( r.Client, - r.StatusPoller, - r.PollingOpts, + statusPoller, + pollingOpts, obj.Spec.KubeConfig, r.KubeConfigOpts, r.DefaultServiceAccount, @@ -475,7 +478,7 @@ func (r *KustomizationReconciler) reconcile( conditions.MarkTrue(obj, meta.ReadyCondition, meta.ReconciliationSucceededReason, - fmt.Sprintf("Applied revision: %s", revision)) + "Applied revision: %s", revision) return nil } @@ -980,10 +983,11 @@ func (r *KustomizationReconciler) finalize(ctx context.Context, obj.Status.Inventory.Entries != nil { objects, _ := inventory.List(obj.Status.Inventory) + statusPoller, pollingOpts := r.getPollerAndOptions(obj) impersonation := runtimeClient.NewImpersonator( r.Client, - r.StatusPoller, - r.PollingOpts, + statusPoller, + pollingOpts, obj.Spec.KubeConfig, r.KubeConfigOpts, r.DefaultServiceAccount, @@ -1130,3 +1134,20 @@ func getOriginRevision(src sourcev1.Source) string { } return a.Metadata[OCIArtifactOriginRevisionAnnotation] } + +// getPollerAndOptions returns the status poller and polling options +// based on the healthcheck expressions defined in the Kustomization +// object spec. +func (r *KustomizationReconciler) getPollerAndOptions( + obj *kustomizev1.Kustomization) (*polling.StatusPoller, polling.Options) { + + poller := r.StatusPoller + opts := r.PollingOpts + + if hc := obj.Spec.HealthCheckExprs; len(hc) > 0 { + opts = cel.PollerWithCustomHealthChecks(hc, r.Mapper, opts) + poller = polling.NewStatusPoller(r.Client, r.Mapper, opts) + } + + return poller, opts +} diff --git a/internal/controller/kustomization_fuzzer_test.go b/internal/controller/kustomization_fuzzer_test.go index c4ffd3f7..523f9e6a 100644 --- a/internal/controller/kustomization_fuzzer_test.go +++ b/internal/controller/kustomization_fuzzer_test.go @@ -125,6 +125,7 @@ func Fuzz_Controllers(f *testing.F) { reconciler := &KustomizationReconciler{ ControllerName: controllerName, Client: testEnv, + Mapper: testEnv.GetRESTMapper(), } if err := (reconciler).SetupWithManager(ctx, testEnv, KustomizationReconcilerOptions{}); err != nil { panic(fmt.Sprintf("Failed to start GitRepositoryReconciler: %v", err)) diff --git a/internal/controller/kustomization_wait_test.go b/internal/controller/kustomization_wait_test.go index 78b7a30e..5129de63 100644 --- a/internal/controller/kustomization_wait_test.go +++ b/internal/controller/kustomization_wait_test.go @@ -29,6 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "github.com/fluxcd/pkg/apis/kustomize" "github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/runtime/conditions" "github.com/fluxcd/pkg/testserver" @@ -275,3 +276,87 @@ parameters: }, timeout, time.Second).Should(BeTrue()) }) } + +func TestKustomizationReconciler_WaitsForCustomHealthChecks(t *testing.T) { + g := NewWithT(t) + id := "wait-" + randStringRunes(5) + revision := "v1.0.0" + resultK := &kustomizev1.Kustomization{} + timeout := 60 * time.Second + + err := createNamespace(id) + g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace") + + manifests := func(name string) []testserver.File { + return []testserver.File{ + { + Name: "config.yaml", + Body: fmt.Sprintf(`--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: %[1]s +data: {} +`, name), + }, + } + } + + artifact, err := testServer.ArtifactFromFiles(manifests(id)) + g.Expect(err).NotTo(HaveOccurred()) + + repositoryName := types.NamespacedName{ + Name: fmt.Sprintf("wait-%s", randStringRunes(5)), + Namespace: id, + } + + err = applyGitRepository(repositoryName, artifact, revision) + g.Expect(err).NotTo(HaveOccurred()) + + kustomizationKey := types.NamespacedName{ + Name: fmt.Sprintf("wait-%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: "./", + SourceRef: kustomizev1.CrossNamespaceSourceReference{ + Name: repositoryName.Name, + Namespace: repositoryName.Namespace, + Kind: sourcev1.GitRepositoryKind, + }, + TargetNamespace: id, + Prune: true, + Timeout: &metav1.Duration{Duration: time.Second}, + Wait: true, + HealthCheckExprs: []kustomize.CustomHealthCheck{{ + APIVersion: "v1", + Kind: "ConfigMap", + HealthCheckExpressions: kustomize.HealthCheckExpressions{ + InProgress: "has(data.foo.bar)", + Current: "true", + }, + }}, + }, + } + + err = k8sClient.Create(context.Background(), kustomization) + g.Expect(err).NotTo(HaveOccurred()) + + g.Eventually(func() bool { + _ = k8sClient.Get(context.Background(), client.ObjectKeyFromObject(kustomization), resultK) + return conditions.IsFalse(resultK, meta.ReadyCondition) + }, timeout, time.Second).Should(BeTrue()) + logStatus(t, resultK) + + msg := conditions.GetMessage(resultK, meta.ReadyCondition) + g.Expect(msg). + To(ContainSubstring("timeout waiting for: [ConfigMap")) + g.Expect(msg). + To(ContainSubstring("failed to evaluate the CEL expression: no such attribute(s): data.foo.bar")) +} diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index bd4bbb6f..2f7053a7 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -176,6 +176,7 @@ func TestMain(m *testing.M) { reconciler = &KustomizationReconciler{ ControllerName: controllerName, Client: testEnv, + Mapper: testEnv.GetRESTMapper(), APIReader: testEnv, EventRecorder: testEnv.GetEventRecorderFor(controllerName), Metrics: testMetricsH, diff --git a/main.go b/main.go index a04bd5fb..65d7fec5 100644 --- a/main.go +++ b/main.go @@ -238,6 +238,7 @@ func main() { ControllerName: controllerName, DefaultServiceAccount: defaultServiceAccount, Client: mgr.GetClient(), + Mapper: mgr.GetRESTMapper(), APIReader: mgr.GetAPIReader(), Metrics: metricsH, EventRecorder: eventRecorder,