Skip to content

Commit a9cbbcd

Browse files
committed
Initial commit
1 parent 9e3f24d commit a9cbbcd

File tree

14 files changed

+1062
-0
lines changed

14 files changed

+1062
-0
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Kubectl Plugin to Add Support for Environment Variable Evaluation in Kustomize
2+
3+
This plugin is a `kubectl` plugin that adds support for evaluating environment variables in Kustomize overlays.
4+
5+
![image](docs/envkustomize.png)
6+
## Why This Plugin?
7+
8+
I've used Kustomize extensively over the years, and I've always missed the ability to evaluate environment variables in overlays. The recommended approach, using patches and overlays, is just too much work when managing a large fleet of clusters. It can be a pain even for basic tasks like patching an image name!
9+
10+
This plugin is largely a response to the frustrations described here:
11+
12+
* [ConfigMapGenerator should not load values from the build environment](https://github.com/kubernetes-sigs/kustomize/issues/4731)
13+
* [Reddit discussion on passing dynamic values to Kustomize](https://www.reddit.com/r/kubernetes/comments/116hze5/how_to_pass_dynamic_values_to_kustomize/)
14+
* [ArgoCD issue on dynamic values](https://github.com/argoproj/argo-cd/issues/1705)
15+
16+
## How Does It Work?
17+
18+
The plugin is a lightweight wrapper around the official Kustomize API (krusty) [Kustomizer](https://github.com/kubernetes-sigs/kustomize/blob/master/api/krusty/kustomizer.go). It replaces the original filesystem loader with a custom one that evaluates environment variables before loading the file.
19+
20+
## How to Use It?
21+
22+
```bash
23+
# You need to run it in a valid Kustomize overlay folder like this:
24+
cd examples
25+
kubectl envkustomize render
26+
# Check the rendered.yaml file generated in the same folder
27+
```
28+
29+
### Evaluations Supported
30+
31+
* Basic environment variables replacement: `${{{ VAR }}}`
32+
* Load of environment variables from `.env` files or from the environment itself (using a prefix to avoid loading all environment variables and to avoid conflicts)
33+
* Load secrets from GCP Secret Manager (Welcome PRs to add AWS Secrets Manager and Azure Key Vault support)
34+
* Expand environment with `${{{env-expand://ENV_}}}` to inject automatically all environment variables starting with `ENV_`
35+
* Support for environment variables substitution even inside helm charts (or other external resources as long as they are referenced in the kustomize overlay)

cmd/render.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package cmd
2+
3+
import "github.com/spf13/cobra"
4+
import "kubectl-envkustomize/pkg"
5+
6+
var renderCmd = &cobra.Command{
7+
Use: "render",
8+
Short: "Render kustomize manifests",
9+
Long: `Render kustomize manifests from the environment configurations. To run this command you meed an .env and kustomization.yaml file on the current directory.`,
10+
Run: func(cmd *cobra.Command, args []string) {
11+
envFile, _ := cmd.Flags().GetString("env-file")
12+
pkg.RenderCmd(envFile)
13+
},
14+
}
15+
16+
func init() {
17+
renderCmd.Flags().StringP("env-file", "e", ".env", "Path to the environment file")
18+
}

cmd/root.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package cmd
2+
3+
import "github.com/spf13/cobra"
4+
5+
var rootCmd = &cobra.Command{
6+
Use: "kubectl-envkustomize",
7+
Short: "Kubectl env kustomize plugin",
8+
Long: `Kustomize with env support`,
9+
}
10+
11+
func Execute() error {
12+
return rootCmd.Execute()
13+
}
14+
15+
func init() {
16+
rootCmd.AddCommand(renderCmd)
17+
}

docs/envkustomize.png

24.5 KB
Loading

examples/configmap.yaml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# example to make envoy serve a static file
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: envoy-config
6+
data:
7+
envoy.yaml: |
8+
static_resources:
9+
listeners:
10+
- name: listener_0
11+
address:
12+
socket_address:
13+
address: 0.0.0.0
14+
port_value: 8080
15+
filter_chains:
16+
- filters:
17+
- name: envoy.filters.network.http_connection_manager
18+
typed_config:
19+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
20+
stat_prefix: ingress_http
21+
route_config:
22+
name: local_route
23+
virtual_hosts:
24+
- name: local_service
25+
domains: ["*"]
26+
routes:
27+
- match:
28+
prefix: "/"
29+
direct_response:
30+
status: 200
31+
body:
32+
filename: /etc/envoy/static-file.html
33+
http_filters:
34+
- name: envoy.filters.http.router
35+
static-file.html: |
36+
<html>
37+
<body>
38+
<h1> ${{{ ENV_KUBECTL_WELCOME_TEXT }}} </h1>
39+
<p>This is a static file served by Envoy.</p>
40+
</body>
41+
</html>

examples/deploy.yaml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: envoy-deployment
5+
spec:
6+
replicas: 1
7+
selector:
8+
matchLabels:
9+
app: envoy
10+
template:
11+
metadata:
12+
labels:
13+
app: envoy
14+
spec:
15+
containers:
16+
- name: envoy
17+
image: ${{{ ENV_KUBECTL_ENVOY_IMAGE }}} # Example injecting environment variables dynamically
18+
ports:
19+
- containerPort: 8080
20+
volumeMounts:
21+
- name: envoy-config
22+
mountPath: /etc/envoy/envoy.yaml
23+
subPath: envoy.yaml
24+
- name: static-file
25+
mountPath: /etc/envoy/static-file.html
26+
subPath: static-file.html
27+
# Example setting resources dynamically from environment variables
28+
resources:
29+
requests:
30+
memory: "${{{ ENV_KUBECTL_MEMORY_REQUEST }}}"
31+
cpu: "${{{ ENV_KUBECTL_CPU_REQUEST }}}"
32+
limits:
33+
memory: "${{{ ENV_KUBECTL_CPU_LIMIT }}}"
34+
cpu: "${{{ ENV_KUBECTL_CPU_LIMIT }}}"
35+
36+
37+
env:
38+
# Example injecting environment variables dynamically
39+
- name: ENVOY_LOG_LEVEL
40+
value: ${{{ ENV_KUBECTL_ENVOY_LOG_LEVEL }}}
41+
42+
# Example injecting ALL environment variables with a prefix dynamically
43+
# check the rendered.yaml to see the result!
44+
- name: ${{{env-expand://ENV_KUBECTL_ENVOY}}}
45+
value: ${{{env-expand://ENV_KUBECTL_ENVOY}}}
46+
volumes:
47+
- name: envoy-config
48+
configMap:
49+
name: envoy-config
50+
items:
51+
- key: envoy.yaml
52+
path: envoy.yaml
53+
- name: static-file
54+
configMap:
55+
name: envoy-config
56+
items:
57+
- key: static-file.html
58+
path: static-file.html
59+
---
60+
apiVersion: v1
61+
kind: Service
62+
metadata:
63+
name: envoy-service
64+
spec:
65+
selector:
66+
app: envoy
67+
ports:
68+
- protocol: TCP
69+
port: 80
70+
targetPort: 8080

examples/kustomization.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
namespace: test-namespace
4+
5+
resources:
6+
- deploy.yaml
7+
- configmap.yaml
8+
- pull-image-secret.yaml

examples/pull-image-secret.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Example rendering secrets using environment variables
2+
apiVersion: v1
3+
data:
4+
.dockerconfigjson: ${{{ ENV_KUBECTL_SECRET_DOCKERCONFIGJSON }}}
5+
kind: Secret
6+
metadata:
7+
name: secret-dockerconfigjson
8+
type: kubernetes.io/dockerconfigjson

examples/rendered.yaml

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: envoy-deployment
5+
namespace: test-namespace
6+
spec:
7+
replicas: 1
8+
selector:
9+
matchLabels:
10+
app: envoy
11+
template:
12+
metadata:
13+
labels:
14+
app: envoy
15+
spec:
16+
containers:
17+
- env:
18+
- name: ENVOY_LOG_LEVEL
19+
value: debug
20+
- name: ENV_KUBECTL_ENVOY_IMAGE
21+
value: envoyproxy/envoy:v1.24.0
22+
- name: ENV_KUBECTL_ENVOY_LOG_LEVEL
23+
value: debug
24+
- name: ENV_KUBECTL_ENVOY_TEST1
25+
value: value1
26+
- name: ENV_KUBECTL_ENVOY_TEST2
27+
value: value2
28+
- name: ENV_KUBECTL_ENVOY_TEST3
29+
value: value3
30+
image: envoyproxy/envoy:v1.24.0
31+
name: envoy
32+
ports:
33+
- containerPort: 8080
34+
resources:
35+
limits:
36+
cpu: 250m
37+
memory: 250m
38+
requests:
39+
cpu: 100m
40+
memory: 128Mi
41+
volumeMounts:
42+
- mountPath: /etc/envoy/envoy.yaml
43+
name: envoy-config
44+
subPath: envoy.yaml
45+
- mountPath: /etc/envoy/static-file.html
46+
name: static-file
47+
subPath: static-file.html
48+
volumes:
49+
- configMap:
50+
items:
51+
- key: envoy.yaml
52+
path: envoy.yaml
53+
name: envoy-config
54+
name: envoy-config
55+
- configMap:
56+
items:
57+
- key: static-file.html
58+
path: static-file.html
59+
name: envoy-config
60+
name: static-file
61+
---
62+
apiVersion: v1
63+
kind: Service
64+
metadata:
65+
name: envoy-service
66+
namespace: test-namespace
67+
spec:
68+
ports:
69+
- port: 80
70+
protocol: TCP
71+
targetPort: 8080
72+
selector:
73+
app: envoy
74+
---
75+
apiVersion: v1
76+
data:
77+
envoy.yaml: |
78+
static_resources:
79+
listeners:
80+
- name: listener_0
81+
address:
82+
socket_address:
83+
address: 0.0.0.0
84+
port_value: 8080
85+
filter_chains:
86+
- filters:
87+
- name: envoy.filters.network.http_connection_manager
88+
typed_config:
89+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
90+
stat_prefix: ingress_http
91+
route_config:
92+
name: local_route
93+
virtual_hosts:
94+
- name: local_service
95+
domains: ["*"]
96+
routes:
97+
- match:
98+
prefix: "/"
99+
direct_response:
100+
status: 200
101+
body:
102+
filename: /etc/envoy/static-file.html
103+
http_filters:
104+
- name: envoy.filters.http.router
105+
static-file.html: |
106+
<html>
107+
<body>
108+
<h1> Welcome to env kubectl </h1>
109+
<p>This is a static file served by Envoy.</p>
110+
</body>
111+
</html>
112+
kind: ConfigMap
113+
metadata:
114+
name: envoy-config
115+
namespace: test-namespace
116+
---
117+
apiVersion: v1
118+
data:
119+
.dockerconfigjson: bXkgc2VjcmV0
120+
kind: Secret
121+
metadata:
122+
name: secret-dockerconfigjson
123+
namespace: test-namespace
124+
type: kubernetes.io/dockerconfigjson
125+
126+
---

go.mod

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
module kubectl-envkustomize
2+
3+
go 1.22.4
4+
5+
require (
6+
cloud.google.com/go/auth v0.7.2 // indirect
7+
cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
8+
cloud.google.com/go/compute/metadata v0.5.0 // indirect
9+
cloud.google.com/go/iam v1.1.11 // indirect
10+
cloud.google.com/go/secretmanager v1.13.4 // indirect
11+
github.com/blang/semver/v4 v4.0.0 // indirect
12+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
13+
github.com/felixge/httpsnoop v1.0.4 // indirect
14+
github.com/go-errors/errors v1.4.2 // indirect
15+
github.com/go-logr/logr v1.4.2 // indirect
16+
github.com/go-logr/stdr v1.2.2 // indirect
17+
github.com/go-openapi/jsonpointer v0.19.6 // indirect
18+
github.com/go-openapi/jsonreference v0.20.2 // indirect
19+
github.com/go-openapi/swag v0.22.4 // indirect
20+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
21+
github.com/golang/protobuf v1.5.4 // indirect
22+
github.com/google/gnostic-models v0.6.8 // indirect
23+
github.com/google/s2a-go v0.1.8 // indirect
24+
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
25+
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
26+
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
27+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
28+
github.com/josharian/intern v1.0.0 // indirect
29+
github.com/mailru/easyjson v0.7.7 // indirect
30+
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
31+
github.com/pkg/errors v0.9.1 // indirect
32+
github.com/spf13/cobra v1.8.1 // indirect
33+
github.com/spf13/pflag v1.0.5 // indirect
34+
github.com/xlab/treeprint v1.2.0 // indirect
35+
go.opencensus.io v0.24.0 // indirect
36+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect
37+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
38+
go.opentelemetry.io/otel v1.28.0 // indirect
39+
go.opentelemetry.io/otel/metric v1.28.0 // indirect
40+
go.opentelemetry.io/otel/trace v1.28.0 // indirect
41+
go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
42+
golang.org/x/crypto v0.25.0 // indirect
43+
golang.org/x/net v0.27.0 // indirect
44+
golang.org/x/oauth2 v0.21.0 // indirect
45+
golang.org/x/sync v0.7.0 // indirect
46+
golang.org/x/sys v0.22.0 // indirect
47+
golang.org/x/text v0.16.0 // indirect
48+
golang.org/x/time v0.5.0 // indirect
49+
google.golang.org/api v0.189.0 // indirect
50+
google.golang.org/genproto v0.0.0-20240722135656-d784300faade // indirect
51+
google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade // indirect
52+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade // indirect
53+
google.golang.org/grpc v1.65.0 // indirect
54+
google.golang.org/protobuf v1.34.2 // indirect
55+
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
56+
gopkg.in/yaml.v3 v3.0.1 // indirect
57+
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
58+
sigs.k8s.io/kustomize/api v0.17.3 // indirect
59+
sigs.k8s.io/kustomize/kyaml v0.17.2 // indirect
60+
sigs.k8s.io/yaml v1.4.0 // indirect
61+
)

0 commit comments

Comments
 (0)