From 476348d695d07bad99757735fb9a4c1b033a0bce Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Mon, 25 Nov 2024 22:35:43 -0800 Subject: [PATCH 1/3] add support for replacing variable items with environment variables --- README.md | 34 ++++++++++++++++++++++++++-------- cmd/bootstrap_fs.go | 21 ++++++++++++--------- cmd/bootstrap_git.go | 31 +++++++++++++++++-------------- pkg/reconcile/reconcile.go | 26 +++++++++++++++++++++----- 4 files changed, 76 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 26c87dc..10a2f89 100644 --- a/README.md +++ b/README.md @@ -26,15 +26,16 @@ Usage: nomoperator bootstrap fs [path] [flags] Flags: - --base-dir string Path to the base directory (default "./") - --delete Enable delete missing jobs - -h, --help help for fs - --path string glob pattern relative to the base-dir (default "**/*.nomad") - --var-path string var glob pattern relative to the base-dir (default "**/*.vars.yml") - --watch Enable watch mode + --base-dir string Path to the base directory (default "./") + --delete Enable delete missing jobs + -h, --help help for fs + --path string glob pattern relative to the base-dir (default "**/*.nomad") + --var-env-prefix string prefix used for environment variable replacement (default "env:") + --var-path string var glob pattern relative to the base-dir (default "**/*.vars.yml") + --watch Enable watch mode Global Flags: - -a, --address string Address of the Nomad server + -a, --address string Address of the Nomad server ``` Use it like this: @@ -62,6 +63,7 @@ Flags: --ssh-key string SSH private key --url string git repository URL --username string SSH username (default "git") + --var-env-prefix string prefix used for environment variable replacement (default "env:") --var-path string var glob pattern relative to the repository root (default "**/*.vars.yml") --watch Enable watch mode (default true) @@ -158,7 +160,7 @@ EOF ## Variables -Variables are yml files. All keys and values in items should be of type string. +Variables are yaml files. All keys and values in items should be of type string. ```yaml path: nomad/jobs/jobname @@ -166,3 +168,19 @@ items: key1: "value1" key2: "value2" ``` + +To replace a value for items in the variable files by an environment variable, use the prefix defined in `--var-env-prefix` which defaults to `env:`. +This allows you to safely store variables without exposing sensitive information. + +```yaml +path: nomad/jobs/jobname +items: + username: "john" + password: "env:PASSWORD" +``` + +You can use tools such as [dotenvx](https://dotenvx.com) to store encrypted environment variables. Then run nomoperator with dotenvx. Refer to the dotenvx documentation for more information. + +```bash +dotenvx run -- nomoperator bootstrap fs --base-dir /path/to/base/dir --path jobs/*.nomad +``` diff --git a/cmd/bootstrap_fs.go b/cmd/bootstrap_fs.go index b4586e8..29b2858 100644 --- a/cmd/bootstrap_fs.go +++ b/cmd/bootstrap_fs.go @@ -9,11 +9,12 @@ import ( ) type fsFlags struct { - base_dir string - path string - var_path string - watch bool - delete bool + base_dir string + path string + var_path string + var_env_prefix string + watch bool + delete bool } var fsArgs fsFlags @@ -23,6 +24,7 @@ func init() { bootstrapFsCmd.Flags().StringVar(&fsArgs.base_dir, "base-dir", "./", "Path to the base directory") bootstrapFsCmd.Flags().StringVar(&fsArgs.path, "path", "**/*.nomad", "glob pattern relative to the base-dir") bootstrapFsCmd.Flags().StringVar(&fsArgs.var_path, "var-path", "**/*.vars.yml", "var glob pattern relative to the base-dir") + bootstrapFsCmd.Flags().StringVar(&fsArgs.var_env_prefix, "var-env-prefix", "env:", "prefix used for environment variable replacement") bootstrapFsCmd.Flags().BoolVar(&fsArgs.watch, "watch", false, "Enable watch mode") bootstrapFsCmd.Flags().BoolVar(&fsArgs.delete, "delete", false, "Enable delete missing jobs") } @@ -33,10 +35,11 @@ var bootstrapFsCmd = &cobra.Command{ Long: ``, RunE: func(cmd *cobra.Command, args []string) error { return reconcile.Run(reconcile.ReconcileOptions{ - Path: fsArgs.path, - VarPath: fsArgs.var_path, - Watch: fsArgs.watch, - Delete: fsArgs.delete, + Path: fsArgs.path, + VarPath: fsArgs.var_path, + VarEnvPrefix: fsArgs.var_env_prefix, + Watch: fsArgs.watch, + Delete: fsArgs.delete, Fs: func() (billy.Filesystem, error) { fs := osfs.New(fsArgs.base_dir) return fs, nil diff --git a/cmd/bootstrap_git.go b/cmd/bootstrap_git.go index 51ffaac..7774c55 100644 --- a/cmd/bootstrap_git.go +++ b/cmd/bootstrap_git.go @@ -15,16 +15,17 @@ import ( ) type gitFlags struct { - url string - branch string - path string - var_path string - username string - password string - sshkey string - sshinsecure bool - watch bool - delete bool + url string + branch string + path string + var_path string + var_env_prefix string + username string + password string + sshkey string + sshinsecure bool + watch bool + delete bool } var gitArgs gitFlags @@ -35,6 +36,7 @@ func init() { bootstrapGitCmd.Flags().StringVar(&gitArgs.branch, "branch", "main", "git branch") bootstrapGitCmd.Flags().StringVar(&gitArgs.path, "path", "**/*.nomad", "glob pattern relative to the repository root") bootstrapGitCmd.Flags().StringVar(&gitArgs.var_path, "var-path", "**/*.vars.yml", "var glob pattern relative to the repository root") + bootstrapGitCmd.Flags().StringVar(&fsArgs.var_env_prefix, "var-env-prefix", "env:", "prefix used for environment variable replacement") bootstrapGitCmd.Flags().StringVar(&gitArgs.username, "username", "git", "SSH username") bootstrapGitCmd.Flags().StringVar(&gitArgs.password, "password", "", "SSH private key password") bootstrapGitCmd.Flags().StringVar(&gitArgs.sshkey, "ssh-key", "", "SSH private key") @@ -50,10 +52,11 @@ var bootstrapGitCmd = &cobra.Command{ // Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return reconcile.Run(reconcile.ReconcileOptions{ - Path: gitArgs.path, - VarPath: gitArgs.var_path, - Watch: gitArgs.watch, - Delete: gitArgs.delete, + Path: gitArgs.path, + VarPath: gitArgs.var_path, + VarEnvPrefix: fsArgs.var_env_prefix, + Watch: gitArgs.watch, + Delete: gitArgs.delete, Fs: func() (billy.Filesystem, error) { repositoryURL, err := url.Parse(gitArgs.url) if err != nil { diff --git a/pkg/reconcile/reconcile.go b/pkg/reconcile/reconcile.go index e296cdf..978a555 100644 --- a/pkg/reconcile/reconcile.go +++ b/pkg/reconcile/reconcile.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "os" "strings" "time" @@ -18,11 +19,12 @@ import ( ) type ReconcileOptions struct { - Path string - VarPath string - Watch bool - Delete bool - Fs func() (billy.Filesystem, error) + Path string + VarPath string + VarEnvPrefix string + Watch bool + Delete bool + Fs func() (billy.Filesystem, error) } func Run(opts ReconcileOptions) error { @@ -90,6 +92,20 @@ func Run(opts ReconcileOptions) error { // Update variable fmt.Printf("Updating vars [%s]\n", newVariable.Path) + + if opts.VarEnvPrefix != "" { + // Update all items that prefix with VarEnvPrefix with environment variables. + // If the environment variable doesn't exist value will be set to empty string when updating the variable. + for key, value := range newVariable.Items { + if strings.HasPrefix(value, opts.VarEnvPrefix) { + envVar := strings.TrimPrefix(value, opts.VarEnvPrefix) + envValue := os.Getenv(envVar) + fmt.Printf("Updating vars [%s][%s] with environment variable [%s]\n", newVariable.Path, key, envVar) + newVariable.Items[key] = envValue + } + } + } + err = client.UpdateVariable(&newVariable) if err != nil { return err From edffb761f97835cf8d1059eee50c3cac0e06b921 Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Tue, 26 Nov 2024 19:56:04 -0800 Subject: [PATCH 2/3] rename to env-prefix instead of var-env-prefix so it can be used for replacements for other future files --- README.md | 20 ++++++++++---------- cmd/bootstrap_fs.go | 24 ++++++++++++------------ cmd/bootstrap_git.go | 34 +++++++++++++++++----------------- pkg/reconcile/reconcile.go | 22 +++++++++++----------- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 10a2f89..473af1f 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,16 @@ Usage: nomoperator bootstrap fs [path] [flags] Flags: - --base-dir string Path to the base directory (default "./") - --delete Enable delete missing jobs - -h, --help help for fs - --path string glob pattern relative to the base-dir (default "**/*.nomad") - --var-env-prefix string prefix used for environment variable replacement (default "env:") - --var-path string var glob pattern relative to the base-dir (default "**/*.vars.yml") - --watch Enable watch mode + --base-dir string Path to the base directory (default "./") + --delete Enable delete missing jobs + --env-prefix string prefix used for environment variable replacement (default "env:") + -h, --help help for fs + --path string glob pattern relative to the base-dir (default "**/*.nomad") + --var-path string var glob pattern relative to the base-dir (default "**/*.vars.yml") + --watch Enable watch mode Global Flags: - -a, --address string Address of the Nomad server + -a, --address string Address of the Nomad server ``` Use it like this: @@ -56,6 +56,7 @@ Usage: Flags: --branch string git branch (default "main") --delete Enable delete missing jobs (default true) + --env-prefix string prefix used for environment variable replacement (default "env:") -h, --help help for git --password string SSH private key password --path string glob pattern relative to the repository root (default "**/*.nomad") @@ -63,7 +64,6 @@ Flags: --ssh-key string SSH private key --url string git repository URL --username string SSH username (default "git") - --var-env-prefix string prefix used for environment variable replacement (default "env:") --var-path string var glob pattern relative to the repository root (default "**/*.vars.yml") --watch Enable watch mode (default true) @@ -169,7 +169,7 @@ items: key2: "value2" ``` -To replace a value for items in the variable files by an environment variable, use the prefix defined in `--var-env-prefix` which defaults to `env:`. +To replace a value for items in the variable files by an environment variable, use the prefix defined in `--env-prefix` which defaults to `env:`. This allows you to safely store variables without exposing sensitive information. ```yaml diff --git a/cmd/bootstrap_fs.go b/cmd/bootstrap_fs.go index 29b2858..e5237e7 100644 --- a/cmd/bootstrap_fs.go +++ b/cmd/bootstrap_fs.go @@ -9,12 +9,12 @@ import ( ) type fsFlags struct { - base_dir string - path string - var_path string - var_env_prefix string - watch bool - delete bool + base_dir string + path string + var_path string + env_prefix string + watch bool + delete bool } var fsArgs fsFlags @@ -24,7 +24,7 @@ func init() { bootstrapFsCmd.Flags().StringVar(&fsArgs.base_dir, "base-dir", "./", "Path to the base directory") bootstrapFsCmd.Flags().StringVar(&fsArgs.path, "path", "**/*.nomad", "glob pattern relative to the base-dir") bootstrapFsCmd.Flags().StringVar(&fsArgs.var_path, "var-path", "**/*.vars.yml", "var glob pattern relative to the base-dir") - bootstrapFsCmd.Flags().StringVar(&fsArgs.var_env_prefix, "var-env-prefix", "env:", "prefix used for environment variable replacement") + bootstrapFsCmd.Flags().StringVar(&fsArgs.env_prefix, "env-prefix", "env:", "prefix used for environment variable replacement") bootstrapFsCmd.Flags().BoolVar(&fsArgs.watch, "watch", false, "Enable watch mode") bootstrapFsCmd.Flags().BoolVar(&fsArgs.delete, "delete", false, "Enable delete missing jobs") } @@ -35,11 +35,11 @@ var bootstrapFsCmd = &cobra.Command{ Long: ``, RunE: func(cmd *cobra.Command, args []string) error { return reconcile.Run(reconcile.ReconcileOptions{ - Path: fsArgs.path, - VarPath: fsArgs.var_path, - VarEnvPrefix: fsArgs.var_env_prefix, - Watch: fsArgs.watch, - Delete: fsArgs.delete, + Path: fsArgs.path, + VarPath: fsArgs.var_path, + EnvPrefix: fsArgs.env_prefix, + Watch: fsArgs.watch, + Delete: fsArgs.delete, Fs: func() (billy.Filesystem, error) { fs := osfs.New(fsArgs.base_dir) return fs, nil diff --git a/cmd/bootstrap_git.go b/cmd/bootstrap_git.go index 7774c55..a147a04 100644 --- a/cmd/bootstrap_git.go +++ b/cmd/bootstrap_git.go @@ -15,17 +15,17 @@ import ( ) type gitFlags struct { - url string - branch string - path string - var_path string - var_env_prefix string - username string - password string - sshkey string - sshinsecure bool - watch bool - delete bool + url string + branch string + path string + var_path string + env_prefix string + username string + password string + sshkey string + sshinsecure bool + watch bool + delete bool } var gitArgs gitFlags @@ -36,7 +36,7 @@ func init() { bootstrapGitCmd.Flags().StringVar(&gitArgs.branch, "branch", "main", "git branch") bootstrapGitCmd.Flags().StringVar(&gitArgs.path, "path", "**/*.nomad", "glob pattern relative to the repository root") bootstrapGitCmd.Flags().StringVar(&gitArgs.var_path, "var-path", "**/*.vars.yml", "var glob pattern relative to the repository root") - bootstrapGitCmd.Flags().StringVar(&fsArgs.var_env_prefix, "var-env-prefix", "env:", "prefix used for environment variable replacement") + bootstrapGitCmd.Flags().StringVar(&fsArgs.env_prefix, "env-prefix", "env:", "prefix used for environment variable replacement") bootstrapGitCmd.Flags().StringVar(&gitArgs.username, "username", "git", "SSH username") bootstrapGitCmd.Flags().StringVar(&gitArgs.password, "password", "", "SSH private key password") bootstrapGitCmd.Flags().StringVar(&gitArgs.sshkey, "ssh-key", "", "SSH private key") @@ -52,11 +52,11 @@ var bootstrapGitCmd = &cobra.Command{ // Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return reconcile.Run(reconcile.ReconcileOptions{ - Path: gitArgs.path, - VarPath: gitArgs.var_path, - VarEnvPrefix: fsArgs.var_env_prefix, - Watch: gitArgs.watch, - Delete: gitArgs.delete, + Path: gitArgs.path, + VarPath: gitArgs.var_path, + EnvPrefix: fsArgs.env_prefix, + Watch: gitArgs.watch, + Delete: gitArgs.delete, Fs: func() (billy.Filesystem, error) { repositoryURL, err := url.Parse(gitArgs.url) if err != nil { diff --git a/pkg/reconcile/reconcile.go b/pkg/reconcile/reconcile.go index 978a555..50cab52 100644 --- a/pkg/reconcile/reconcile.go +++ b/pkg/reconcile/reconcile.go @@ -19,12 +19,12 @@ import ( ) type ReconcileOptions struct { - Path string - VarPath string - VarEnvPrefix string - Watch bool - Delete bool - Fs func() (billy.Filesystem, error) + Path string + VarPath string + EnvPrefix string + Watch bool + Delete bool + Fs func() (billy.Filesystem, error) } func Run(opts ReconcileOptions) error { @@ -93,12 +93,12 @@ func Run(opts ReconcileOptions) error { // Update variable fmt.Printf("Updating vars [%s]\n", newVariable.Path) - if opts.VarEnvPrefix != "" { - // Update all items that prefix with VarEnvPrefix with environment variables. - // If the environment variable doesn't exist value will be set to empty string when updating the variable. + if opts.EnvPrefix != "" { + // Update all items that prefix with EnvPrefix with environment variables. + // If the environment variable doesn't exist, value will be set to empty string when updating the variable. for key, value := range newVariable.Items { - if strings.HasPrefix(value, opts.VarEnvPrefix) { - envVar := strings.TrimPrefix(value, opts.VarEnvPrefix) + if strings.HasPrefix(value, opts.EnvPrefix) { + envVar := strings.TrimPrefix(value, opts.EnvPrefix) envValue := os.Getenv(envVar) fmt.Printf("Updating vars [%s][%s] with environment variable [%s]\n", newVariable.Path, key, envVar) newVariable.Items[key] = envValue From 04b33fbad5307038fdab914604200bc969f60b6f Mon Sep 17 00:00:00 2001 From: Prabir Shrestha Date: Tue, 26 Nov 2024 21:52:53 -0800 Subject: [PATCH 3/3] use <<(VARIABLE)>> template to replace values instead of prefix so it can potentially work for other files such as nomad job files in the future --- README.md | 21 +++++++++-------- cmd/bootstrap_fs.go | 24 ++++++++++---------- cmd/bootstrap_git.go | 12 +++++----- pkg/reconcile/reconcile.go | 46 ++++++++++++++++++++++++++------------ 4 files changed, 60 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 473af1f..daefb70 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,13 @@ Usage: nomoperator bootstrap fs [path] [flags] Flags: - --base-dir string Path to the base directory (default "./") - --delete Enable delete missing jobs - --env-prefix string prefix used for environment variable replacement (default "env:") - -h, --help help for fs - --path string glob pattern relative to the base-dir (default "**/*.nomad") - --var-path string var glob pattern relative to the base-dir (default "**/*.vars.yml") - --watch Enable watch mode + --base-dir string Path to the base directory (default "./") + --delete Enable delete missing jobs + --env-template string template used for environment variable replacement (default "<<\\$env\\(([A-Z0-9_]+)\\)>>") + -h, --help help for fs + --path string glob pattern relative to the base-dir (default "**/*.nomad") + --var-path string var glob pattern relative to the base-dir (default "**/*.vars.yml") + --watch Enable watch mode Global Flags: -a, --address string Address of the Nomad server @@ -56,7 +56,7 @@ Usage: Flags: --branch string git branch (default "main") --delete Enable delete missing jobs (default true) - --env-prefix string prefix used for environment variable replacement (default "env:") + --env-template string template used for environment variable replacement (default "<<\\$env\\(([A-Z0-9_]+)\\)>>") -h, --help help for git --password string SSH private key password --path string glob pattern relative to the repository root (default "**/*.nomad") @@ -169,14 +169,13 @@ items: key2: "value2" ``` -To replace a value for items in the variable files by an environment variable, use the prefix defined in `--env-prefix` which defaults to `env:`. -This allows you to safely store variables without exposing sensitive information. +To replace a value for items in the variable files by an environment variable, use the prefix defined in `--env-template` which defaults to `<<$env(VARIABLE)>>`. ```yaml path: nomad/jobs/jobname items: username: "john" - password: "env:PASSWORD" + password: "<<$env(PASSWORD)>>" ``` You can use tools such as [dotenvx](https://dotenvx.com) to store encrypted environment variables. Then run nomoperator with dotenvx. Refer to the dotenvx documentation for more information. diff --git a/cmd/bootstrap_fs.go b/cmd/bootstrap_fs.go index e5237e7..9a58c0d 100644 --- a/cmd/bootstrap_fs.go +++ b/cmd/bootstrap_fs.go @@ -9,12 +9,12 @@ import ( ) type fsFlags struct { - base_dir string - path string - var_path string - env_prefix string - watch bool - delete bool + base_dir string + path string + var_path string + env_template string + watch bool + delete bool } var fsArgs fsFlags @@ -24,7 +24,7 @@ func init() { bootstrapFsCmd.Flags().StringVar(&fsArgs.base_dir, "base-dir", "./", "Path to the base directory") bootstrapFsCmd.Flags().StringVar(&fsArgs.path, "path", "**/*.nomad", "glob pattern relative to the base-dir") bootstrapFsCmd.Flags().StringVar(&fsArgs.var_path, "var-path", "**/*.vars.yml", "var glob pattern relative to the base-dir") - bootstrapFsCmd.Flags().StringVar(&fsArgs.env_prefix, "env-prefix", "env:", "prefix used for environment variable replacement") + bootstrapFsCmd.Flags().StringVar(&fsArgs.env_template, "env-template", "<<\\$env\\(([A-Z0-9_]+)\\)>>", "template used for environment variable replacement") bootstrapFsCmd.Flags().BoolVar(&fsArgs.watch, "watch", false, "Enable watch mode") bootstrapFsCmd.Flags().BoolVar(&fsArgs.delete, "delete", false, "Enable delete missing jobs") } @@ -35,11 +35,11 @@ var bootstrapFsCmd = &cobra.Command{ Long: ``, RunE: func(cmd *cobra.Command, args []string) error { return reconcile.Run(reconcile.ReconcileOptions{ - Path: fsArgs.path, - VarPath: fsArgs.var_path, - EnvPrefix: fsArgs.env_prefix, - Watch: fsArgs.watch, - Delete: fsArgs.delete, + Path: fsArgs.path, + VarPath: fsArgs.var_path, + EnvTemplate: fsArgs.env_template, + Watch: fsArgs.watch, + Delete: fsArgs.delete, Fs: func() (billy.Filesystem, error) { fs := osfs.New(fsArgs.base_dir) return fs, nil diff --git a/cmd/bootstrap_git.go b/cmd/bootstrap_git.go index a147a04..ce09dfd 100644 --- a/cmd/bootstrap_git.go +++ b/cmd/bootstrap_git.go @@ -36,7 +36,7 @@ func init() { bootstrapGitCmd.Flags().StringVar(&gitArgs.branch, "branch", "main", "git branch") bootstrapGitCmd.Flags().StringVar(&gitArgs.path, "path", "**/*.nomad", "glob pattern relative to the repository root") bootstrapGitCmd.Flags().StringVar(&gitArgs.var_path, "var-path", "**/*.vars.yml", "var glob pattern relative to the repository root") - bootstrapGitCmd.Flags().StringVar(&fsArgs.env_prefix, "env-prefix", "env:", "prefix used for environment variable replacement") + bootstrapGitCmd.Flags().StringVar(&fsArgs.env_template, "env-template", "<<\\$env\\(([A-Z0-9_]+)\\)>>", "template used for environment variable replacement") bootstrapGitCmd.Flags().StringVar(&gitArgs.username, "username", "git", "SSH username") bootstrapGitCmd.Flags().StringVar(&gitArgs.password, "password", "", "SSH private key password") bootstrapGitCmd.Flags().StringVar(&gitArgs.sshkey, "ssh-key", "", "SSH private key") @@ -52,11 +52,11 @@ var bootstrapGitCmd = &cobra.Command{ // Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return reconcile.Run(reconcile.ReconcileOptions{ - Path: gitArgs.path, - VarPath: gitArgs.var_path, - EnvPrefix: fsArgs.env_prefix, - Watch: gitArgs.watch, - Delete: gitArgs.delete, + Path: gitArgs.path, + VarPath: gitArgs.var_path, + EnvTemplate: fsArgs.env_template, + Watch: gitArgs.watch, + Delete: gitArgs.delete, Fs: func() (billy.Filesystem, error) { repositoryURL, err := url.Parse(gitArgs.url) if err != nil { diff --git a/pkg/reconcile/reconcile.go b/pkg/reconcile/reconcile.go index 50cab52..4eb70c3 100644 --- a/pkg/reconcile/reconcile.go +++ b/pkg/reconcile/reconcile.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "regexp" "strings" "time" @@ -19,12 +20,12 @@ import ( ) type ReconcileOptions struct { - Path string - VarPath string - EnvPrefix string - Watch bool - Delete bool - Fs func() (billy.Filesystem, error) + Path string + VarPath string + EnvTemplate string + Watch bool + Delete bool + Fs func() (billy.Filesystem, error) } func Run(opts ReconcileOptions) error { @@ -93,15 +94,14 @@ func Run(opts ReconcileOptions) error { // Update variable fmt.Printf("Updating vars [%s]\n", newVariable.Path) - if opts.EnvPrefix != "" { - // Update all items that prefix with EnvPrefix with environment variables. - // If the environment variable doesn't exist, value will be set to empty string when updating the variable. + if opts.EnvTemplate != "" { + // Update variable items with environment variables + pattern := regexp.MustCompile(opts.EnvTemplate) for key, value := range newVariable.Items { - if strings.HasPrefix(value, opts.EnvPrefix) { - envVar := strings.TrimPrefix(value, opts.EnvPrefix) - envValue := os.Getenv(envVar) - fmt.Printf("Updating vars [%s][%s] with environment variable [%s]\n", newVariable.Path, key, envVar) - newVariable.Items[key] = envValue + replaced, newValue := replaceTemplateVariables(value, pattern) + if replaced { + fmt.Printf("Updating vars [%s] [%s] with environment variable\n", newVariable.Path, key) + newVariable.Items[key] = newValue } } } @@ -266,3 +266,21 @@ func keys[K comparable, V any](m map[K]V) []K { } return keys } + +func replaceTemplateVariables(input string, pattern *regexp.Regexp) (bool, string) { + replaced := false + + // Replace variables using the custom syntax + result := pattern.ReplaceAllStringFunc(input, func(match string) string { + replaced = true + // Extract variable name + name := pattern.FindStringSubmatch(match)[1] + // Get the environment variable value or use a default if not set + if value, exists := os.LookupEnv(name); exists { + return value + } + return "" // Default value if the variable is not found + }) + + return replaced, result +}