Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: fluxcd/kustomize-controller
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.20.2
Choose a base ref
...
head repository: fluxcd/kustomize-controller
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.21.0
Choose a head ref
  • 7 commits
  • 10 files changed
  • 2 contributors

Commits on Feb 13, 2022

  1. Use strings.ReplaceAll function when applicable

    Signed-off-by: Steven E. Harris <seh@panix.com>
    seh committed Feb 13, 2022

    Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    e437cb8 View commit details
  2. Trap failure to create Vault instance in tests

    When the Docker service isn't running, the test suite can't create a
    Vault instance. Trap this failure earlier in the test program to
    preclude panicking after tests relying on Vault have failed.
    
    Signed-off-by: Steven E. Harris <seh@panix.com>
    seh committed Feb 13, 2022

    Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    e665bcc View commit details

Commits on Feb 15, 2022

  1. Tolerate absence of resources in post-build subst.

    In a Kustomization's post-build substitution sources, introduce a new
    "Optional" field to allow referencing a Kubernetes ConfigMap or Secret
    that may not exist at time of reconciliation. Treat substitution when
    the referenced object is missing as if the object had been present but
    empty, lacking any variable bindings.
    
    Retain the longstanding behavior of interpreting references to
    Kubernetes objects being mandatory by default, such that
    reconciliation fails if such a referenced object does not exist. Only
    when the "Optional" field is set to true will reconciliation tolerate
    finding the referenced object to be missing.
    
    Signed-off-by: Steven E. Harris <seh@panix.com>
    seh committed Feb 15, 2022

    Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    eba4168 View commit details
  2. Copyedit Kustomization documentation

    Signed-off-by: Steven E. Harris <seh@panix.com>
    seh committed Feb 15, 2022

    Unverified

    This user has not yet uploaded their public signing key.
    Copy the full SHA
    af038d6 View commit details
  3. Merge pull request #570 from seh/tolerate-absent-post-build-subst-ref…

    …erences
    
    Tolerate absence of resources in post-build substitution
    stefanprodan authored Feb 15, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    5666108 View commit details

Commits on Feb 16, 2022

  1. Release v0.21.0

    Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
    stefanprodan committed Feb 16, 2022

    Verified

    This commit was signed with the committer’s verified signature.
    stefanprodan Stefan Prodan
    Copy the full SHA
    af84d6d View commit details
  2. Merge pull request #571 from fluxcd/release-v0.21.0

    Release v0.21.0
    stefanprodan authored Feb 16, 2022

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    b73d337 View commit details
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -2,6 +2,19 @@

All notable changes to this project are documented in this file.

## 0.21.0

**Release date:** 2022-02-16

This prerelease comes with support for making the Kubernetes Secrets and ConfigMaps
referenced in `postBuild.substituteFrom` optional.
When `substituteFrom.optional` is set to `true`, the controller will ignore
not found errors, and will substitute the variables with their default values.

Features:
- Tolerate absence of resources in post-build substitution
[#570](https://github.com/fluxcd/kustomize-controller/pull/570)

## 0.20.2

**Release date:** 2022-02-10
9 changes: 8 additions & 1 deletion api/v1beta2/kustomization_types.go
Original file line number Diff line number Diff line change
@@ -17,10 +17,10 @@ limitations under the License.
package v1beta2

import (
apimeta "k8s.io/apimachinery/pkg/api/meta"
"time"

apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apimeta "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

@@ -207,6 +207,13 @@ type SubstituteReference struct {
// +kubebuilder:validation:MaxLength=253
// +required
Name string `json:"name"`

// Optional indicates whether the referenced resource must exist, or whether to
// tolerate its absence. If true and the referenced resource is absent, proceed
// as if the resource was present but empty, without any variables defined.
// +kubebuilder:default:=false
// +optional
Optional bool `json:"optional,omitempty"`
}

// KustomizationStatus defines the observed state of a kustomization.
Original file line number Diff line number Diff line change
@@ -894,6 +894,14 @@ spec:
maxLength: 253
minLength: 1
type: string
optional:
default: false
description: Optional indicates whether the referenced resource
must exist, or whether to tolerate its absence. If true
and the referenced resource is absent, proceed as if the
resource was present but empty, without any variables
defined.
type: boolean
required:
- kind
- name
2 changes: 1 addition & 1 deletion config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -5,4 +5,4 @@ resources:
images:
- name: fluxcd/kustomize-controller
newName: fluxcd/kustomize-controller
newTag: v0.20.2
newTag: v0.21.0
13 changes: 10 additions & 3 deletions controllers/kustomization_varsub.go
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import (

"github.com/drone/envsubst"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/kustomize/api/resource"
@@ -48,26 +49,32 @@ func substituteVariables(
case "ConfigMap":
resource := &corev1.ConfigMap{}
if err := kubeClient.Get(ctx, namespacedName, resource); err != nil {
if reference.Optional && apierrors.IsNotFound(err) {
continue
}
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)
vars[k] = strings.ReplaceAll(v, "\n", "")
}
case "Secret":
resource := &corev1.Secret{}
if err := kubeClient.Get(ctx, namespacedName, resource); err != nil {
if reference.Optional && apierrors.IsNotFound(err) {
continue
}
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)
vars[k] = strings.ReplaceAll(string(v), "\n", "")
}
}
}

