From d66c8c204507ff75e27151ef868af5d48b0bbf43 Mon Sep 17 00:00:00 2001 From: Lewis Goettner Date: Thu, 21 Dec 2023 09:08:38 -0500 Subject: [PATCH] Major Refactor & KV2 Support - Added new pattern for KV reading with support for KV versions 1 and 2 - Switch CLI Framework to Cobra - Switch to Vault Client Library - Upgrade Go version & Libraries #minor --- .github/workflows/tag.yml | 4 +- Makefile | 3 +- README.md | 102 +++++++------ buildenv.go | 273 ----------------------------------- cmd/mlock-linux.go | 19 +++ cmd/mlock-non-linux.go | 9 ++ cmd/root.go | 151 +++++++++++++++++++ docker-compose.yml | 4 +- go.mod | 46 ++++-- go.sum | 227 +++++++++++------------------ main.go | 10 ++ mlock-win.go | 17 --- mlock.go | 22 --- reader/reader.go | 273 +++++++++++++++++++++++++++++++++++ reader/reader_test.go | 297 ++++++++++++++++++++++++++++++++++++++ variables.yml | 31 ++-- 16 files changed, 967 insertions(+), 521 deletions(-) delete mode 100644 buildenv.go create mode 100644 cmd/mlock-linux.go create mode 100644 cmd/mlock-non-linux.go create mode 100644 cmd/root.go create mode 100644 main.go delete mode 100644 mlock-win.go delete mode 100644 mlock.go create mode 100644 reader/reader.go create mode 100644 reader/reader_test.go diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml index 1220124..72c8d11 100644 --- a/.github/workflows/tag.yml +++ b/.github/workflows/tag.yml @@ -26,10 +26,10 @@ jobs: body: GitHub Actions Release draft: false prerelease: false - - name: Set up Go 1.16 + - name: Set up Go 1.21 uses: actions/setup-go@v1 with: - go-version: 1.16 + go-version: 1.21 id: go - name: Check out new tag into the Go module directory uses: actions/checkout@v2 diff --git a/Makefile b/Makefile index 2689802..993d56f 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,7 @@ PROJECT_NAME := buildenv all: clean build-deps build build-deps: - go get github.com/mitchellh/gox - go get github.com/aktau/github-release + go install github.com/mitchellh/gox@latest build: CGO_ENABLED=0 gox -ldflags "-X main.version=$(VERSION)" -osarch="darwin/amd64 darwin/arm64 linux/386 linux/amd64 linux/arm linux/arm64 windows/386 windows/amd64" -output "pkg/{{.OS}}_{{.Arch}}/$(PROJECT_NAME)" diff --git a/README.md b/README.md index dbb80a9..9562550 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ buildenv ======== -A tool for generating environment exports from a YAML file. _Now with vault integration!_ +A tool for generating environment exports from a YAML file. Variables can be set in plain test, or by specifying vault key-value (version 2) paths and keys (`kv_secrets`) or the older generic / kv paths (`secrets`) where the key name "value" is assumed. Usage ----- @@ -9,55 +9,66 @@ Usage Given a `variables.yml` file like this: ```yaml --- - vars: - GLOBAL: "global" - - secrets: - SECRET_TEST: "secret/test" - - environments: - stage: - vars: - ENVIRONMENT: "stage" - - secrets: - ANOTHER_SECRET: "secret/test2" - - dcs: - ndc_one: - secrets: - YET_ANOTHER_SECRET: "secret/test3" - vars: - DC: "one" - - ndc_two: - secrets: - YET_ANOTHER_SECRET: "secret/test3" - vars: - DC: "one" +vars: + GLOBAL: "global" + +secrets: + GENERIC_SECRET: "gen/test" + KV_SECRET: "old/test" + KV2_SECRET: "secret/oldstyle" + +kv_secrets: + - path: "secret/test" + vars: + KV2_ONE: "one" + KV2_TWO: "two" + - path: "old/test" + vars: + KV1: "value" + - path: "gen/test" + vars: + KV_GENERIC: "value" + +environments: + stage: + vars: + ENVIRONMENT: "stage" + + secrets: + ANOTHER_SECRET: "secret/oldstyle" + + dcs: + ndc_one: + vars: + DC: "one" + kv_secrets: + - path: "old/test" + vars: + KV2_THREE: "three" ``` Output would look like this: ``` -% buildenv -e stage -d ndc_one -# Setting Variables for: -# Environment: stage -# Datacenter: ndc_one -# Global Vars: +% buildenv -c -e stage -d ndc_one +# Global Variables export GLOBAL="global" -# Global Secrets: -export SECRET_TEST="It Works" # secret/test -# Environment (stage) Vars: +export KV2_ONE="1" # Path: secret/test, Key: one +export KV2_TWO="2" # Path: secret/test, Key: two +export KV1="old" # Path: old/test, Key: value +export KV_GENERIC="generic" # Path: gen/test, Key: value +export GENERIC_SECRET="generic" # Path: gen/test, Key: value +export KV_SECRET="old" # Path: old/test, Key: value +export KV2_SECRET="default" # Path: secret/oldstyle, Key: value +# Environment: stage export ENVIRONMENT="stage" -# Environment (stage) Secrets: -export ANOTHER_SECRET="It Still Works" # secret/test -# Datacenter (ndc_one) Specific Vars: -YET_ANOTHER_SECRET: "secretpassword" +export ANOTHER_SECRET="default" # Path: secret/oldstyle, Key: value +# Datacenter: ndc_one export DC="one" +export KV2_THREE="3" # Path: old/test, Key: three ``` -*A Note About Vault:* If you have `secrets` defined in either the global or environment scope, it's a mapping from environment variable to the path in vault. Buildenv uses all the standard vault environment variables to communicate with vault (`VAULT_ADDR` and `VAULT_TOKEN` being the two you're most likely to use.) +*A Note About Vault:* If you have `secrets` defined in either the global or environment scope, it's a mapping from environment variable to the path in vault. Buildenv uses all the standard vault environment variables to communicate with vault (`VAULT_ADDR` and `VAULT_TOKEN` being the two you're most likely to use.) You can find the complete list [in the vault client docs](https://pkg.go.dev/github.com/hashicorp/vault-client-go@v0.4.2#WithEnvironment). Running on Linux or in Docker container ---------- @@ -73,8 +84,13 @@ To test with vault, run: docker-compose up vault -d export VAULT_ADDR="http://localhost:8200" export VAULT_TOKEN="test" -vault write secret/test "value=It Works" -vault write secret/test2 "value=It Still Works" -buildenv -e stage +vault secrets enable -path gen generic +vault secrets enable -version=1 -path old kv +vault kv put secret/test "one=1" "two=2" +vault kv put secret/oldstyle "value=default" +vault kv put old/test "value=old" "three=3" +vault write gen/test "value=generic" + +buildenv -c -e stage -d ndc_one docker-compose down ``` diff --git a/buildenv.go b/buildenv.go deleted file mode 100644 index 6450405..0000000 --- a/buildenv.go +++ /dev/null @@ -1,273 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "io/ioutil" - "path/filepath" - - "github.com/urfave/cli" - "gopkg.in/yaml.v2" - - vaultapi "github.com/hashicorp/vault/api" -) - -var ( - version = "Development" - vault *vaultapi.Client -) - -// EnvErrorCode Exit Code for Missing Environment -const EnvErrorCode = 2 - -// YamlErrorCode Exit Code for YAML Errors -const YamlErrorCode = 5 - -// VaultErrorCode Exit Code for Vault Errors -const VaultErrorCode = 6 - -// VaultConfig is the configuration for connecting to a vault server. -type VaultConfig struct { - Address string - Token string - // SSL indicates we should use a secure connection while talking to Vault. - SSL *SSLConfig -} - -// SSLConfig is the configuration for SSL. -type SSLConfig struct { - Enabled bool - Verify bool - Cert string - CaCert string -} - -// GetVaultSecret - Pull a Secret From Vault given a path -func GetVaultSecret(path string) (*vaultapi.Secret, error) { - // Get Config Completely From Environment - var c *vaultapi.Config - - if vault == nil { - var err error - vault, err = vaultapi.NewClient(c) - if err != nil { - return nil, fmt.Errorf("Vault - Client Error: %s", err) - } - } - - vaultSecret, err := vault.Logical().Read(path) - - if err != nil { - return nil, fmt.Errorf("Vault - Read Error: %s", err) - } - if vaultSecret == nil { - return nil, fmt.Errorf("Vault - No secret at path: %s", path) - } - return vaultSecret, nil -} - -func main() { - app := cli.NewApp() - - var env string - var dc string - var varsFile string - var mlockBool = false - var skipComments = false - - type EnvVars map[string]string - - type Secrets map[string]string - - type ConfigV1 struct { - Vars EnvVars - Secrets Secrets - Environments map[string]struct { - Vars EnvVars - Secrets Secrets - Dcs map[string]EnvVars - } - } - - type Config struct { - Vars EnvVars - Secrets Secrets - Environments map[string]struct { - Vars EnvVars - Secrets Secrets - Dcs map[string]struct { - Vars EnvVars - Secrets Secrets - } - } - } - - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "environment, e", - Usage: "Environment (qa, dev, stage, prod, etc)", - EnvVar: "ENVIRONMENT", - Destination: &env, - }, - cli.StringFlag{ - Name: "datacenter, d", - Usage: "Datacenter (ndc_as_a, etc)", - EnvVar: "DATACENTER", - Destination: &dc, - }, - cli.StringFlag{ - Name: "variables_file, f", - Value: "variables.yml", - Usage: "Variables YAML file", - EnvVar: "VARIABLES_FILE", - Destination: &varsFile, - }, - cli.BoolFlag{ - Name: "mlock_enabled, m", - Usage: "Will attempt system mlock if set (prevent write to swap)", - Required: false, - Destination: &mlockBool, - }, - cli.BoolFlag{ - Name: "no_comments, c", - Usage: "Supress all comments in the output", - Required: false, - Destination: &skipComments, - }, - } - - app.Version = version - app.Name = "buildenv" - app.Usage = "Get the Build Environment from a settings yaml file." - - app.Action = func(c *cli.Context) error { - - enableMlock(mlockBool) - - if env == "" { - return cli.NewExitError("environment is required", EnvErrorCode) - } - - filename, _ := filepath.Abs(varsFile) - yamlFile, err := ioutil.ReadFile(filename) - - if err != nil { - return cli.NewExitError(fmt.Sprintf("unable to read variable file %s", varsFile), 4) - } - - // Legacy - var legacy = false - var configV1 ConfigV1 - var config Config - - err = yaml.Unmarshal(yamlFile, &config) - if err != nil { - err = yaml.Unmarshal(yamlFile, &configV1) - legacy = true - - if err != nil { - fmt.Println(err) - return cli.NewExitError("unable to unmarshal yaml", YamlErrorCode) - } - } - - if !skipComments { - fmt.Println("# Setting Variables for:") - fmt.Printf("# Environment: %s\n", env) - } - if dc != "" { - if !skipComments { - fmt.Printf("# Datacenter: %s\n", dc) - } - } - - // Print The Globals - if !skipComments { - fmt.Println("# Global Vars:") - } - for k, v := range config.Vars { - fmt.Printf("export %s=%q\n", k, v) - } - - if !skipComments { - fmt.Println("# Global Secrets:") - } - for k, path := range config.Secrets { - secret, err := GetVaultSecret(path) - if err == nil { - fmt.Printf("export %s=%q", k, secret.Data["value"]) - if !skipComments { - fmt.Printf(" # %s", path) - } - fmt.Println() - } else { - return cli.NewExitError(err.Error(), VaultErrorCode) - } - } - - // Print The Environment Specific Vars - if !skipComments { - fmt.Printf("# Environment (%s) Vars:\n", env) - } - for k, v := range config.Environments[env].Vars { - fmt.Printf("export %s=%q\n", k, v) - } - - if !skipComments { - fmt.Printf("# Environment (%s) Secrets:\n", env) - } - for k, path := range config.Environments[env].Secrets { - secret, err := GetVaultSecret(path) - if err == nil { - fmt.Printf("export %s=%q", k, secret.Data["value"]) - if !skipComments { - fmt.Printf(" # %s", path) - } - fmt.Println() - } else { - return cli.NewExitError(err.Error(), VaultErrorCode) - } - } - - // Print the DC Specific Vars - if legacy { - if dc != "" { - if !skipComments { - fmt.Printf("# Datacenter (%s) Specific Vars:\n", dc) - } - for k, v := range config.Environments[env].Dcs[dc].Vars { - fmt.Printf("export %s=%q\n", k, v) - } - } - } else { - if !skipComments { - fmt.Printf("# Datacenter (%s) Specific Vars:\n", env) - } - for k, v := range config.Environments[env].Dcs[dc].Vars { - fmt.Printf("export %s=%q\n", k, v) - } - - if !skipComments { - fmt.Printf("# Datacenter (%s) Specific Secrets:\n", env) - } - - for k, path := range config.Environments[env].Dcs[dc].Secrets { - secret, err := GetVaultSecret(path) - if err == nil { - fmt.Printf("export %s=%q", k, secret.Data["value"]) - if !skipComments { - fmt.Printf(" # %s", path) - } - fmt.Println() - } else { - return cli.NewExitError(err.Error(), VaultErrorCode) - } - } - } - - return nil - } - - app.Run(os.Args) -} diff --git a/cmd/mlock-linux.go b/cmd/mlock-linux.go new file mode 100644 index 0000000..05b4062 --- /dev/null +++ b/cmd/mlock-linux.go @@ -0,0 +1,19 @@ +//go:build linux +// +build linux + +package cmd + +import ( + "fmt" + "syscall" + + "golang.org/x/sys/unix" +) + +func EnableMlock() error { + mlockError := unix.Mlockall(syscall.MCL_CURRENT | syscall.MCL_FUTURE) + if mlockError != nil { + return fmt.Errorf("mlock error: %w", mlockError) + } + return nil +} diff --git a/cmd/mlock-non-linux.go b/cmd/mlock-non-linux.go new file mode 100644 index 0000000..3556e42 --- /dev/null +++ b/cmd/mlock-non-linux.go @@ -0,0 +1,9 @@ +//go:build !linux +// +build !linux + +package cmd + +func EnableMlock() error { + // NOTE: Not required for windows + return nil +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..f6cfadb --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,151 @@ +/* +Copyright © 2023 Comcast Cable Communications Management, LLC +*/ +package cmd + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "github.com/Comcast/Buildenv-Tool/reader" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" +) + +const ( + // ErrorCodeMLock Exit Code for MLock Errors + ErrorCodeMLock = 1 + // ErrorCodeEnv Exit Code for Missing Environment + ErrorCodeEnv = 2 + // ErrorCodeYaml Exit Code for YAML Errors + ErrorCodeYaml = 5 + // ErrorCodeVault Exit Code for Vault Errors + ErrorCodeVault = 6 +) + +var cfgFile string + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "buildenv", + Short: "Set environment variables from a configuation file", + Long: `Set environment variables based on environment and datacenter. +Values can be specified in plain text, or set from a vault server.`, + // Uncomment the following line if your bare application + // has an action associated with it: + Run: func(cmd *cobra.Command, args []string) { + + ctx := context.Background() + debug, _ := cmd.Flags().GetBool("debug") + + enableMlock, _ := cmd.Flags().GetBool("mlock") + if !enableMlock { + err := EnableMlock() + if err != nil { + fmt.Printf("Failure locking memory: %v", err) + os.Exit(ErrorCodeMLock) + } + } + + // Read the Data File + variablesFile, _ := cmd.Flags().GetString("variables_file") + var data reader.Variables + + yamlFile, err := os.ReadFile(variablesFile) + if err != nil { + fmt.Printf("Unable to read file %s: %v", variablesFile, err) + os.Exit(ErrorCodeYaml) + } + err = yaml.Unmarshal(yamlFile, &data) + if err != nil { + fmt.Printf("Unable to parse YAML file %s: %v", variablesFile, err) + } + if debug { + inData, _ := json.MarshalIndent(data, "", " ") + fmt.Printf("Data:\n%s\n\n", inData) + } + + // Setup the Reader + reader, err := reader.NewReader() + if err != nil { + fmt.Printf("Failure creating Reader: %v", err) + os.Exit(ErrorCodeVault) + } + + // Get the Output + env, _ := cmd.Flags().GetString("environment") + dc, _ := cmd.Flags().GetString("datacenter") + + out, err := reader.Read(ctx, &data, env, dc) + if err != nil { + fmt.Printf("Failure reading data: %v", err) + os.Exit(ErrorCodeVault) + } + + if debug { + outData, _ := json.MarshalIndent(out, "", " ") + fmt.Printf("Output:\n%s\n\n", outData) + } + + // Output the Exports + comments, _ := cmd.Flags().GetBool("comments") + out.Print(comments) + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.buildenv.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().StringP("environment", "e", "", "Environment (qa, dev, stage, prod, etc)") + rootCmd.Flags().StringP("datacenter", "d", "", "Datacenter (ndc_as_a, us-east-1 etc)") + rootCmd.Flags().StringP("variables_file", "f", "variables.yml", "Variables Source YAML file") + + rootCmd.Flags().BoolP("mlock", "m", false, "Will enable system mlock if set (prevent write to swap on linux)") + rootCmd.Flags().BoolP("comments", "c", false, "Comments will be included in output") + rootCmd.Flags().Bool("debug", false, "Turn on debugging output") + +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := os.UserHomeDir() + cobra.CheckErr(err) + + // Search config in home directory with name ".buildenv" (without extension). + viper.AddConfigPath(home) + viper.SetConfigType("yaml") + viper.SetConfigName(".buildenv") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/docker-compose.yml b/docker-compose.yml index 2faa360..dbe2227 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,11 @@ version: "3" services: build: - image: golang:1.16-alpine + image: golang:1.21-alpine volumes: - ./:/go/src/buildenv vault: - image: vault:0.9.0 + image: hashicorp/vault cap_add: - IPC_LOCK environment: diff --git a/go.mod b/go.mod index d41450c..b8ed6bb 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,39 @@ module github.com/Comcast/Buildenv-Tool -go 1.16 +go 1.21 require ( - github.com/aktau/github-release v0.10.0 // indirect - github.com/dustin/go-humanize v1.0.0 // indirect - github.com/github-release/github-release v0.10.0 // indirect - github.com/hashicorp/vault/api v1.0.4 - github.com/kevinburke/rest v0.0.0-20210222204520-f7a2e216372f // indirect - github.com/mitchellh/gox v1.0.1 // indirect - github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 // indirect - github.com/urfave/cli v1.22.2 - github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 // indirect - golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e - gopkg.in/yaml.v2 v2.2.8 + github.com/hashicorp/vault-client-go v0.4.2 + github.com/spf13/cobra v1.8.0 + github.com/spf13/viper v1.18.2 + golang.org/x/sys v0.15.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.1 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 6d843eb..19317d2 100644 --- a/go.sum +++ b/go.sum @@ -1,153 +1,102 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aktau/github-release v0.7.2 h1:la7AnShr2MQPIlBEcRA9MPbI8av0YFmpFP9WM5EoqJs= -github.com/aktau/github-release v0.7.2/go.mod h1:cPkP83iRnV8pAJyQlQ4vjLJoC+JE+aT5sOrYz3sTsX0= -github.com/aktau/github-release v0.10.0 h1:4U+9iRM7n094ZCROdnoah084FDmdQ01hwQsz0f0hwIw= -github.com/aktau/github-release v0.10.0/go.mod h1:cPkP83iRnV8pAJyQlQ4vjLJoC+JE+aT5sOrYz3sTsX0= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/github-release/github-release v0.10.0 h1:nJ3oEV2JrC0brYi6B8CsXumn/ORFeiAEOB2fwN9epOw= -github.com/github-release/github-release v0.10.0/go.mod h1:CcaWgA5VoBGz94mOHYIXavqUA8kADNZxU+5/oDQxF6o= -github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= -github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY= -github.com/hashicorp/go-retryablehttp v0.5.4 h1:1BZvpawXoJCWX6pNtow9+rpEj+3itIlutiqnntI6jOE= -github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.1 h1:DMo4fmknnz0E0evoNYnV48RjWndOsmd6OW+09R3cEP8= -github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= +github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/vault v1.3.2 h1:6KPAGWBFLUG9KnkUr956f4o1LK6pjifDpEC4ax7ZYAU= -github.com/hashicorp/vault/api v1.0.4 h1:j08Or/wryXT4AcHj1oCbMd7IijXcKzYUGw59LGu9onU= -github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= -github.com/hashicorp/vault/sdk v0.1.13 h1:mOEPeOhT7jl0J4AMl1E705+BcmeRs1VmKNb9F0sMLy8= -github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/kevinburke/rest v0.0.0-20210222204520-f7a2e216372f h1:AIRcGLQqg/FdPmErgrk19FNofLIo3gFPSRzXz6UUS6o= -github.com/kevinburke/rest v0.0.0-20210222204520-f7a2e216372f/go.mod h1:pD+iEcdAGVXld5foVN4e24zb/6fnb60tgZPZ3P/3T/I= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/hashicorp/vault-client-go v0.4.2 h1:XeUXb5jnDuCUhC8HRpkdGPLh1XtzXmiOnF0mXEbARxI= +github.com/hashicorp/vault-client-go v0.4.2/go.mod h1:4tDw7Uhq5XOxS1fO+oMtotHL7j4sB9cp0T7U6m4FzDY= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI= -github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4= -github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y= -github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE= -github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2 h1:txplJASvd6b/hrE0s/Ixfpp2cuwH9IO9oZBAN9iYa4A= -github.com/voxelbrain/goptions v0.0.0-20180630082107-58cddc247ea2/go.mod h1:DGCIhurYgnLz8J9ga1fMV/fbLDyUvTyrWXVWUIyJon4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e h1:nFYrTHrdrAOpShe27kaFHjsqYSEQ0KWqdWLu3xuZJts= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..c75213b --- /dev/null +++ b/main.go @@ -0,0 +1,10 @@ +/* +Copyright © 2023 Comcast Cable Communications Management, LLC +*/ +package main + +import "github.com/Comcast/Buildenv-Tool/cmd" + +func main() { + cmd.Execute() +} diff --git a/mlock-win.go b/mlock-win.go deleted file mode 100644 index f64ac00..0000000 --- a/mlock-win.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build windows - -package main - -import ( - "fmt" - - "github.com/urfave/cli" -) - -func enableMlock(mlockBool bool) error { - - if mlockBool { - return cli.NewExitError(fmt.Sprintf("mlock not necessary for windows"), 1) - } - return nil -} diff --git a/mlock.go b/mlock.go deleted file mode 100644 index ca16a65..0000000 --- a/mlock.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build !windows - -package main - -import ( - "fmt" - "syscall" - - "github.com/urfave/cli" - "golang.org/x/sys/unix" -) - -func enableMlock(mlockBool bool) error { - - if mlockBool { - mlockError := unix.Mlockall(syscall.MCL_CURRENT | syscall.MCL_FUTURE) - if mlockError != nil { - return cli.NewExitError(fmt.Sprintf("mlock error: %s", mlockError), 1) - } - } - return nil -} diff --git a/reader/reader.go b/reader/reader.go new file mode 100644 index 0000000..30241c8 --- /dev/null +++ b/reader/reader.go @@ -0,0 +1,273 @@ +package reader + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/hashicorp/vault-client-go" +) + +type Reader struct { + client *vault.Client + mounts Mounts +} + +type EnvVars map[string]string + +func (e EnvVars) GetOutput() OutputList { + output := OutputList{} + for k, v := range e { + output = append(output, Output{ + Key: k, + Value: v, + }) + } + return output +} + +type Secrets map[string]string + +func (s Secrets) GetOutput(ctx context.Context, r *Reader) (OutputList, error) { + // Read it like a kv secrets where all keys are "value" + kvSecrets := KVSecrets{} + for outVar, path := range s { + kvSecret := KVSecretBlock{ + Path: path, + Vars: KVSecret{ + outVar: "value", + }, + } + kvSecrets = append(kvSecrets, kvSecret) + } + return kvSecrets.GetOutput(ctx, r) +} + +type KVSecret map[string]string + +type KVSecretBlock struct { + Path string + Vars KVSecret +} + +type KVSecrets []KVSecretBlock + +func (s KVSecretBlock) GetOutput(ctx context.Context, r *Reader) (OutputList, error) { + output := OutputList{} + + // The first thing we need to do is get the mount point for the KV engine + mountPoint, secretPath := r.MountAndPath(s.Path) + if mountPoint == "" { + return nil, fmt.Errorf("no mount point found for path %s", s.Path) + } + + // V2 KV Secrets + if r.mounts[mountPoint].Type == "kv" && r.mounts[mountPoint].Version == "2" { + // Get Secret + resp, err := r.client.Secrets.KvV2Read(ctx, secretPath, vault.WithMountPath(mountPoint)) + if err != nil { + if vault.IsErrorStatus(err, http.StatusNotFound) { + return nil, fmt.Errorf("secret does not exist: '%s'", s.Path) + } + return nil, fmt.Errorf("error reading path '%s': %w", s.Path, err) + } + for varName, varKey := range s.Vars { + if _, hasValue := resp.Data.Data[varKey]; !hasValue { + return nil, fmt.Errorf("key %s not found in path %s", varKey, s.Path) + } + val := fmt.Sprintf("%s", resp.Data.Data[varKey]) + output = append(output, Output{ + Key: varName, + Value: val, + Comment: fmt.Sprintf("Path: %s, Key: %s", s.Path, varKey), + }) + } + } else { + // Treat it as a KVv1 secret + resp, err := r.client.Secrets.KvV1Read(ctx, secretPath, vault.WithMountPath(mountPoint)) + if err != nil { + return nil, fmt.Errorf("error reading path %s: %w", s.Path, err) + } + for varName, varKey := range s.Vars { + if _, hasValue := resp.Data[varKey]; !hasValue { + return nil, fmt.Errorf("key %s not found in path %s", varKey, s.Path) + } + val := fmt.Sprintf("%s", resp.Data[varKey]) + output = append(output, Output{ + Key: varName, + Value: val, + Comment: fmt.Sprintf("Path: %s, Key: %s", s.Path, varKey), + }) + } + } + + return output, nil +} + +func (s KVSecrets) GetOutput(ctx context.Context, r *Reader) (OutputList, error) { + output := OutputList{} + for _, block := range s { + blockOutput, err := block.GetOutput(ctx, r) + if err != nil { + return nil, err + } + output = append(output, blockOutput...) + } + return output, nil +} + +type DC struct { + Vars EnvVars `yaml:"vars,omitempty"` + Secrets Secrets `yaml:"secrets,omitempty"` + KVSecrets KVSecrets `yaml:"kv_secrets,omitempty"` +} + +type Environment struct { + Vars EnvVars `yaml:"vars,omitempty"` + Secrets Secrets `yaml:"secrets,omitempty"` + KVSecrets KVSecrets `yaml:"kv_secrets,omitempty"` + Dcs map[string]DC `yaml:"dcs,omitempty"` +} + +type Variables struct { + Vars EnvVars `yaml:"vars,omitempty"` + Secrets Secrets `yaml:"secrets,omitempty"` + KVSecrets KVSecrets `yaml:"kv_secrets,omitempty"` + Environments map[string]Environment `yaml:"environments,omitempty"` +} + +type Output struct { + Key string + Value string + Comment string +} +type OutputList []Output + +func (o OutputList) Print(showComments bool) { + for _, out := range o { + keySpace := "" + nl := false + if out.Key != "" { + fmt.Printf("export %s=%q", out.Key, out.Value) + keySpace = " " + nl = true + } + if out.Comment != "" && showComments { + fmt.Printf("%s# %s", keySpace, out.Comment) + nl = true + } + if nl { + fmt.Println() + } + } +} + +type MountInfo struct { + Type string + Version string +} + +type Mounts map[string]MountInfo + +func NewReader() (*Reader, error) { + vaultClient, err := vault.New(vault.WithEnvironment()) + if err != nil { + return nil, err + } + + // Get mount info + resp, err := vaultClient.System.MountsListSecretsEngines(context.Background()) + if err != nil { + return nil, fmt.Errorf("failure reading secret mounts: %w", err) + } + + mounts := Mounts{} + for mount, details := range resp.Data { + detailMap := details.(map[string]interface{}) + thisMount := MountInfo{ + Type: detailMap["type"].(string), + } + if options, hasOptions := detailMap["options"]; hasOptions && options != nil { + optionMap := options.(map[string]interface{}) + if version, hasVersion := optionMap["version"]; hasVersion { + thisMount.Version = version.(string) + } + } + mounts[mount] = thisMount + } + + return &Reader{ + client: vaultClient, + mounts: mounts, + }, nil +} + +func (r *Reader) MountAndPath(path string) (string, string) { + for mount := range r.mounts { + if strings.HasPrefix(path, mount) { + return mount, strings.TrimPrefix(path, mount) + } + } + return "", "" +} + +func (r *Reader) Read(ctx context.Context, input *Variables, env string, dc string) (OutputList, error) { + output := OutputList{} + + // Global Variables + output = append(output, Output{ + Comment: "Global Variables", + }) + output = append(output, input.Vars.GetOutput()...) + + // Global Secrets + kvOut, err := input.KVSecrets.GetOutput(ctx, r) + if err != nil { + return nil, fmt.Errorf("kv secret error: %w", err) + } + output = append(output, kvOut...) + secretOut, err := input.Secrets.GetOutput(ctx, r) + if err != nil { + return nil, fmt.Errorf("secret error: %w", err) + } + output = append(output, secretOut...) + + // Environment Variablers + if env != "" { + output = append(output, Output{ + Comment: fmt.Sprintf("Environment: %s", env), + }) + output = append(output, input.Environments[env].Vars.GetOutput()...) + kvOut, err := input.Environments[env].KVSecrets.GetOutput(ctx, r) + if err != nil { + return nil, fmt.Errorf("kv secret error: %w", err) + } + output = append(output, kvOut...) + secretOut, err := input.Environments[env].Secrets.GetOutput(ctx, r) + if err != nil { + return nil, fmt.Errorf("secret error: %w", err) + } + output = append(output, secretOut...) + } + + // DC Variables + if dc != "" { + output = append(output, Output{ + Comment: fmt.Sprintf("Datacenter: %s", dc), + }) + output = append(output, input.Environments[env].Dcs[dc].Vars.GetOutput()...) + kvOut, err := input.Environments[env].Dcs[dc].KVSecrets.GetOutput(ctx, r) + if err != nil { + return nil, fmt.Errorf("kv secret error: %w", err) + } + output = append(output, kvOut...) + secretOut, err := input.Environments[env].Dcs[dc].Secrets.GetOutput(ctx, r) + if err != nil { + return nil, fmt.Errorf("secret error: %w", err) + } + output = append(output, secretOut...) + } + + return output, nil +} diff --git a/reader/reader_test.go b/reader/reader_test.go new file mode 100644 index 0000000..184bd13 --- /dev/null +++ b/reader/reader_test.go @@ -0,0 +1,297 @@ +package reader + +import ( + "context" + "log" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/hashicorp/vault-client-go" +) + +func TestEnvVars_GetOutput(t *testing.T) { + tests := []struct { + name string + e EnvVars + want OutputList + }{ + { + name: "Test Output", + e: EnvVars{ + "a": "b", + }, + want: OutputList{ + { + Key: "a", + Value: "b", + }, + }, + }, + { + name: "Empty Output", + e: EnvVars{}, + want: OutputList{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.e.GetOutput(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("EnvVars.GetOutput() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestReader_Read(t *testing.T) { + type fields struct { + client *vault.Client + } + type args struct { + input *Variables + env string + dc string + } + tests := []struct { + name string + fields fields + args args + want OutputList + wantErr bool + }{ + { + name: "Just Plain Variables", + fields: fields{}, + args: args{ + env: "dev", + dc: "us-least-1", + input: &Variables{ + Vars: EnvVars{ + "FOO": "bar", + }, + Environments: map[string]Environment{ + "dev": { + Vars: EnvVars{ + "ENV": "dev", + }, + Dcs: map[string]DC{ + "us-least-1": { + Vars: EnvVars{ + "DC": "us-least-1", + }, + }, + }, + }, + "stage": { + Vars: EnvVars{ + "env": "stage", + }, + }, + }, + }, + }, + want: OutputList{ + { + Comment: "Global Variables", + }, + { + Key: "FOO", + Value: "bar", + }, + { + Comment: "Environment: dev", + }, + { + Key: "ENV", + Value: "dev", + }, + { + Comment: "Datacenter: us-least-1", + }, + { + Key: "DC", + Value: "us-least-1", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + r := &Reader{ + client: tt.fields.client, + } + got, err := r.Read(ctx, tt.args.input, tt.args.env, tt.args.dc) + if (err != nil) != tt.wantErr { + t.Errorf("Reader.Read() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Reader.Read() = %+v, want %+v", got, tt.want) + } + }) + } +} + +func TestKVSecretBlock_GetOutput(t *testing.T) { + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%+v", r) + var resp []byte + var status = http.StatusOK + + // KV Data + switch r.URL.Path { + case "/v1/kv2/data/test": + resp = []byte(`{"request_id":"bf3b02c0-096e-84d3-dad7-196aa9f112ed","lease_id":"","renewable":false,"lease_duration":0,"data":{"data":{"one":"1","two":"2"},"metadata":{"created_time":"2023-12-20T15:32:32.814115685Z","custom_metadata":null,"deletion_time":"","destroyed":false,"version":1}},"wrap_info":null,"warnings":null,"auth":null}`) + case "/v1/kv/test": + resp = []byte(`{"request_id":"63c8c31b-f03f-81ac-cfaa-324239789c3f","lease_id":"","renewable":false,"lease_duration":2764800,"data":{"value":"old"},"wrap_info":null,"warnings":null,"auth":null}`) + default: + status = http.StatusNotFound + resp = []byte(`{"errors":[]}`) + } + + w.WriteHeader(status) + w.Write(resp) + })) + defer server.Close() + + client, _ := vault.New(vault.WithAddress(server.URL)) + reader := &Reader{ + client: client, + mounts: Mounts{ + "kv2/": { + Type: "kv", + Version: "2", + }, + "kv/": { + Type: "kv", + }, + "generic/": { + Type: "generic", + }, + }, + } + + type fields struct { + Path string + Vars KVSecret + } + type args struct { + r *Reader + } + tests := []struct { + name string + fields fields + args args + want OutputList + wantErr bool + }{ + { + name: "No Mount", + args: args{ + r: reader, + }, + fields: fields{ + Path: "secret/test", + Vars: KVSecret{ + "should": "fail", + }, + }, + want: nil, + wantErr: true, + }, + { + name: "No KV Path", + args: args{ + r: reader, + }, + fields: fields{ + Path: "kv2/path", + Vars: KVSecret{ + "NOT": "here", + }, + }, + wantErr: true, + want: nil, + }, + { + name: "No KV2 Key", + args: args{ + r: reader, + }, + fields: fields{ + Path: "kv2/test", + Vars: KVSecret{ + "THREE": "nope", + }, + }, + wantErr: true, + want: nil, + }, + { + name: "Test KV Read", + args: args{ + r: reader, + }, + fields: fields{ + Path: "kv/test", + Vars: KVSecret{ + "VALUE": "value", + }, + }, + want: OutputList{ + { + Key: "VALUE", + Value: "old", + Comment: "Path: kv/test, Key: value", + }, + }, + }, + { + name: "Test KV2 Read", + args: args{ + r: reader, + }, + fields: fields{ + Path: "kv2/test", + Vars: KVSecret{ + "ONE": "one", + "TWO": "two", + }, + }, + want: OutputList{ + { + Key: "ONE", + Value: "1", + Comment: "Path: kv2/test, Key: one", + }, + { + Key: "TWO", + Value: "2", + Comment: "Path: kv2/test, Key: two", + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + s := KVSecretBlock{ + Path: tt.fields.Path, + Vars: tt.fields.Vars, + } + got, err := s.GetOutput(ctx, tt.args.r) + if (err != nil) != tt.wantErr { + t.Errorf("KVSecretBlock.GetOutput() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("KVSecretBlock.GetOutput() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/variables.yml b/variables.yml index c91907d..90090dc 100644 --- a/variables.yml +++ b/variables.yml @@ -3,7 +3,21 @@ vars: GLOBAL: "global" secrets: - SECRET_TEST: "secret/test" + GENERIC_SECRET: "gen/test" + KV_SECRET: "old/test" + KV2_SECRET: "secret/oldstyle" + +kv_secrets: + - path: "secret/test" + vars: + KV2_ONE: "one" + KV2_TWO: "two" + - path: "old/test" + vars: + KV1: "value" + - path: "gen/test" + vars: + KV_GENERIC: "value" environments: stage: @@ -11,16 +25,15 @@ environments: ENVIRONMENT: "stage" secrets: - ANOTHER_SECRET: "secret/test2" + ANOTHER_SECRET: "secret/oldstyle" dcs: ndc_one: vars: DC: "one" - secrets: - YET_ANOTHER_SECRET: "secret/test3" - ndc_two: - vars: - DC: "two" - secrets: - YET_ANOTHER_SECRET: "secret/test4" + kv_secrets: + - path: "old/test" + vars: + KV2_THREE: "three" + +