From 9ba3b6da99ea6941fe648310ca40c48616249a8e Mon Sep 17 00:00:00 2001 From: sarmad-abualkaz Date: Sat, 19 Feb 2022 19:59:09 -0500 Subject: [PATCH 1/2] ft: adding dataKey option - parameterizing data-key for secrets and cms - removing hard-coded `values.yaml` --- cmd/cm.go | 4 +++- cmd/dowloader_test.go | 19 +++++++++++++++++- cmd/downloader.go | 26 +++++++++++++++---------- cmd/secret.go | 4 +++- util/kubernetes/config-map.go | 4 ++-- util/kubernetes/config-map_test.go | 31 +++++++++++++++++++++++++++++- util/kubernetes/secret.go | 4 ++-- util/kubernetes/secret_test.go | 31 +++++++++++++++++++++++++++++- 8 files changed, 104 insertions(+), 19 deletions(-) diff --git a/cmd/cm.go b/cmd/cm.go index 1491553..0ecd134 100644 --- a/cmd/cm.go +++ b/cmd/cm.go @@ -10,6 +10,7 @@ import ( ) var kubeNamespace string +var dataKey string var output string var cmCmd = &cobra.Command{ @@ -25,7 +26,7 @@ var cmCmd = &cobra.Command{ cmd.PrintErrln(err) os.Exit(1) } - values := k8s.ComposeValues(cm) + values := k8s.ComposeValues(cm, dataKey) util.WriteValuesToFile(values, output) fmt.Printf("%s written to %s\n", cmName, output) }, @@ -34,5 +35,6 @@ var cmCmd = &cobra.Command{ func init() { rootCmd.AddCommand(cmCmd) cmCmd.PersistentFlags().StringVar(&kubeNamespace, "kube_namespace", "default", "The namespace to get the cm from") + cmCmd.PersistentFlags().StringVar(&dataKey, "dataKey", "dataKey", "The key to get the cm from") cmCmd.PersistentFlags().StringVarP(&output, "out", "o", "values-cm.yaml", "The file to output the values to") } diff --git a/cmd/dowloader_test.go b/cmd/dowloader_test.go index 4c386f3..44a97f1 100644 --- a/cmd/dowloader_test.go +++ b/cmd/dowloader_test.go @@ -15,6 +15,7 @@ func TestParseUrl(t *testing.T) { wantProtocol string wantNamespace string wantConfigMapName string + wantKey string wantErr error }{ { @@ -23,6 +24,7 @@ func TestParseUrl(t *testing.T) { wantProtocol: "cm", wantNamespace: "default", wantConfigMapName: "helm-values", + wantKey: "values.yaml", wantErr: nil, }, { @@ -31,6 +33,16 @@ func TestParseUrl(t *testing.T) { wantProtocol: "cm", wantNamespace: "kuuji", wantConfigMapName: "helm-values", + wantKey: "values.yaml", + wantErr: nil, + }, + { + name: "Should return namespace and name", + args: args{"cm://kuuji/helm-values/values-key"}, + wantProtocol: "cm", + wantNamespace: "kuuji", + wantConfigMapName: "helm-values", + wantKey: "values-key", wantErr: nil, }, { @@ -39,6 +51,7 @@ func TestParseUrl(t *testing.T) { wantProtocol: "cm", wantNamespace: "", wantConfigMapName: "", + wantKey: "", wantErr: errors.New("no config provided after protocol"), }, { @@ -47,12 +60,13 @@ func TestParseUrl(t *testing.T) { wantProtocol: "weird", wantNamespace: "", wantConfigMapName: "", + wantKey: "", wantErr: errors.New(":// missing after protocol"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - gotProtocol, gotNamespace, gotConfigMapName, err := ParseUrl(tt.args.url) + gotProtocol, gotNamespace, gotConfigMapName, gotKey, err := ParseUrl(tt.args.url) if err != nil && errors.Is(err, tt.wantErr) { t.Errorf("ParseUrl() error = %v, wantErr %v", err, tt.wantErr) } @@ -65,6 +79,9 @@ func TestParseUrl(t *testing.T) { if gotConfigMapName != tt.wantConfigMapName { t.Errorf("ParseUrl() gotConfigMapName = %v, want %v", gotConfigMapName, tt.wantConfigMapName) } + if gotKey != tt.wantKey { + t.Errorf("ParseUrl() gotKey = %v, want %v", gotKey, tt.wantKey) + } }) } } diff --git a/cmd/downloader.go b/cmd/downloader.go index f93689d..6e0172b 100644 --- a/cmd/downloader.go +++ b/cmd/downloader.go @@ -16,50 +16,50 @@ var downloaderCmd = &cobra.Command{ Short: "Get value from a remote source and output it to stdout", Long: `Get value from a remote source and output it to stdout. URL is formatted like below -:/// +://// Helm will invoke this command with the url in the 4th parameter. See https://helm.sh/docs/topics/plugins/#downloader-plugins.`, - Args: cobra.ExactArgs(4), + Args: cobra.ExactArgs(5), Run: func(cmd *cobra.Command, args []string) { - protocol, ns, name, err := ParseUrl(args[3]) + protocol, ns, name, key, err := ParseUrl(args[4]) if err != nil { cmd.PrintErrln(err) os.Exit(1) } switch protocol { case "cm": - ComposeCM(ns, name, cmd) + ComposeCM(ns, name, key, cmd) case "secret": - ComposeSecret(ns, name, cmd) + ComposeSecret(ns, name, key, cmd) } }, } -func ComposeSecret(ns string, secretName string, cmd *cobra.Command) { +func ComposeSecret(ns string, secretName string, dataKey string, cmd *cobra.Command) { client := k8s.GetK8sClient() secret, err := k8s.GetSecret(ns, secretName, client) if err != nil { cmd.PrintErrln(err) os.Exit(1) } - values := k8s.ComposeSecretValues(secret) + values := k8s.ComposeSecretValues(secret, dataKey) fmt.Printf("%s\n", values) } -func ComposeCM(ns string, cmName string, cmd *cobra.Command) { +func ComposeCM(ns string, cmName string, dataKey string, cmd *cobra.Command) { client := k8s.GetK8sClient() cm, err := k8s.GetConfigMap(ns, cmName, client) if err != nil { cmd.PrintErrln(err) os.Exit(1) } - values := k8s.ComposeValues(cm) + values := k8s.ComposeValues(cm, dataKey) fmt.Printf("%s\n", values) } -func ParseUrl(url string) (protocol string, namespace string, configMapName string, err error) { +func ParseUrl(url string) (protocol string, namespace string, configMapName string, dataKey string, err error) { parsedUrl := strings.Split(url, "://") protocol = parsedUrl[0] err = nil @@ -74,9 +74,15 @@ func ParseUrl(url string) (protocol string, namespace string, configMapName stri } else if len(config) == 1 { namespace = "default" configMapName = config[0] + dataKey = "values.yaml" + } else if len(config) == 2 { + namespace = config[0] + configMapName = config[1] + dataKey = "values.yaml" } else { namespace = config[0] configMapName = config[1] + dataKey = config[2] } return } diff --git a/cmd/secret.go b/cmd/secret.go index ce2c97b..9767016 100644 --- a/cmd/secret.go +++ b/cmd/secret.go @@ -10,6 +10,7 @@ import ( ) var kubeSecretNamespace string +var dataSecretKey string var secretOutput string // secretCmd represents the secret command @@ -26,7 +27,7 @@ var secretCmd = &cobra.Command{ cmd.PrintErrln(err) os.Exit(1) } - values := k8s.ComposeSecretValues(secret) + values := k8s.ComposeSecretValues(secret, dataSecretKey) util.WriteValuesToFile(values, secretOutput) fmt.Printf("%s written to %s\n", secretName, secretOutput) }, @@ -35,5 +36,6 @@ var secretCmd = &cobra.Command{ func init() { rootCmd.AddCommand(secretCmd) secretCmd.PersistentFlags().StringVar(&kubeSecretNamespace, "kube_namespace", "default", "The namespace to get the secret from") + secretCmd.PersistentFlags().StringVar(&dataSecretKey, "dataKey", "values.yaml", "The key to get the data from a secret") secretCmd.PersistentFlags().StringVarP(&secretOutput, "out", "o", "values-secret.yaml", "The file to output the values to") } diff --git a/util/kubernetes/config-map.go b/util/kubernetes/config-map.go index c8c1a10..e009c0c 100644 --- a/util/kubernetes/config-map.go +++ b/util/kubernetes/config-map.go @@ -16,7 +16,7 @@ func GetConfigMap(namespace string, name string, client Client) (*v1.ConfigMap, return cm, nil } -func ComposeValues(configmap *v1.ConfigMap) (yaml string) { - yaml = configmap.Data["values.yaml"] +func ComposeValues(configmap *v1.ConfigMap, dataKey string) (yaml string) { + yaml = configmap.Data[dataKey] return yaml } diff --git a/util/kubernetes/config-map_test.go b/util/kubernetes/config-map_test.go index a170850..27e270d 100644 --- a/util/kubernetes/config-map_test.go +++ b/util/kubernetes/config-map_test.go @@ -49,6 +49,7 @@ func TestGetConfigMap(t *testing.T) { func TestComposeValues(t *testing.T) { type args struct { configmap *v1.ConfigMap + dataKey string } tests := []struct { name string @@ -63,13 +64,41 @@ func TestComposeValues(t *testing.T) { "values.yaml": "replicas: \"3\"\ndeployment:\n server:\n replicas: \"3\"\n", }, }, + dataKey: "values.yaml", }, want: "replicas: \"3\"\ndeployment:\n server:\n replicas: \"3\"\n", }, + { + name: "Should create file", + args: args{ + configmap: &v1.ConfigMap{ + Data: map[string]string{ + "values.yaml": "replicas: \"3\"\ndeployment:\n server:\n replicas: \"3\"\n", + "test.yaml": "replicas: \"8\"\ndeployment:\n server:\n replicas: \"2\"\n", + "ignore.yaml": "replicas: \"20\"\ndeployment:\n server:\n replicas: \"11\"\n", + }, + }, + dataKey: "test.yaml", + }, + want: "replicas: \"8\"\ndeployment:\n server:\n replicas: \"2\"\n", + }, + { + name: "Should get nothing", + args: args{ + configmap: &v1.ConfigMap{ + Data: map[string]string{ + "test.yaml": "replicas: \"8\"\ndeployment:\n server:\n replicas: \"2\"\n", + "ignore.yaml": "replicas: \"20\"\ndeployment:\n server:\n replicas: \"11\"\n", + }, + }, + dataKey: "values.yaml", + }, + want: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := ComposeValues(tt.args.configmap); !reflect.DeepEqual(got, tt.want) { + if got := ComposeValues(tt.args.configmap, tt.args.dataKey); !reflect.DeepEqual(got, tt.want) { t.Errorf("GetConfigMap() = %v, want %v", got, tt.want) } }) diff --git a/util/kubernetes/secret.go b/util/kubernetes/secret.go index ed76e2d..294e823 100644 --- a/util/kubernetes/secret.go +++ b/util/kubernetes/secret.go @@ -16,7 +16,7 @@ func GetSecret(namespace string, name string, client Client) (*v1.Secret, error) return secret, nil } -func ComposeSecretValues(secret *v1.Secret) (yaml string) { - yaml = string(secret.Data["values.yaml"]) +func ComposeSecretValues(secret *v1.Secret, dataKey string) (yaml string) { + yaml = string(secret.Data[dataKey]) return yaml } diff --git a/util/kubernetes/secret_test.go b/util/kubernetes/secret_test.go index 092bb14..b5cbec8 100644 --- a/util/kubernetes/secret_test.go +++ b/util/kubernetes/secret_test.go @@ -50,6 +50,7 @@ func TestGetSecret(t *testing.T) { func TestComposeSecretValues(t *testing.T) { type args struct { secret *v1.Secret + dataKey string } tests := []struct { name string @@ -64,13 +65,41 @@ func TestComposeSecretValues(t *testing.T) { "values.yaml": []byte("replicas: \"3\"\ndeployment:\n server:\n replicas: \"3\"\n"), }, }, + dataKey: "values.yaml", }, want: "replicas: \"3\"\ndeployment:\n server:\n replicas: \"3\"\n", }, + { + name: "Should get the right value out", + args: args{ + secret: &v1.Secret{ + Data: map[string][]byte{ + "values.yaml": []byte("replicas: \"3\"\ndeployment:\n server:\n replicas: \"3\"\n"), + "test.yaml": []byte("replicas: \"5\"\ndeployment:\n server:\n replicas: \"12\"\n"), + "ignore.yaml": []byte("replicas: \"6\"\ndeployment:\n server:\n replicas: \"14\"\n"), + }, + }, + dataKey: "test.yaml", + }, + want: "replicas: \"5\"\ndeployment:\n server:\n replicas: \"12\"\n", + }, + { + name: "Should get nothing", + args: args{ + secret: &v1.Secret{ + Data: map[string][]byte{ + "test.yaml": []byte("replicas: \"5\"\ndeployment:\n server:\n replicas: \"12\"\n"), + "ignore.yaml": []byte("replicas: \"6\"\ndeployment:\n server:\n replicas: \"14\"\n"), + }, + }, + dataKey: "value.yaml", + }, + want: "", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := ComposeSecretValues(tt.args.secret); !reflect.DeepEqual(got, tt.want) { + if got := ComposeSecretValues(tt.args.secret, tt.args.dataKey); !reflect.DeepEqual(got, tt.want) { t.Errorf("GetConfigMap() = %v, want %v", got, tt.want) } }) From 895d27914bbd9b3f32b889efc1b08a249c3386f2 Mon Sep 17 00:00:00 2001 From: sarmad-abualkaz Date: Sat, 19 Feb 2022 21:39:04 -0500 Subject: [PATCH 2/2] fixes and documentation: - fixes the `dataKey` default for cm to `values.yaml` - adding documetion around newly added `dataKey` flag --- README.md | 9 ++++++--- cmd/cm.go | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 00369e1..5db870e 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ Usage: helm-external-val cm [flags] Flags: + --dataKey string The key to get the cm from (default "values.yaml") -h, --help help for cm --kube_namespace string The namespace to get the cm from (default "default") -o, --out string The file to output the values to (default "values-cm.yaml") @@ -128,6 +129,7 @@ Usage: helm-external-val secret [flags] Flags: + --dataKey string The key to get the data from a secret (default "values.yaml") -h, --help help for secret --kube_namespace string The namespace to get the secret from (default "default") -o, --out string The file to output the values to (default "values-secret.yaml") @@ -140,15 +142,16 @@ Helm will invoke the downloader plugin with 4 parameters `certFile keyFile caFil The url has to be formatted as follows ``` -:/// +://// ``` - source (required) : the protocol to use (`cm` and `secret` are currently supported) - namespace (optional) : the namespace in which to look for the resource (defaults to `default`) - name (required) : the name of the resource to fetch +- key (optional) : the key in which to look for the data in the resource (defaults to `values.yaml`) -for example the url below will fetch the ConfigMap named `helm-values` from the namespace `kuuji`. +for example the url below will fetch the data under `my-values` from ConfigMap named `helm-values` in the namespace `kuuji`. ``` -cm://kuuji/helm-values +cm://kuuji/helm-values/my-values ``` diff --git a/cmd/cm.go b/cmd/cm.go index 0ecd134..af75939 100644 --- a/cmd/cm.go +++ b/cmd/cm.go @@ -35,6 +35,6 @@ var cmCmd = &cobra.Command{ func init() { rootCmd.AddCommand(cmCmd) cmCmd.PersistentFlags().StringVar(&kubeNamespace, "kube_namespace", "default", "The namespace to get the cm from") - cmCmd.PersistentFlags().StringVar(&dataKey, "dataKey", "dataKey", "The key to get the cm from") + cmCmd.PersistentFlags().StringVar(&dataKey, "dataKey", "values.yaml", "The key to get the data from a cm") cmCmd.PersistentFlags().StringVarP(&output, "out", "o", "values-cm.yaml", "The file to output the values to") }