Skip to content

Commit 4eaedb7

Browse files
authored
feat: adding merge function to support templating (#1)
* feat: adding merge function to support templating * feat: adding secret templating upon loading * feat: adding support for k8s ConfigMap as type
1 parent f6841df commit 4eaedb7

22 files changed

+616
-45
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
arch: [amd64, arm64]
15-
os: [ubuntu-latest, ubuntu-18.04, windows-latest, macos-11]
15+
os: [ubuntu-latest, windows-latest, macos-11]
1616
include:
1717
- os: ubuntu-latest
1818
os_name: ubuntu
1919
artifact_suffix: ""
20-
- os: ubuntu-18.04
21-
os_name: ubuntu-18.04
22-
artifact_suffix: ""
2320
- os: windows-latest
2421
os_name: windows
2522
artifact_suffix: ".exe"

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ the secrets are stored in a Git repository and secured using SOPS.
3535
#### Secret storage
3636

3737
Secrets are stored in any directory of your git repository. The GitOps CLI will pick
38-
up any file that ends with `*.gitops.secret.enc.yml` or `*.gitops.secret.enc.yaml`. The secret files
39-
must be encrypted using SOPS.
38+
up any file that ends with `*.gitops.secret.enc.y[a]ml` except for `values.gitops.secret.enc.y[a]ml` (see [Secrets Templating](#secrets-templating))
39+
The secret files must be encrypted using SOPS.
4040

4141
**NOTE:** Secrets MUST NEVER be committed into version control unencrypted.
4242
Therefore, it is very much encouraged to add the following lines to your `.gitignore` file:
@@ -92,6 +92,12 @@ name: my-secret-name
9292

9393
This implies, that the filename must be a valid K8s secret name.
9494

95+
#### Secrets Templating
96+
97+
It is possible to use Go templates in the secret files. The values will originate from sops-encrypted `values.gitops.secret.enc.y[a]ml` files.
98+
Values files can be located anywhere in the repository. The GitOps CLI will pick up all files that are located on the direct path towards the respective secret file.
99+
Values files closer to the secret file will have higher precedence. Any object structure is allowed to be used in a values file.
100+
95101
## Repository
96102

97103
### After the first clone

cmd/gitops/kubernetes.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ func createKubernetesPlan(c *cli.Context) (*plan.Plan, error) {
8181
log.Error("Failed to connect to Kubernetes cluster")
8282
return nil, err
8383
}
84-
localSecrets, err := secret.LoadLocalSecrets(c.String("root-dir"), secret.SecretTargetKubernetes)
84+
localSecrets, err := secret.LoadLocalSecrets(secret.SecretTargetKubernetes)
8585
if err != nil {
8686
log.Error("Failed to load local secrets with target ", secret.SecretTargetKubernetes)
8787
return nil, err
@@ -147,6 +147,7 @@ func createKubernetesPlan(c *cli.Context) (*plan.Plan, error) {
147147
remoteSecret, err := k8s.GetSecret(&secret.Secret{
148148
Name: stateSecret.Name,
149149
Namespace: stateSecret.Namespace,
150+
Type: stateSecret.Type,
150151
})
151152
if err != nil {
152153
// only throw error if err is not "not found"

cmd/gitops/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func main() {
4343
Commands: []*cli.Command{
4444
{
4545
Name: "secrets",
46+
Aliases: []string{"s"},
4647
Usage: "GitOps managed secrets",
4748
Subcommands: []*cli.Command{
4849
{

cmd/gitops/templating.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
)
88

99
func testTemplating(c *cli.Context) error {
10-
secretFiles, err := util.GetSecretFiles(c.String("root-dir"))
10+
secretFiles, err := util.GetSecretFiles()
1111
if err != nil {
1212
log.Fatal(err)
1313
}

cmd/gitops/util.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ import (
1010
func initApplication(c *cli.Context) error {
1111
printLogo(c)
1212
setLogLevel(c)
13-
util.ComputeRootDir(c)
1413
util.SetCliContext(c)
15-
if c.String("root-dir") == "" {
16-
log.Fatal("No root directory specified")
17-
}
14+
util.GetRootDir()
1815
err := state.LoadState(c)
1916
return err
2017
}

cmd/gitops/vault.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package main
2+

go.mod

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ require (
2424
github.com/google/gnostic v0.5.7-v3refs // indirect
2525
github.com/google/go-cmp v0.5.9 // indirect
2626
github.com/google/gofuzz v1.1.0 // indirect
27+
github.com/hashicorp/vault-client-go v0.2.0 // indirect
2728
github.com/imdario/mergo v0.3.6 // indirect
2829
github.com/josharian/intern v1.0.0 // indirect
2930
github.com/json-iterator/go v1.1.12 // indirect
@@ -33,6 +34,7 @@ require (
3334
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
3435
github.com/pmezard/go-difflib v1.0.0 // indirect
3536
github.com/spf13/pflag v1.0.5 // indirect
37+
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
3638
gopkg.in/inf.v0 v0.9.1 // indirect
3739
k8s.io/klog/v2 v2.80.1 // indirect
3840
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
@@ -78,7 +80,7 @@ require (
7880
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
7981
github.com/hashicorp/go-multierror v1.1.1 // indirect
8082
github.com/hashicorp/go-plugin v1.4.3 // indirect
81-
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
83+
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
8284
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
8385
github.com/hashicorp/go-secure-stdlib/mlock v0.1.2 // indirect
8486
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.3 // indirect
@@ -118,10 +120,10 @@ require (
118120
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
119121
golang.org/x/net v0.7.0 // indirect
120122
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
121-
golang.org/x/sys v0.5.0 // indirect
123+
golang.org/x/sys v0.6.0 // indirect
122124
golang.org/x/term v0.5.0 // indirect
123125
golang.org/x/text v0.7.0 // indirect
124-
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 // indirect
126+
golang.org/x/time v0.3.0 // indirect
125127
google.golang.org/api v0.74.0 // indirect
126128
google.golang.org/appengine v1.6.7 // indirect
127129
google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf // indirect

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es
304304
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
305305
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
306306
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
307+
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
308+
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
307309
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
308310
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
309311
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
@@ -332,6 +334,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l
332334
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
333335
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
334336
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
337+
github.com/hashicorp/vault-client-go v0.2.0 h1:Zzf5D2kj7QmBZE2ZTdril1aJlujMptPatxslTkdDF+U=
338+
github.com/hashicorp/vault-client-go v0.2.0/go.mod h1:C9rbJeHeI1Dy/MXXd5YLrzRfAH27n6mARnhpvaW/8gk=
335339
github.com/hashicorp/vault/api v1.5.0 h1:Bp6yc2bn7CWkOrVIzFT/Qurzx528bdavF3nz590eu28=
336340
github.com/hashicorp/vault/api v1.5.0/go.mod h1:LkMdrZnWNrFaQyYYazWVn7KshilfDidgVBq6YiTq/bM=
337341
github.com/hashicorp/vault/sdk v0.4.1 h1:3SaHOJY687jY1fnB61PtL0cOkKItphrbLmux7T92HBo=
@@ -545,6 +549,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
545549
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
546550
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
547551
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
552+
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
553+
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
548554
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
549555
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
550556
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -711,6 +717,8 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
711717
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
712718
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
713719
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
720+
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
721+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
714722
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
715723
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
716724
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
@@ -733,6 +741,8 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb
733741
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
734742
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
735743
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
744+
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
745+
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
736746
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
737747
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
738748
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=

internal/k8s/kubernetes.go

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,35 @@ func TestClusterConnection() (bool, error) {
5959
}
6060

6161
func CreateSecret(s *secret.Secret) error {
62-
log.Trace("Creating secret ", s.Name, " in namespace ", s.Namespace)
62+
if s.Type == "ConfigMap" {
63+
return CreateK8sConfigMap(s)
64+
} else {
65+
return CreateK8sSecret(s)
66+
}
67+
}
68+
69+
func CreateK8sConfigMap(s *secret.Secret) error {
70+
log.Trace("Creating ConfigMap ", s.Name, " in namespace ", s.Namespace)
71+
k8sConfigMap, err := clientset.CoreV1().ConfigMaps(s.Namespace).Create(context.Background(), &v1.ConfigMap{
72+
ObjectMeta: metav1.ObjectMeta{
73+
Name: s.Name,
74+
Namespace: s.Namespace,
75+
Annotations: map[string]string{
76+
"gitops.mxcd.de/secret-id": s.ID,
77+
},
78+
},
79+
Data: s.Data,
80+
}, metav1.CreateOptions{})
81+
log.Trace(k8sConfigMap)
82+
if err != nil {
83+
return err
84+
}
85+
println(s.Namespace, "/", s.Name, color.InGreen(" created"))
86+
return err
87+
}
88+
89+
func CreateK8sSecret(s *secret.Secret) error {
90+
log.Trace("Creating Secret ", s.Name, " in namespace ", s.Namespace)
6391
k8sSecret, err := clientset.CoreV1().Secrets(s.Namespace).Create(context.Background(), &v1.Secret{
6492
ObjectMeta: metav1.ObjectMeta{
6593
Name: s.Name,
@@ -80,7 +108,15 @@ func CreateSecret(s *secret.Secret) error {
80108
}
81109

82110
func UpdateSecret(s *secret.Secret) error {
83-
log.Trace("Updating secret ", s.Name, " in namespace ", s.Namespace)
111+
if s.Type == "ConfigMap" {
112+
return UpdateK8sConfigMap(s)
113+
} else {
114+
return UpdateK8sSecret(s)
115+
}
116+
}
117+
118+
func UpdateK8sSecret(s *secret.Secret) error {
119+
log.Trace("Updating Secret ", s.Name, " in namespace ", s.Namespace)
84120

85121
k8sSecret, err := clientset.CoreV1().Secrets(s.Namespace).Update(context.Background(), &v1.Secret{
86122
ObjectMeta: metav1.ObjectMeta{
@@ -101,8 +137,47 @@ func UpdateSecret(s *secret.Secret) error {
101137
return err
102138
}
103139

140+
func UpdateK8sConfigMap(s *secret.Secret) error {
141+
log.Trace("Updating ConfigMap ", s.Name, " in namespace ", s.Namespace)
142+
143+
k8sConfigMap, err := clientset.CoreV1().ConfigMaps(s.Namespace).Update(context.Background(), &v1.ConfigMap{
144+
ObjectMeta: metav1.ObjectMeta{
145+
Name: s.Name,
146+
Namespace: s.Namespace,
147+
Annotations: map[string]string{
148+
"gitops.mxcd.de/secret-id": s.ID,
149+
},
150+
},
151+
Data: s.Data,
152+
}, metav1.UpdateOptions{})
153+
log.Trace(k8sConfigMap)
154+
if err != nil {
155+
return err
156+
}
157+
println(s.Namespace, "/", s.Name, color.InYellow(" updated"))
158+
return err
159+
}
160+
104161
func DeleteSecret(s *secret.Secret) error {
105-
log.Trace("Deleting secret ", s.Name, " in namespace ", s.Namespace)
162+
if s.Type == "ConfigMap" {
163+
return DeleteK8sConfigMap(s)
164+
} else {
165+
return DeleteK8sSecret(s)
166+
}
167+
}
168+
169+
func DeleteK8sConfigMap(s *secret.Secret) error {
170+
log.Trace("Deleting ConfigMap ", s.Name, " in namespace ", s.Namespace)
171+
err := clientset.CoreV1().ConfigMaps(s.Namespace).Delete(context.Background(), s.Name, metav1.DeleteOptions{})
172+
if err != nil {
173+
return err
174+
}
175+
println(s.Namespace, "/", s.Name, color.InRed(" deleted"))
176+
return err
177+
}
178+
179+
func DeleteK8sSecret(s *secret.Secret) error {
180+
log.Trace("Deleting Secret ", s.Name, " in namespace ", s.Namespace)
106181
err := clientset.CoreV1().Secrets(s.Namespace).Delete(context.Background(), s.Name, metav1.DeleteOptions{})
107182
if err != nil {
108183
return err
@@ -112,6 +187,30 @@ func DeleteSecret(s *secret.Secret) error {
112187
}
113188

114189
func GetSecret(s *secret.Secret) (*secret.Secret, error) {
190+
if s.Type == "ConfigMap" {
191+
return GetK8sConfigMap(s)
192+
} else {
193+
return GetK8sSecret(s)
194+
}
195+
}
196+
197+
func GetK8sConfigMap(s *secret.Secret) (*secret.Secret, error) {
198+
k8sConfigMap, err := clientset.CoreV1().ConfigMaps(s.Namespace).Get(context.Background(), s.Name, metav1.GetOptions{})
199+
200+
if err != nil {
201+
return nil, err
202+
}
203+
204+
return &secret.Secret{
205+
Name: k8sConfigMap.Name,
206+
Target: secret.SecretTargetKubernetes,
207+
Namespace: k8sConfigMap.Namespace,
208+
Data: k8sConfigMap.Data,
209+
Type: "ConfigMap",
210+
}, nil
211+
}
212+
213+
func GetK8sSecret(s *secret.Secret) (*secret.Secret, error) {
115214
k8sSecret, err := clientset.CoreV1().Secrets(s.Namespace).Get(context.Background(), s.Name, metav1.GetOptions{})
116215

117216
if err != nil {
@@ -127,8 +226,6 @@ func GetSecret(s *secret.Secret) (*secret.Secret, error) {
127226
}, nil
128227
}
129228

130-
131-
132229
func getStringData(s *v1.Secret) map[string]string {
133230
stringData := make(map[string]string)
134231
for key, value := range s.Data {

0 commit comments

Comments
 (0)