// 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)
vars[k] = strings.ReplaceAll(v, "\n", "")
}
}

156 changes: 156 additions & 0 deletions controllers/kustomization_varsub_test.go
Original file line number Diff line number Diff line change
@@ -193,3 +193,159 @@ stringData:
g.Expect(resultSA.Labels[fmt.Sprintf("%s/namespace", kustomizev1.GroupVersion.Group)]).To(Equal(client.ObjectKeyFromObject(resultK).Namespace))
})
}

func TestKustomizationReconciler_VarsubOptional(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:
color: "${color:=blue}"
shape: "${shape:=square}"
`, 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())

configName := types.NamespacedName{
Name: randStringRunes(5),
Namespace: id,
}
configMap := &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: configName.Name,
Namespace: configName.Namespace,
},
Data: map[string]string{"color": "\nred\n"},
}
g.Expect(k8sClient.Create(ctx, configMap)).Should(Succeed())

secretName := types.NamespacedName{
Name: randStringRunes(5),
Namespace: id,
}
secret := &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName.Name,
Namespace: secretName.Namespace,
},
StringData: map[string]string{"shape": "\ntriangle\n"},
}
g.Expect(k8sClient.Create(ctx, secret)).Should(Succeed())

inputK := &kustomizev1.Kustomization{
ObjectMeta: metav1.ObjectMeta{
Name: id,
Namespace: id,
},
Spec: kustomizev1.KustomizationSpec{
KubeConfig: &kustomizev1.KubeConfig{
SecretRef: meta.LocalObjectReference{
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{"var_substitution_enabled": "true"},
SubstituteFrom: []kustomizev1.SubstituteReference{
{
Kind: "ConfigMap",
Name: configName.Name,
Optional: true,
},
{
Kind: "Secret",
Name: secretName.Name,
Optional: true,
},
},
},
HealthChecks: []meta.NamespacedObjectKindReference{
{
APIVersion: "v1",
Kind: "ServiceAccount",
Name: id,
Namespace: id,
},
},
},
}
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 == meta.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["color"]).To(Equal("red"))
g.Expect(resultSA.Labels["shape"]).To(Equal("triangle"))
})

for _, o := range []client.Object{
configMap,
secret,
} {
g.Expect(k8sClient.Delete(ctx, o)).Should(Succeed())
}

// Force a second detectable reconciliation of the Kustomization.
g.Expect(k8sClient.Get(ctx, client.ObjectKeyFromObject(inputK), inputK)).Should(Succeed())
inputK.Status.Conditions = nil
g.Expect(k8sClient.Status().Update(ctx, inputK)).Should(Succeed())
ensureReconciles(" without optional ConfigMap")
t.Run("replaces vars tolerating absent ConfigMap", func(t *testing.T) {
g.Expect(resultSA.Labels["color"]).To(Equal("blue"))
g.Expect(resultSA.Labels["shape"]).To(Equal("square"))
})
}
5 changes: 4 additions & 1 deletion controllers/suite_test.go
Original file line number Diff line number Diff line change
@@ -121,8 +121,11 @@ func runInContext(registerControllers func(*testenv.Environment), run func() err
panic(fmt.Sprintf("Failed to create k8s client: %v", err))
}

// Create a vault test instance
// Create a Vault test instance.
pool, resource, err := createVaultTestInstance()
if err != nil {
panic(fmt.Sprintf("Failed to create Vault instance: %v", err))
}
defer func() {
pool.Purge(resource)
}()
14 changes: 14 additions & 0 deletions docs/api/kustomize.md
Original file line number Diff line number Diff line change
@@ -1126,6 +1126,20 @@ string
referring resource.</p>
</td>
</tr>
<tr>
<td>
<code>optional</code><br>
<em>
bool
</em>
</td>
<td>
<em>(Optional)</em>
<p>Optional indicates whether the referenced resource must exist, or whether to
tolerate its absence. If true and the referenced resource is absent, proceed
as if the resource was present but empty, without any variables defined.</p>
</td>
</tr>
</tbody>
</table>
</div>
25 changes: 19 additions & 6 deletions docs/spec/v1beta2/kustomization.md
Original file line number Diff line number Diff line change
@@ -285,10 +285,10 @@ On multi-tenant clusters, platform admins can disable cross-namespace references

If your repository contains plain Kubernetes manifests, the
`kustomization.yaml` file is automatically generated for all the Kubernetes
manifests in the `spec.path` of the Flux `Kustomization` and sub-directories.
This expects all YAML files present under that path to be valid kubernetes
manifests and needs non-kubernetes ones to be excluded using `.sourceignore`
file or `spec.ignore` on `GitRepository` object.
manifests in the directory tree specified in the `spec.path` field of the Flux `Kustomization`.
All YAML files present under that path must be valid Kubernetes
manifests, unless they're excluded either by way of the `.sourceignore`
file or the `spec.ignore` field on the corresponding `GitRepository` object.

Example of excluding CI workflows and SOPS config files:

@@ -748,6 +748,16 @@ With `spec.postBuild.substituteFrom` you can provide a list of ConfigMaps and Se
from which the variables are loaded.
The ConfigMap and Secret data keys are used as the var names.

The `spec.postBuild.substituteFrom.optional` field indicates how the
controller should handle a referenced ConfigMap or Secret being absent
at renconciliation time. The controller's default behavior ― with
`optional` unspecified or set to `false` ― has it fail reconciliation if
the referenced object is missing. By setting the `optional` field to
`true`, you can indicate that controller should use the referenced
object if it's there, but also tolerate its absence, treating that
absence as if the object had been present but empty, defining no
variables.

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

@@ -790,8 +800,11 @@ spec:
substituteFrom:
- kind: ConfigMap
name: cluster-vars
# Use this ConfigMap if it exists, but proceed if it doesn't.
optional: true
- kind: Secret
name: cluster-secret-vars
# Fail if this Secret does not exist.
```

Note that for substituting variables in a secret, `spec.stringData` field must be used i.e
@@ -1040,10 +1053,10 @@ spec:
### HashiCorp Vault

Export the `VAULT_ADDR` and `VAULT_TOKEN` environment variables to your shell,
then use `sops` to encrypt a kubernetes secret (see [HashiCorp Vault](https://www.vaultproject.io/docs/secrets/transit)
then use `sops` to encrypt a Kubernetes Secret (see [HashiCorp Vault](https://www.vaultproject.io/docs/secrets/transit)
for more details on enabling the transit backend and [sops](https://github.com/mozilla/sops#encrypting-using-hashicorp-vault)).

Then use `sops` to encrypt a kubernetes secret:
Then use `sops` to encrypt a Kubernetes Secret:

```console
$ export VAULT_ADDR=https://vault.example.com:8200
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ require (
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3
github.com/cyphar/filepath-securejoin v0.2.2
github.com/drone/envsubst v1.0.3-0.20200804185402-58bc65f69603
github.com/fluxcd/kustomize-controller/api v0.20.2
github.com/fluxcd/kustomize-controller/api v0.21.0
github.com/fluxcd/pkg/apis/acl v0.0.3
github.com/fluxcd/pkg/apis/kustomize v0.3.1
github.com/fluxcd/pkg/apis/meta v0.10.2