Skip to content

Commit b2daff1

Browse files
authored
Merge pull request #1129 from fluxcd/substitute-int-bool
Document how to use numbers and booleans in post build substitutions
2 parents f445fd2 + eaaa511 commit b2daff1

File tree

2 files changed

+142
-1
lines changed

2 files changed

+142
-1
lines changed

docs/spec/v1/kustomizations.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -624,12 +624,15 @@ kind: Kustomization
624624
metadata:
625625
name: apps
626626
spec:
627-
...
628627
postBuild:
629628
substitute:
630629
var_substitution_enabled: "true"
631630
```
632631

632+
**Note:** When using numbers or booleans as values for variables, they must be
633+
enclosed in double quotes vars to be treated as strings, for more information see
634+
[substitution of numbers and booleans](#post-build-substitution-of-numbers-and-booleans).
635+
633636
You can replicate the controller post-build substitutions locally using
634637
[kustomize](https://github.com/kubernetes-sigs/kustomize)
635638
and Drone's [envsubst](https://github.com/drone/envsubst):
@@ -1554,6 +1557,38 @@ secretGenerator:
15541557
- .dockerconfigjson=ghcr.dockerconfigjson.encrypted
15551558
```
15561559

1560+
### Post build substitution of numbers and booleans
1561+
1562+
When using [variable substitution](#post-build-variable-substitution) with values
1563+
that are numbers or booleans, the reconciliation may fail if the substitution
1564+
is for a field that must be of type string. To convert the number or boolean
1565+
to a string, you can wrap the variable with a double quotes var:
1566+
1567+
```yaml
1568+
apiVersion: v1
1569+
kind: ServiceAccount
1570+
metadata:
1571+
name: app
1572+
annotations:
1573+
id: ${quote}${id}${quote}
1574+
enabled: ${quote}${enabled}${quote}
1575+
```
1576+
1577+
Then in the Flux Kustomization, define the variables as:
1578+
1579+
```yaml
1580+
apiVersion: kustomize.toolkit.fluxcd.io/v1
1581+
kind: Kustomization
1582+
metadata:
1583+
name: app
1584+
spec:
1585+
postBuild:
1586+
substitute:
1587+
quote: '"' # double quote var
1588+
id: "123"
1589+
enabled: "true"
1590+
```
1591+
15571592
### Triggering a reconcile
15581593

15591594
To manually tell the kustomize-controller to reconcile a Kustomization outside

internal/controller/kustomization_varsub_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,3 +349,109 @@ metadata:
349349
g.Expect(resultSA.Labels["shape"]).To(Equal("square"))
350350
})
351351
}
352+
353+
func TestKustomizationReconciler_VarsubNumberBool(t *testing.T) {
354+
ctx := context.Background()
355+
356+
g := NewWithT(t)
357+
id := "vars-" + randStringRunes(5)
358+
revision := "v1.0.0/" + randStringRunes(7)
359+
360+
err := createNamespace(id)
361+
g.Expect(err).NotTo(HaveOccurred(), "failed to create test namespace")
362+
363+
err = createKubeConfigSecret(id)
364+
g.Expect(err).NotTo(HaveOccurred(), "failed to create kubeconfig secret")
365+
366+
manifests := func(name string) []testserver.File {
367+
return []testserver.File{
368+
{
369+
Name: "service-account.yaml",
370+
Body: fmt.Sprintf(`
371+
apiVersion: v1
372+
kind: ServiceAccount
373+
metadata:
374+
name: %[1]s
375+
namespace: %[1]s
376+
labels:
377+
id: ${numberStr}
378+
enabled: ${booleanStr}
379+
annotations:
380+
id: ${q}${number}${q}
381+
enabled: ${q}${boolean}${q}
382+
`, name),
383+
},
384+
}
385+
}
386+
387+
artifact, err := testServer.ArtifactFromFiles(manifests(id))
388+
g.Expect(err).NotTo(HaveOccurred())
389+
390+
repositoryName := types.NamespacedName{
391+
Name: randStringRunes(5),
392+
Namespace: id,
393+
}
394+
395+
err = applyGitRepository(repositoryName, artifact, revision)
396+
g.Expect(err).NotTo(HaveOccurred())
397+
398+
inputK := &kustomizev1.Kustomization{
399+
ObjectMeta: metav1.ObjectMeta{
400+
Name: id,
401+
Namespace: id,
402+
},
403+
Spec: kustomizev1.KustomizationSpec{
404+
KubeConfig: &meta.KubeConfigReference{
405+
SecretRef: meta.SecretKeyReference{
406+
Name: "kubeconfig",
407+
},
408+
},
409+
Interval: metav1.Duration{Duration: reconciliationInterval},
410+
Path: "./",
411+
Prune: true,
412+
SourceRef: kustomizev1.CrossNamespaceSourceReference{
413+
Kind: sourcev1.GitRepositoryKind,
414+
Name: repositoryName.Name,
415+
},
416+
PostBuild: &kustomizev1.PostBuild{
417+
Substitute: map[string]string{
418+
"q": `"`,
419+
420+
"numberStr": "!!str 123",
421+
"number": "123",
422+
"booleanStr": "!!str true",
423+
"boolean": "true",
424+
},
425+
},
426+
Wait: true,
427+
},
428+
}
429+
g.Expect(k8sClient.Create(ctx, inputK)).Should(Succeed())
430+
431+
resultSA := &corev1.ServiceAccount{}
432+
433+
ensureReconciles := func(nameSuffix string) {
434+
t.Run("reconciles successfully"+nameSuffix, func(t *testing.T) {
435+
g.Eventually(func() bool {
436+
resultK := &kustomizev1.Kustomization{}
437+
_ = k8sClient.Get(ctx, client.ObjectKeyFromObject(inputK), resultK)
438+
for _, c := range resultK.Status.Conditions {
439+
if c.Reason == kustomizev1.ReconciliationSucceededReason {
440+
return true
441+
}
442+
}
443+
return false
444+
}, timeout, interval).Should(BeTrue())
445+
446+
g.Expect(k8sClient.Get(ctx, types.NamespacedName{Name: id, Namespace: id}, resultSA)).Should(Succeed())
447+
})
448+
}
449+
450+
ensureReconciles(" with optional ConfigMap")
451+
t.Run("replaces vars from optional ConfigMap", func(t *testing.T) {
452+
g.Expect(resultSA.Labels["id"]).To(Equal("123"))
453+
g.Expect(resultSA.Annotations["id"]).To(Equal("123"))
454+
g.Expect(resultSA.Labels["enabled"]).To(Equal("true"))
455+
g.Expect(resultSA.Annotations["enabled"]).To(Equal("true"))
456+
})
457+
}

0 commit comments

Comments
 (0)