Skip to content
Open
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
50 changes: 32 additions & 18 deletions cmd/flux/envsubst.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ limitations under the License.
package main

import (
"bufio"
"fmt"
"io"
"os"

"github.com/fluxcd/pkg/envsubst"
"github.com/fluxcd/pkg/kustomize"
"github.com/spf13/cobra"
)

Expand All @@ -37,38 +39,50 @@ to replicate the behavior of the Flux Kustomization post-build substitutions.`),

# Run env var substitutions and error out if a variable is not set
kustomize build . | flux envsubst --strict

# Run env var substitutions, skipping resources with substitute disabled
kustomize build . | flux envsubst --strict --k8s-aware
`,
RunE: runEnvsubstCmd,
}

type envsubstFlags struct {
strict bool
strict bool
k8sAware bool
}

var envsubstArgs envsubstFlags

func init() {
envsubstCmd.Flags().BoolVar(&envsubstArgs.strict, "strict", false,
"fail if a variable without a default value is declared in the input but is missing from the environment")
envsubstCmd.Flags().BoolVar(&envsubstArgs.k8sAware, "k8s-aware", false,
"treat the input as multi-doc Kubernetes YAML and skip substitution for resources "+
"annotated or labeled with kustomize.toolkit.fluxcd.io/substitute: disabled")
rootCmd.AddCommand(envsubstCmd)
}

func runEnvsubstCmd(cmd *cobra.Command, args []string) error {
stdin := bufio.NewScanner(rootCmd.InOrStdin())
stdout := bufio.NewWriter(rootCmd.OutOrStdout())
for stdin.Scan() {
line, err := envsubst.EvalEnv(stdin.Text(), envsubstArgs.strict)
if err != nil {
return err
}
_, err = fmt.Fprintln(stdout, line)
if err != nil {
return err
}
err = stdout.Flush()
if err != nil {
return err
}
data, err := io.ReadAll(rootCmd.InOrStdin())
if err != nil {
return err
}

mapping := envsubst.Getenv
if envsubstArgs.strict {
mapping = os.LookupEnv
}
return nil

var result string
if envsubstArgs.k8sAware {
result, err = kustomize.SubstituteEnvVariables(string(data), mapping)
} else {
result, err = envsubst.Eval(string(data), mapping)
}
if err != nil {
return err
}

_, err = fmt.Fprint(rootCmd.OutOrStdout(), result)
return err
}
77 changes: 77 additions & 0 deletions cmd/flux/envsubst_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,80 @@ func TestEnvsubst_Strinct(t *testing.T) {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring("variable not set (strict mode)"))
}

