Skip to content

Document how to use numbers and booleans in post build substitutions #1129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion docs/spec/v1/kustomizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,12 +624,15 @@ kind: Kustomization
metadata:
name: apps
spec:
...
postBuild:
substitute:
var_substitution_enabled: "true"
```

**Note:** When using numbers or booleans as values for variables, they must be
enclosed in double quotes vars to be treated as strings, for more information see
[substitution of numbers and booleans](#post-build-substitution-of-numbers-and-booleans).

You can replicate the controller post-build substitutions locally using
[kustomize](https://github.com/kubernetes-sigs/kustomize)
and Drone's [envsubst](https://github.com/drone/envsubst):
Expand Down Expand Up @@ -1554,6 +1557,38 @@ secretGenerator:
- .dockerconfigjson=ghcr.dockerconfigjson.encrypted
```

### Post build substitution of numbers and booleans

When using [variable substitution](#post-build-variable-substitution) with values
that are numbers or booleans, the reconciliation may fail if the substitution
is for a field that must be of type string. To convert the number or boolean
to a string, you can wrap the variable with a double quotes var:

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: app
annotations:
id: ${quote}${id}${quote}
enabled: ${quote}${enabled}${quote}
```

Then in the Flux Kustomization, define the variables as:

```yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: app
spec:
postBuild:
substitute:
quote: '"' # double quote var
id: "123"
enabled: "true"
```

### Triggering a reconcile

To manually tell the kustomize-controller to reconcile a Kustomization outside
Expand Down
106 changes: 106 additions & 0 deletions internal/controller/kustomization_varsub_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,109 @@ metadata:
g.Expect(resultSA.Labels["shape"]).To(Equal("square"))
})
}

func TestKustomizationReconciler_VarsubNumberBool(t *testing.T) {
ctx := context.Background()

g := NewWithT(t)
id := "vars-" + randStringRunes(5)
revision := "v1.0.0/" + randStringRunes(7)

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 string) []testserver.File {
return []testserver.File{
{
Name: "service-account.yaml",
Body: fmt.Sprintf(`
apiVersion: v1
kind: ServiceAccount
metadata:
name: %[1]s
namespace: %[1]s
labels:
id: ${numberStr}
enabled: ${booleanStr}
annotations:
id: ${q}${number}${q}
enabled: ${q}${boolean}${q}
`, name),
},
}
}

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

repositoryName := types.NamespacedName{
Name: randStringRunes(5),
Namespace: id,
}

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

inputK := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: id,
Namespace: id,
},
Spec: kustomizev1.KustomizationSpec{
KubeConfig: &meta.KubeConfigReference{
SecretRef: meta.SecretKeyReference{
Name: "kubeconfig",
},
},
Interval: metav1.Duration{Duration: reconciliationInterval},
Path: "./",
Prune: true,
SourceRef: kustomizev1.CrossNamespaceSourceReference{
Kind: sourcev1.GitRepositoryKind,
Name: repositoryName.Name,
},
PostBuild: &kustomizev1.PostBuild{
Substitute: map[string]string{
"q": `"`,

"numberStr": "!!str 123",
"number": "123",
"booleanStr": "!!str true",
"boolean": "true",
},
},
Wait: true,
},
}
g.Expect(k8sClient.Create(ctx, inputK)).Should(Succeed())

resultSA := &corev1.ServiceAccount{}

ensureReconciles := func(nameSuffix string) {
t.Run("reconciles successfully"+nameSuffix, func(t *testing.T) {
g.Eventually(func() bool {
resultK := &kustomizev1.Kustomization{}
_ = k8sClient.Get(ctx, client.ObjectKeyFromObject(inputK), resultK)
for _, c := range resultK.Status.Conditions {
if c.Reason == kustomizev1.ReconciliationSucceededReason {
return true
}
}
return false
}, timeout, interval).Should(BeTrue())

g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: id, Namespace: id}, resultSA)).Should(Succeed())
})
}

ensureReconciles(" with optional ConfigMap")
t.Run("replaces vars from optional ConfigMap", func(t *testing.T) {
g.Expect(resultSA.Labels["id"]).To(Equal("123"))
g.Expect(resultSA.Annotations["id"]).To(Equal("123"))
g.Expect(resultSA.Labels["enabled"]).To(Equal("true"))
g.Expect(resultSA.Annotations["enabled"]).To(Equal("true"))
})
}
Loading