diff --git a/README.md b/README.md index 430ad0a4..7d247e00 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ Usage: | -cert-manager-install-crd | Allows the user to install cert-manager CRD as part of the cert-manager subchart.(default "true") | `helmify -cert-manager-install-crd` | | -preserve-ns | Allows users to use the object's original namespace instead of adding all the resources to a common namespace. (default "false") | `helmify -preserve-ns` | | -add-webhook-option | Adds an option to enable/disable webhook installation | `helmify -add-webhook-option`| +| -optional-secrets | Secrets whose values should not be required by helm | `helmify -optional-secrets mysecret1,mysecret2`| ## Status Supported k8s resources: - Deployment, DaemonSet, StatefulSet diff --git a/cmd/helmify/flags.go b/cmd/helmify/flags.go index 7534afb2..30ea9570 100644 --- a/cmd/helmify/flags.go +++ b/cmd/helmify/flags.go @@ -53,6 +53,7 @@ func (i *arrayFlags) Set(value string) error { // ReadFlags command-line flags into app config. func ReadFlags() config.Config { files := arrayFlags{} + optionalSecrets := arrayFlags{} result := config.Config{} var h, help, version, crd, preservens bool flag.BoolVar(&h, "h", false, "Print help. Example: helmify -h") @@ -69,6 +70,7 @@ func ReadFlags() config.Config { flag.BoolVar(&result.FilesRecursively, "r", false, "Scan dirs from -f option recursively") flag.BoolVar(&result.OriginalName, "original-name", false, "Use the object's original name instead of adding the chart's release name as the common prefix.") flag.Var(&files, "f", "File or directory containing k8s manifests") + flag.Var(&optionalSecrets, "optional-secrets", "List of secrets to be templated as optional (their values will not be required).") flag.BoolVar(&preservens, "preserve-ns", false, "Use the object's original namespace instead of adding all the resources to a common namespace") flag.BoolVar(&result.AddWebhookOption, "add-webhook-option", false, "Allows the user to add webhook option in values.yaml") @@ -94,5 +96,6 @@ func ReadFlags() config.Config { result.PreserveNs = true } result.Files = files + result.OptionalSecrets = optionalSecrets return result } diff --git a/pkg/config/config.go b/pkg/config/config.go index b0f9a8ad..6ad3789e 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -43,6 +43,8 @@ type Config struct { PreserveNs bool // AddWebhookOption enables the generation of a webhook option in values.yamlß AddWebhookOption bool + // OptionalSecrets - list of secrets that are optional and should only be generated if values are given + OptionalSecrets []string } func (c *Config) Validate() error { diff --git a/pkg/helmify/values.go b/pkg/helmify/values.go index 86dfe7f6..68fadb6a 100644 --- a/pkg/helmify/values.go +++ b/pkg/helmify/values.go @@ -1,11 +1,12 @@ package helmify import ( - "dario.cat/mergo" "fmt" "strconv" "strings" + "dario.cat/mergo" + "github.com/iancoleman/strcase" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -71,12 +72,15 @@ func (v *Values) AddYaml(value interface{}, indent int, newLine bool, name ...st // AddSecret - adds empty value to values and returns its helm template representation {{ required "" .Values. }}. // Set toBase64=true for Secret data to be base64 encoded and set false for Secret stringData. -func (v *Values) AddSecret(toBase64 bool, name ...string) (string, error) { +func (v *Values) AddSecret(toBase64 bool, optionalSecret bool, name ...string) (string, error) { name = toCamelCase(name) nameStr := strings.Join(name, ".") - err := unstructured.SetNestedField(*v, "", name...) - if err != nil { - return "", fmt.Errorf("%w: unable to set value: %v", err, nameStr) + var err error = nil + if !optionalSecret { + err = unstructured.SetNestedField(*v, "", name...) + if err != nil { + return "", fmt.Errorf("%w: unable to set value: %v", err, nameStr) + } } res := fmt.Sprintf(`{{ required "%[1]s is required" .Values.%[1]s`, nameStr) if toBase64 { diff --git a/pkg/helmify/values_test.go b/pkg/helmify/values_test.go index 6d1c0107..b5f85d3b 100644 --- a/pkg/helmify/values_test.go +++ b/pkg/helmify/values_test.go @@ -71,13 +71,13 @@ func TestValues_Add(t *testing.T) { func TestValues_AddSecret(t *testing.T) { t.Run("add base64 enc secret", func(t *testing.T) { testVal := Values{} - res, err := testVal.AddSecret(true, "a", "b") + res, err := testVal.AddSecret(true, false, "a", "b") assert.NoError(t, err) assert.Contains(t, res, "b64enc") }) t.Run("add not encoded secret", func(t *testing.T) { testVal := Values{} - res, err := testVal.AddSecret(false, "a", "b") + res, err := testVal.AddSecret(false, false, "a", "b") assert.NoError(t, err) assert.NotContains(t, res, "b64enc") }) diff --git a/pkg/processor/secret/secret.go b/pkg/processor/secret/secret.go index 7dec4de9..8c7b0b56 100644 --- a/pkg/processor/secret/secret.go +++ b/pkg/processor/secret/secret.go @@ -2,11 +2,13 @@ package secret import ( "fmt" - "github.com/arttor/helmify/pkg/format" "io" + "slices" "strings" "text/template" + "github.com/arttor/helmify/pkg/format" + "github.com/arttor/helmify/pkg/processor" "github.com/arttor/helmify/pkg/helmify" @@ -18,17 +20,24 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" ) -var secretTempl, _ = template.New("secret").Parse( - `{{ .Meta }} -{{- if .Data }} -{{ .Data }} +// Template uses << and >> as delimiters because if we used the default ones "{{" and ""}}" +// we would not be able ton insert {{- end }} in the template. +var secretTempl, _ = template.New("secret").Delims("<<", ">>").Parse(`<< if .Optional ->> +<< .Optional >> +<< end ->> +<< .Meta >> +<<- if .Data >> +<< .Data >> +<<- end >> +<<- if .StringData >> +<< .StringData >> +<<- end >> +<<- if .Type >> +<< .Type >> +<<- end >> +<<- if .Optional >> {{- end }} -{{- if .StringData }} -{{ .StringData }} -{{- end }} -{{- if .Type }} -{{ .Type }} -{{- end }}`) +<<- end >>`) var configMapGVC = schema.GroupVersionKind{ Group: "", @@ -60,7 +69,11 @@ func (d secret) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructu name := appMeta.TrimName(obj.GetName()) nameCamelCase := strcase.ToLowerCamel(name) - + isOptional := slices.Contains(appMeta.Config().OptionalSecrets, obj.GetName()) + var optionalData string + if isOptional { + optionalData = fmt.Sprintf("{{- if not (empty .Values.%s) }}", nameCamelCase) + } secretType := string(sec.Type) if secretType != "" { secretType, err = yamlformat.Marshal(map[string]interface{}{"type": secretType}, 0) @@ -77,7 +90,7 @@ func (d secret) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructu if key == strings.ToUpper(key) { keyCamelCase = strcase.ToLowerCamel(strings.ToLower(key)) } - templatedName, err := values.AddSecret(true, nameCamelCase, keyCamelCase) + templatedName, err := values.AddSecret(true, isOptional, nameCamelCase, keyCamelCase) if err != nil { return true, nil, fmt.Errorf("%w: unable add secret to values", err) } @@ -98,7 +111,7 @@ func (d secret) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructu if key == strings.ToUpper(key) { keyCamelCase = strcase.ToLowerCamel(strings.ToLower(key)) } - templatedName, err := values.AddSecret(false, nameCamelCase, keyCamelCase) + templatedName, err := values.AddSecret(false, isOptional, nameCamelCase, keyCamelCase) if err != nil { return true, nil, fmt.Errorf("%w: unable add secret to values", err) } @@ -120,7 +133,8 @@ func (d secret) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructu Meta string Data string StringData string - }{Type: secretType, Meta: meta, Data: data, StringData: stringData}, + Optional string + }{Type: secretType, Meta: meta, Data: data, StringData: stringData, Optional: optionalData}, values: values, }, nil } @@ -132,6 +146,7 @@ type result struct { Meta string Data string StringData string + Optional string } values helmify.Values }