Skip to content
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

[RFC] Add support for variable substitutions #253

Merged
merged 3 commits into from
Feb 12, 2021
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
18 changes: 18 additions & 0 deletions api/v1beta1/kustomization_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ type KustomizationSpec struct {
// +optional
Path string `json:"path,omitempty"`

// PostBuild describes which actions to perform on the YAML manifest
// generated by building the kustomize overlay.
// +optional
PostBuild *PostBuild `json:"postBuild,omitempty"`

// Prune enables garbage collection.
// +required
Prune bool `json:"prune"`
Expand Down Expand Up @@ -150,6 +155,19 @@ type KubeConfig struct {
SecretRef meta.LocalObjectReference `json:"secretRef,omitempty"`
}

// PostBuild describes which actions to perform on the YAML manifest
// generated by building the kustomize overlay.
type PostBuild struct {
// Substitute holds a map of key/value pairs.
// The variables defined in your YAML manifests
// that match any of the keys defined in the map
// will be substituted with the set value.
// Includes support for bash string replacement functions
// e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}.
// +optional
Substitute map[string]string `json:"substitute,omitempty"`
}

// KustomizationStatus defines the observed state of a kustomization.
type KustomizationStatus struct {
// ObservedGeneration is the last reconciled generation.
Expand Down
27 changes: 27 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,20 @@ spec:
for. Defaults to 'None', which translates to the root path of the
SourceRef.
type: string
postBuild:
description: PostBuild describes which actions to perform on the YAML
manifest generated by building the kustomize overlay.
properties:
substitute:
additionalProperties:
type: string
description: Substitute holds a map of key/value pairs. The variables
defined in your YAML manifests that match any of the keys defined
in the map will be substituted with the set value. Includes
support for bash string replacement functions e.g. ${var:=default},
${var:position} and ${var/substring/replacement}.
type: object
type: object
prune:
description: Prune enables garbage collection.
type: boolean
Expand Down
6 changes: 6 additions & 0 deletions controllers/kustomization_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,12 @@ func (r *KustomizationReconciler) build(kustomization kustomizev1.Kustomization,
return nil, fmt.Errorf("kustomize build failed: %w", err)
}

// run post-build actions
resources, err = runPostBuildActions(kustomization, resources)
if err != nil {
return nil, fmt.Errorf("post-build actions failed: %w", err)
}

manifestsFile := filepath.Join(dirPath, fmt.Sprintf("%s.yaml", kustomization.GetUID()))
if err := fs.WriteFile(manifestsFile, resources); err != nil {
return nil, err
Expand Down
11 changes: 11 additions & 0 deletions controllers/kustomization_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ var _ = Describe("KustomizationReconciler", func() {
Suspend: false,
Timeout: nil,
Validation: "client",
PostBuild: &kustomizev1.PostBuild{
Substitute: map[string]string{"region": "eu-central-1"},
},
HealthChecks: []meta.NamespacedObjectKindReference{
{
APIVersion: "v1",
Expand Down Expand Up @@ -205,6 +208,11 @@ var _ = Describe("KustomizationReconciler", func() {
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "test"}, ns)).Should(Succeed())
Expect(ns.Labels[fmt.Sprintf("%s/name", kustomizev1.GroupVersion.Group)]).To(Equal(kName.Name))
Expect(ns.Labels[fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group)]).To(Equal(kName.Namespace))

sa := &corev1.ServiceAccount{}
Expect(k8sClient.Get(context.Background(), types.NamespacedName{Name: "test", Namespace: "test"}, sa)).Should(Succeed())
Expect(sa.Labels["environment"]).To(Equal("dev"))
Expect(sa.Labels["region"]).To(Equal("eu-central-1"))
},
Entry("namespace-sa", refTestCase{
artifacts: []testserver.File{
Expand All @@ -225,6 +233,9 @@ kind: ServiceAccount
metadata:
name: test
namespace: test
labels:
environment: ${env:=dev}
region: "${region}"
`,
},
},
Expand Down
28 changes: 28 additions & 0 deletions controllers/kustomization_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"path/filepath"
"strings"

"github.com/drone/envsubst"
"sigs.k8s.io/kustomize/api/filesys"
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
"sigs.k8s.io/kustomize/api/konfig"
Expand Down Expand Up @@ -251,6 +252,12 @@ func (kg *KustomizeGenerator) checksum(dirPath string) (string, error) {
return "", fmt.Errorf("kustomize build failed: %w", err)
}

// run post-build actions
resources, err = runPostBuildActions(kg.kustomization, resources)
if err != nil {
return "", fmt.Errorf("post-build actions failed: %w", err)
}

return fmt.Sprintf("%x", sha1.Sum(resources)), nil
}

Expand Down Expand Up @@ -333,3 +340,24 @@ func buildKustomization(fs filesys.FileSystem, dirPath string) (resmap.ResMap, e
k := krusty.MakeKustomizer(fs, buildOptions)
return k.Run(dirPath)
}

// runPostBuildActions runs actions on the multi-doc YAML manifest generated by kustomize build
func runPostBuildActions(kustomization kustomizev1.Kustomization, manifests []byte) ([]byte, error) {
if kustomization.Spec.PostBuild == nil {
return manifests, nil
}

// run bash variable substitutions
vars := kustomization.Spec.PostBuild.Substitute
if vars != nil && len(vars) > 0 {
output, err := envsubst.Eval(string(manifests), func(s string) string {
return vars[s]
})
if err != nil {
return nil, fmt.Errorf("variable substitution failed: %w", err)
}
manifests = []byte(output)
}

return manifests, nil
}
69 changes: 69 additions & 0 deletions docs/api/kustomize.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,21 @@ Defaults to ‘None’, which translates to the root path of the SourceR
</tr>
<tr>
<td>
<code>postBuild</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1beta1.PostBuild">
PostBuild
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>PostBuild describes which actions to perform on the YAML manifest
generated by building the kustomize overlay.</p>
</td>
</tr>
<tr>
<td>
<code>prune</code><br>
<em>
bool
Expand Down Expand Up @@ -586,6 +601,21 @@ Defaults to &lsquo;None&rsquo;, which translates to the root path of the SourceR
</tr>
<tr>
<td>
<code>postBuild</code><br>
<em>
<a href="#kustomize.toolkit.fluxcd.io/v1beta1.PostBuild">
PostBuild
</a>
</em>
</td>
<td>
<em>(Optional)</em>
<p>PostBuild describes which actions to perform on the YAML manifest
generated by building the kustomize overlay.</p>
</td>
</tr>
<tr>
<td>
<code>prune</code><br>
<em>
bool
Expand Down Expand Up @@ -837,6 +867,45 @@ Snapshot
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1beta1.PostBuild">PostBuild
</h3>
<p>
(<em>Appears on:</em>
<a href="#kustomize.toolkit.fluxcd.io/v1beta1.KustomizationSpec">KustomizationSpec</a>)
</p>
<p>PostBuild describes which actions to perform on the YAML manifest
generated by building the kustomize overlay.</p>
<div class="md-typeset__scrollwrap">
<div class="md-typeset__table">
<table>
<thead>
<tr>
<th>Field</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code>substitute</code><br>
<em>
map[string]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Substitute holds a map of key/value pairs.
The variables defined in your YAML manifests
that match any of the keys defined in the map
will be substituted with the set value.
Includes support for bash string replacement functions
e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}.</p>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3 id="kustomize.toolkit.fluxcd.io/v1beta1.Snapshot">Snapshot
</h3>
<p>
Expand Down
1 change: 1 addition & 0 deletions docs/spec/v1beta1/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ of Kubernetes objects generated with Kustomize.
+ [Kustomization dependencies](kustomization.md#kustomization-dependencies)
+ [Role-based access control](kustomization.md#role-based-access-control)
+ [Override kustomize config](kustomization.md#override-kustomize-config)
+ [Variable substitution](kustomization.md#variable-substitution)
+ [Targeting remote clusters](kustomization.md#remote-clusters--cluster-api)
+ [Secrets decryption](kustomization.md#secrets-decryption)
+ [Status](kustomization.md#status)
Expand Down
86 changes: 85 additions & 1 deletion docs/spec/v1beta1/kustomization.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type KustomizationSpec struct {
// value to retry failures.
// +optional
RetryInterval *metav1.Duration `json:"retryInterval,omitempty"`

// The KubeConfig for reconciling the Kustomization on a remote cluster.
// When specified, KubeConfig takes precedence over ServiceAccountName.
// +optional
Expand All @@ -42,6 +42,11 @@ type KustomizationSpec struct {
// +optional
Path string `json:"path,omitempty"`

// PostBuild describes which actions to perform on the YAML manifest
// generated by building the kustomize overlay.
// +optional
PostBuild *PostBuild `json:"postBuild,omitempty"`

// Enables garbage collection.
// +required
Prune bool `json:"prune"`
Expand Down Expand Up @@ -147,6 +152,21 @@ type Image struct {
}
```

The post-build section defines which actions to perform on the YAML manifest after kustomize build:

```go
type PostBuild struct {
// Substitute holds a map of key/value pairs.
// The variables defined in your YAML manifests
// that match any of the keys defined in the map
// will be substituted with the set value.
// Includes support for bash string replacement functions
// e.g. ${var:=default}, ${var:position} and ${var/substring/replacement}.
// +optional
Substitute map[string]string `json:"substitute,omitempty"`
}
```

The status sub-resource records the result of the last reconciliation:

```go
Expand Down Expand Up @@ -663,6 +683,70 @@ spec:
digest: sha256:24a0c4b4a4c0eb97a1aabb8e29f18e917d05abfe1b7a7c07857230879ce7d3d3
```

## Variable substitution

With `spec.postBuild.substitute` you can provide a map of key/value pairs holding the
variables to be substituted in the final YAML manifest, after kustomize build.

This offers basic templating for your manifests including support
for [bash string replacement functions](https://github.com/drone/envsubst) e.g.:

- `${var:=default}`
- `${var:position}`
- `${var:position:length}`
- `${var/substring/replacement}`

Assuming you have manifests with the following variables:

```yaml
apiVersion: v1
kind: Namespace
metadata:
name: apps
labels:
environment: ${cluster_env:=dev}
region: "${cluster_region}"
```

You can specify the variables and their values in the Kustomization definition under
the `substitute` post build section:

````yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
name: apps
spec:
interval: 5m
path: "./apps/"
postBuild:
substitute:
cluster_env: "prod"
cluster_region: "eu-central-1"
````

Note that you should prefix the variables that get replaced by kustomize-controller
to avoid conflicts with any existing scripts embedded in ConfigMaps or container commands.

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):

```console
$ go install github.com/drone/envsubst/cmd/envsubst

$ export cluster_region=eu-central-1
$ kustomize build ./apps/ | $GOPATH/bin/envsubst
---
apiVersion: v1
kind: Namespace
metadata:
name: apps
labels:
environment: dev
region: eu-central-1
```

## Remote Clusters / Cluster-API

If the `kubeConfig` field is set, objects will be applied, health-checked, pruned, and deleted for the default
Expand Down
Loading