func TestEnvsubst_K8sAware(t *testing.T) {
tests := []struct {
name string
args string
env map[string]string
input string
gold string
wantErr string
}{
{
name: "annotation disabled",
args: "envsubst --k8s-aware",
env: map[string]string{"REPO_NAME": "test"},
input: "testdata/envsubst/k8s-aware.yaml",
gold: "testdata/envsubst/k8s-aware.gold",
},
{
name: "label disabled",
args: "envsubst --k8s-aware",
input: "testdata/envsubst/k8s-aware-label.yaml",
gold: "testdata/envsubst/k8s-aware-label.gold",
},
{
name: "strict skips disabled resources",
args: "envsubst --strict --k8s-aware",
env: map[string]string{"REPO_NAME": "test"},
input: "testdata/envsubst/k8s-aware.yaml",
gold: "testdata/envsubst/k8s-aware.gold",
},
{
name: "strict errors on enabled resource with missing var",
args: "envsubst --strict --k8s-aware",
input: "testdata/envsubst/k8s-aware.yaml",
wantErr: "variable not set (strict mode)",
},
{
name: "bash script in disabled resource",
args: "envsubst --k8s-aware",
env: map[string]string{"APP_NAME": "myapp"},
input: "testdata/envsubst/k8s-aware-bash.yaml",
gold: "testdata/envsubst/k8s-aware-bash.gold",
},
{
name: "strict with bash script in disabled resource",
args: "envsubst --strict --k8s-aware",
env: map[string]string{"APP_NAME": "myapp"},
input: "testdata/envsubst/k8s-aware-bash.yaml",
gold: "testdata/envsubst/k8s-aware-bash.gold",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)

for k, v := range tt.env {
t.Setenv(k, v)
}

input, err := os.ReadFile(tt.input)
g.Expect(err).NotTo(HaveOccurred())

output, err := executeCommandWithIn(tt.args, bytes.NewReader(input))
if tt.wantErr != "" {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
return
}
g.Expect(err).NotTo(HaveOccurred())

expected, err := os.ReadFile(tt.gold)
g.Expect(err).NotTo(HaveOccurred())
g.Expect(output).To(Equal(string(expected)))
})
}
}
29 changes: 29 additions & 0 deletions cmd/flux/testdata/envsubst/k8s-aware-bash.gold
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: myapp
namespace: default
data:
key: value
---
apiVersion: v1
kind: ConfigMap
metadata:
name: init-scripts
namespace: default
annotations:
kustomize.toolkit.fluxcd.io/substitute: disabled
data:
setup.sh: |
#!/bin/bash
process_args() {
echo "First arg: $1"
echo "Second arg: $2"
echo "All args: $@"
local name=${1:-default}
local count=${2:-0}
for i in $(seq 1 $count); do
echo "$i: processing $name"
done
}
process_args "$@"
29 changes: 29 additions & 0 deletions cmd/flux/testdata/envsubst/k8s-aware-bash.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: ${APP_NAME}
namespace: ${APP_NAMESPACE:=default}
data:
key: value
---
apiVersion: v1
kind: ConfigMap
metadata:
name: init-scripts
namespace: default
annotations:
kustomize.toolkit.fluxcd.io/substitute: disabled
data:
setup.sh: |
#!/bin/bash
process_args() {
echo "First arg: $1"
echo "Second arg: $2"
echo "All args: $@"
local name=${1:-default}
local count=${2:-0}
for i in $(seq 1 $count); do
echo "$i: processing $name"
done
}
process_args "$@"
9 changes: 9 additions & 0 deletions cmd/flux/testdata/envsubst/k8s-aware-label.gold
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard
namespace: monitoring
labels:
kustomize.toolkit.fluxcd.io/substitute: disabled
data:
dashboard.json: '{"panels": [{"datasource": "${DataSource}"}]}'
9 changes: 9 additions & 0 deletions cmd/flux/testdata/envsubst/k8s-aware-label.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard
namespace: monitoring
labels:
kustomize.toolkit.fluxcd.io/substitute: disabled
data:
dashboard.json: '{"panels": [{"datasource": "${DataSource}"}]}'
25 changes: 25 additions & 0 deletions cmd/flux/testdata/envsubst/k8s-aware.gold
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: test
namespace: flux-system
data:
key: value
---
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard
namespace: monitoring
annotations:
kustomize.toolkit.fluxcd.io/substitute: disabled
data:
dashboard.json: '{"panels": [{"datasource": "${DataSource}"}]}'
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-config
namespace: flux-system
data:
region: eu-central-1
25 changes: 25 additions & 0 deletions cmd/flux/testdata/envsubst/k8s-aware.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: ${REPO_NAME}
namespace: ${REPO_NAMESPACE:=flux-system}
data:
key: value
---
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-dashboard
namespace: monitoring
annotations:
kustomize.toolkit.fluxcd.io/substitute: disabled
data:
dashboard.json: '{"panels": [{"datasource": "${DataSource}"}]}'
---
apiVersion: v1
kind: ConfigMap
metadata:
name: ${REPO_NAME}-config
namespace: ${REPO_NAMESPACE:=flux-system}
data:
region: ${CLUSTER_REGION:=eu-central-1}