Allow disabling var substitution for certain resources
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
parent
0ac1f9e631
commit
401fec6c8d
|
|
@ -33,6 +33,7 @@ const (
|
||||||
KustomizationKind = "Kustomization"
|
KustomizationKind = "Kustomization"
|
||||||
KustomizationFinalizer = "finalizers.fluxcd.io"
|
KustomizationFinalizer = "finalizers.fluxcd.io"
|
||||||
MaxConditionMessageLength = 20000
|
MaxConditionMessageLength = 20000
|
||||||
|
DisabledValue = "disabled"
|
||||||
)
|
)
|
||||||
|
|
||||||
// KustomizationSpec defines the desired state of a kustomization.
|
// KustomizationSpec defines the desired state of a kustomization.
|
||||||
|
|
|
||||||
|
|
@ -517,9 +517,9 @@ func (r *KustomizationReconciler) build(ctx context.Context, kustomization kusto
|
||||||
return nil, fmt.Errorf("kustomize build failed: %w", err)
|
return nil, fmt.Errorf("kustomize build failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if resources are encrypted and decrypt them before generating the final YAML
|
for _, res := range m.Resources() {
|
||||||
if kustomization.Spec.Decryption != nil {
|
// check if resources are encrypted and decrypt them before generating the final YAML
|
||||||
for _, res := range m.Resources() {
|
if kustomization.Spec.Decryption != nil {
|
||||||
outRes, err := dec.Decrypt(res)
|
outRes, err := dec.Decrypt(res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("decryption failed for '%s': %w", res.GetName(), err)
|
return nil, fmt.Errorf("decryption failed for '%s': %w", res.GetName(), err)
|
||||||
|
|
@ -532,6 +532,21 @@ func (r *KustomizationReconciler) build(ctx context.Context, kustomization kusto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// run variable substitutions
|
||||||
|
if kustomization.Spec.PostBuild != nil {
|
||||||
|
outRes, err := substituteVariables(ctx, r.Client, kustomization, res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if outRes != nil {
|
||||||
|
_, err = m.Replace(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resources, err := m.AsYaml()
|
resources, err := m.AsYaml()
|
||||||
|
|
@ -539,12 +554,6 @@ func (r *KustomizationReconciler) build(ctx context.Context, kustomization kusto
|
||||||
return nil, fmt.Errorf("kustomize build failed: %w", err)
|
return nil, fmt.Errorf("kustomize build failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run post-build actions
|
|
||||||
resources, err = runPostBuildActions(ctx, r.Client, 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()))
|
manifestsFile := filepath.Join(dirPath, fmt.Sprintf("%s.yaml", kustomization.GetUID()))
|
||||||
if err := fs.WriteFile(manifestsFile, resources); err != nil {
|
if err := fs.WriteFile(manifestsFile, resources); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -144,9 +144,8 @@ func (kgc *KustomizeGarbageCollector) isStale(obj unstructured.Unstructured) boo
|
||||||
|
|
||||||
func (kgc *KustomizeGarbageCollector) shouldSkip(obj unstructured.Unstructured) bool {
|
func (kgc *KustomizeGarbageCollector) shouldSkip(obj unstructured.Unstructured) bool {
|
||||||
key := fmt.Sprintf("%s/prune", kustomizev1.GroupVersion.Group)
|
key := fmt.Sprintf("%s/prune", kustomizev1.GroupVersion.Group)
|
||||||
val := "disabled"
|
|
||||||
|
|
||||||
return obj.GetLabels()[key] == val || obj.GetAnnotations()[key] == val
|
return obj.GetLabels()[key] == kustomizev1.DisabledValue || obj.GetAnnotations()[key] == kustomizev1.DisabledValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (kgc *KustomizeGarbageCollector) matchingLabels(name, namespace string) client.MatchingLabels {
|
func (kgc *KustomizeGarbageCollector) matchingLabels(name, namespace string) client.MatchingLabels {
|
||||||
|
|
|
||||||
|
|
@ -22,14 +22,11 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/types"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/drone/envsubst"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
"sigs.k8s.io/kustomize/api/filesys"
|
"sigs.k8s.io/kustomize/api/filesys"
|
||||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||||
"sigs.k8s.io/kustomize/api/konfig"
|
"sigs.k8s.io/kustomize/api/konfig"
|
||||||
|
|
@ -253,17 +250,28 @@ func (kg *KustomizeGenerator) checksum(ctx context.Context, dirPath string) (str
|
||||||
return "", fmt.Errorf("kustomize build failed: %w", err)
|
return "", fmt.Errorf("kustomize build failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// run variable substitutions
|
||||||
|
if kg.kustomization.Spec.PostBuild != nil {
|
||||||
|
for _, res := range m.Resources() {
|
||||||
|
outRes, err := substituteVariables(ctx, kg.Client, kg.kustomization, res)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("var substitution failed for '%s': %w", res.GetName(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if outRes != nil {
|
||||||
|
_, err = m.Replace(res)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resources, err := m.AsYaml()
|
resources, err := m.AsYaml()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("kustomize build failed: %w", err)
|
return "", fmt.Errorf("kustomize build failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run post-build actions
|
|
||||||
resources, err = runPostBuildActions(ctx, kg.Client, kg.kustomization, resources)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("post-build actions failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%x", sha1.Sum(resources)), nil
|
return fmt.Sprintf("%x", sha1.Sum(resources)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -346,55 +354,3 @@ func buildKustomization(fs filesys.FileSystem, dirPath string) (resmap.ResMap, e
|
||||||
k := krusty.MakeKustomizer(fs, buildOptions)
|
k := krusty.MakeKustomizer(fs, buildOptions)
|
||||||
return k.Run(dirPath)
|
return k.Run(dirPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runPostBuildActions runs actions on the multi-doc YAML manifest generated by kustomize build
|
|
||||||
func runPostBuildActions(ctx context.Context, kubeClient client.Client, kustomization kustomizev1.Kustomization, manifests []byte) ([]byte, error) {
|
|
||||||
if kustomization.Spec.PostBuild == nil {
|
|
||||||
return manifests, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
vars := make(map[string]string)
|
|
||||||
|
|
||||||
// load vars from ConfigMaps and Secrets data keys
|
|
||||||
for _, reference := range kustomization.Spec.PostBuild.SubstituteFrom {
|
|
||||||
namespacedName := types.NamespacedName{Namespace: kustomization.Namespace, Name: reference.Name}
|
|
||||||
switch reference.Kind {
|
|
||||||
case "ConfigMap":
|
|
||||||
resource := &corev1.ConfigMap{}
|
|
||||||
if err := kubeClient.Get(ctx, namespacedName, resource); err != nil {
|
|
||||||
return nil, fmt.Errorf("substitute from 'ConfigMap/%s' error: %w", reference.Name, err)
|
|
||||||
}
|
|
||||||
for k, v := range resource.Data {
|
|
||||||
vars[k] = strings.Replace(v, "\n", "", -1)
|
|
||||||
}
|
|
||||||
case "Secret":
|
|
||||||
resource := &corev1.Secret{}
|
|
||||||
if err := kubeClient.Get(ctx, namespacedName, resource); err != nil {
|
|
||||||
return nil, fmt.Errorf("substitute from 'Secret/%s' error: %w", reference.Name, err)
|
|
||||||
}
|
|
||||||
for k, v := range resource.Data {
|
|
||||||
vars[k] = strings.Replace(string(v), "\n", "", -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load in-line vars (overrides the ones from resources)
|
|
||||||
if kustomization.Spec.PostBuild.Substitute != nil {
|
|
||||||
for k, v := range kustomization.Spec.PostBuild.Substitute {
|
|
||||||
vars[k] = strings.Replace(v, "\n", "", -1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run bash variable substitutions
|
|
||||||
if 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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/drone/envsubst"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
"sigs.k8s.io/kustomize/api/resource"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// substituteVariables replaces the vars with their values in the specified resource.
|
||||||
|
// If a resource is labeled or annotated with
|
||||||
|
// 'kustomize.toolkit.fluxcd.io/substitute: disabled' the substitution is skipped.
|
||||||
|
func substituteVariables(
|
||||||
|
ctx context.Context,
|
||||||
|
kubeClient client.Client,
|
||||||
|
kustomization kustomizev1.Kustomization,
|
||||||
|
res *resource.Resource) (*resource.Resource, error) {
|
||||||
|
resData, err := res.AsYAML()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
key := fmt.Sprintf("%s/substitute", kustomizev1.GroupVersion.Group)
|
||||||
|
|
||||||
|
if res.GetLabels()[key] == kustomizev1.DisabledValue || res.GetAnnotations()[key] == kustomizev1.DisabledValue {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := make(map[string]string)
|
||||||
|
|
||||||
|
// load vars from ConfigMaps and Secrets data keys
|
||||||
|
for _, reference := range kustomization.Spec.PostBuild.SubstituteFrom {
|
||||||
|
namespacedName := types.NamespacedName{Namespace: kustomization.Namespace, Name: reference.Name}
|
||||||
|
switch reference.Kind {
|
||||||
|
case "ConfigMap":
|
||||||
|
resource := &corev1.ConfigMap{}
|
||||||
|
if err := kubeClient.Get(ctx, namespacedName, resource); err != nil {
|
||||||
|
return nil, fmt.Errorf("substitute from 'ConfigMap/%s' error: %w", reference.Name, err)
|
||||||
|
}
|
||||||
|
for k, v := range resource.Data {
|
||||||
|
vars[k] = strings.Replace(v, "\n", "", -1)
|
||||||
|
}
|
||||||
|
case "Secret":
|
||||||
|
resource := &corev1.Secret{}
|
||||||
|
if err := kubeClient.Get(ctx, namespacedName, resource); err != nil {
|
||||||
|
return nil, fmt.Errorf("substitute from 'Secret/%s' error: %w", reference.Name, err)
|
||||||
|
}
|
||||||
|
for k, v := range resource.Data {
|
||||||
|
vars[k] = strings.Replace(string(v), "\n", "", -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load in-line vars (overrides the ones from resources)
|
||||||
|
if kustomization.Spec.PostBuild.Substitute != nil {
|
||||||
|
for k, v := range kustomization.Spec.PostBuild.Substitute {
|
||||||
|
vars[k] = strings.Replace(v, "\n", "", -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run bash variable substitutions
|
||||||
|
if len(vars) > 0 {
|
||||||
|
output, err := envsubst.Eval(string(resData), func(s string) string {
|
||||||
|
return vars[s]
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("variable substitution failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonData, err := yaml.YAMLToJSON([]byte(output))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("YAMLToJSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = res.UnmarshalJSON(jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("UnmarshalJSON: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
@ -736,9 +736,9 @@ spec:
|
||||||
cluster_region: "eu-central-1"
|
cluster_region: "eu-central-1"
|
||||||
substituteFrom:
|
substituteFrom:
|
||||||
- kind: ConfigMap
|
- kind: ConfigMap
|
||||||
name: cluster-config
|
name: cluster-vars
|
||||||
- kind: Secret
|
- kind: Secret
|
||||||
name: cluster-secret-config
|
name: cluster-secret-vars
|
||||||
```
|
```
|
||||||
|
|
||||||
The var values which are specified in-line with `substitute`
|
The var values which are specified in-line with `substitute`
|
||||||
|
|
@ -748,6 +748,13 @@ Note that if you want to avoid var substitutions in scripts embedded in ConfigMa
|
||||||
you must use the format `$var` instead of `${var}`. All the undefined variables in the format `${var}`
|
you must use the format `$var` instead of `${var}`. All the undefined variables in the format `${var}`
|
||||||
will be substituted with string empty, unless a default is provided e.g. `${var:=default}`.
|
will be substituted with string empty, unless a default is provided e.g. `${var:=default}`.
|
||||||
|
|
||||||
|
You can disable the variable substitution for certain resources by either
|
||||||
|
labeling or annotating them with:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
kustomize.toolkit.fluxcd.io/substitute: disabled
|
||||||
|
```
|
||||||
|
|
||||||
You can replicate the controller post-build substitutions locally using
|
You can replicate the controller post-build substitutions locally using
|
||||||
[kustomize](https://github.com/kubernetes-sigs/kustomize)
|
[kustomize](https://github.com/kubernetes-sigs/kustomize)
|
||||||
and Drone's [envsubst](https://github.com/drone/envsubst):
|
and Drone's [envsubst](https://github.com/drone/envsubst):
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue