diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e0aae4f..47c3611 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,10 +1,8 @@ name: CI on: - push: - branches: [master, main] pull_request: - branches: [master, main] + branches: [master, main, v3] permissions: contents: write @@ -37,6 +35,9 @@ jobs: - name: Set SemVer Version uses: gittools/actions/gitversion/execute@v4.1.0 id: gitversion + with: + overrideConfig: | + next-version=3.0.0 - name: echo VERSIONS run: | @@ -58,9 +59,6 @@ jobs: - name: Install Eirctl uses: ensono/actions/eirctl-setup@v0.3.1 - with: - version: 0.9.3 - isPrerelease: false - name: Run Lint run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 405c112..89db3d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,12 +2,13 @@ name: release on: workflow_run: - workflows: ['CI'] + workflows: ["CI"] types: - completed branches: - master - main + - v3 permissions: contents: write @@ -34,10 +35,13 @@ jobs: - name: Install GitVersion uses: gittools/actions/gitversion/setup@v4.1.0 with: - versionSpec: '6.x' + versionSpec: "6.x" - name: Set SemVer Version uses: gittools/actions/gitversion/execute@v4.1.0 id: gitversion + with: + overrideConfig: | + next-version=3.0.0 release: name: Release @@ -52,9 +56,6 @@ jobs: - name: Install Eirctl uses: ensono/actions/eirctl-setup@v0.3.1 - with: - version: 0.7.6 - isPrerelease: false - name: build binary run: | diff --git a/.github/workflows/release_container.yml b/.github/workflows/release_container.yml index 9a1c45d..2d1e3d5 100644 --- a/.github/workflows/release_container.yml +++ b/.github/workflows/release_container.yml @@ -2,15 +2,15 @@ name: Publish Container on: workflow_run: - workflows: ['Lint and Test'] + workflows: ["CI"] types: - completed - branches: + branches: - main permissions: contents: write - packages: write + packages: write jobs: set-version-tag: @@ -25,12 +25,15 @@ jobs: with: fetch-depth: 0 - name: Install GitVersion - uses: gittools/actions/gitversion/setup@v3.0 + uses: gittools/actions/gitversion/setup@v4.1.0 with: - versionSpec: '5.x' + versionSpec: "6.x" - name: Set SemVer Version - uses: gittools/actions/gitversion/execute@v3.0 + uses: gittools/actions/gitversion/execute@v4.1.0 id: gitversion + with: + overrideConfig: | + next-version=3.0.0 build-and-push: runs-on: ubuntu-latest @@ -63,4 +66,4 @@ jobs: build-args: Version=${{ needs.set-version-tag.outputs.semVer }},Revision=${{ github.sha }} tags: | ghcr.io/ensono/eirctl:${{ needs.set-version-tag.outputs.semVer }} - platforms: linux/amd64,linux/arm64 # adjust as needed + platforms: linux/amd64,linux/arm64 # adjust as needed diff --git a/.gitignore b/.gitignore index 573d3c6..0c0758a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,12 +7,18 @@ # Go vendor -bin dist .deps/ +# generated +bin +plugins/**/bin + # tests .coverage # local testers and local/ +.bin +.configmanager +.trivy/fs-sbom-file.json diff --git a/.trivyignore.yaml b/.trivyignore.yaml new file mode 100644 index 0000000..31c34bb --- /dev/null +++ b/.trivyignore.yaml @@ -0,0 +1,7 @@ +vulnerabilities: + +secrets: + +misconfigurations: + +licenses: diff --git a/Dockerfile b/Dockerfile index 5871513..ba98147 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,4 +17,10 @@ FROM docker.io/alpine:3 COPY --from=builder /app/bin/configmanager /usr/bin/configmanager +RUN chmod +x /usr/bin/configmanager + +RUN adduser -D -s /bin/sh -h /home/configmanager configmanager + +USER configmanager + ENTRYPOINT ["configmanager"] diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..7aea791 --- /dev/null +++ b/buf.gen.yaml @@ -0,0 +1,15 @@ +version: v2 +# this ensures we can store the .proto and generated files in the same directory +clean: false +plugins: + - remote: buf.build/protocolbuffers/go + out: tokenstore/proto + opt: + - paths=source_relative + - remote: buf.build/grpc/go:v1.3.0 + out: tokenstore/proto + opt: + - paths=source_relative + - require_unimplemented_servers=false +inputs: + - directory: tokenstore/proto diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..fa50bb2 --- /dev/null +++ b/buf.yaml @@ -0,0 +1,9 @@ +version: v2 +modules: + - path: ./tokenstore/proto +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/cmd/configmanager/configmanager.go b/cmd/configmanager/configmanager.go index 1eed6cb..c989e55 100644 --- a/cmd/configmanager/configmanager.go +++ b/cmd/configmanager/configmanager.go @@ -6,9 +6,9 @@ import ( "io" "github.com/DevLabFoundry/configmanager/v3" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/generator" "github.com/DevLabFoundry/configmanager/v3/internal/cmdutils" - "github.com/DevLabFoundry/configmanager/v3/internal/config" "github.com/DevLabFoundry/configmanager/v3/internal/log" "github.com/spf13/cobra" ) @@ -58,6 +58,7 @@ func addSubCmds(rootCmd *Root) { newFromStrCmd(rootCmd) newRetrieveCmd(rootCmd) newInsertCmd(rootCmd) + newInitCmd(rootCmd) } func (rc *Root) Execute(ctx context.Context) error { @@ -73,7 +74,7 @@ func cmdutilsInit(rootCmd *Root, cmd *cobra.Command, path string) (*cmdutils.Cmd cm := configmanager.New(cmd.Context()) cm.Config.WithTokenSeparator(rootCmd.rootFlags.tokenSeparator).WithOutputPath(path).WithKeySeparator(rootCmd.rootFlags.keySeparator).WithEnvSubst(rootCmd.rootFlags.enableEnvSubst) - gnrtr := generator.NewGenerator(cmd.Context(), func(gv *generator.GenVars) { + gnrtr := generator.New(cmd.Context(), func(gv *generator.Generator) { if rootCmd.rootFlags.verbose { rootCmd.logger.SetLevel(log.DebugLvl) } diff --git a/cmd/configmanager/init.go b/cmd/configmanager/init.go new file mode 100644 index 0000000..e51d68e --- /dev/null +++ b/cmd/configmanager/init.go @@ -0,0 +1,52 @@ +package cmd + +import ( + "strings" + + "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/DevLabFoundry/configmanager/v3/generator" + "github.com/DevLabFoundry/configmanager/v3/internal/log" + "github.com/spf13/cobra" +) + +type initFlags struct { + tokens []string +} + +func newInitCmd(rootCmd *Root) { + + f := &initFlags{} + + initCmd := &cobra.Command{ + Use: "init", + Aliases: []string{}, + Short: `Initialises the plugins required for config retrieval.`, + Long: `Initialises the plugins required by creating the relevant folder structure and plugin downloads for the current architecture and operating system`, + RunE: func(cmd *cobra.Command, args []string) error { + if f.tokens != nil { + lt := []string{} + for _, v := range f.tokens { + lt = append(lt, strings.ToLower(v)) + } + c := generator.New(cmd.Context(), func(gv *generator.Generator) { + if rootCmd.rootFlags.verbose { + rootCmd.logger.SetLevel(log.DebugLvl) + } + gv.Logger = rootCmd.logger + // still need to parse the root level flags in case the key or token separator is specified + }).WithConfig(config.NewConfig().WithTokenSeparator(rootCmd.rootFlags.tokenSeparator).WithKeySeparator(rootCmd.rootFlags.keySeparator)) + + // NOTE: add additional bells and whistles here to allow for config file parsing and lock management + return c.InitPlugins(lt) + } + // logrus.Debug("zero tokens specified not initialising") + return nil + }, + } + initCmd.PersistentFlags().StringArrayVarP(&f.tokens, "plugin", "", []string{}, `Multi-valued flag to specify the plugins to use with configmanager. +When not all are specified they will not be pre-initialised and instead will be initialised during the generate run, +NOTE: this may cause issues in more controlled environments +`) + _ = initCmd.MarkPersistentFlagRequired("plugin") + rootCmd.Cmd.AddCommand(initCmd) +} diff --git a/internal/config/config.go b/config/config.go similarity index 84% rename from internal/config/config.go rename to config/config.go index d1d05ed..8702e5b 100644 --- a/internal/config/config.go +++ b/config/config.go @@ -9,6 +9,12 @@ import ( const ( SELF_NAME = "configmanager" + // CONFIGMANAGER_DIR is used for any operations that require a lookup of dependencies/providers + // + // If it is empty or unset the default locations for these is `$PWD/.configmanager` and then `~/.configmanager` + CONFIGMANAGER_DIR string = "CONFIGMANAGER_DIR" + + CONFIGMANAGER_LOG string = "CONFIGMANAGER_LOG" ) const ( @@ -41,14 +47,6 @@ const ( ) var ( - // default varPrefix used by the replacer function - // any token must beging with one of these else - // it will be skipped as not a replaceable token - VarPrefix = map[ImplementationPrefix]bool{ - SecretMgrPrefix: true, ParamStorePrefix: true, AzKeyVaultSecretsPrefix: true, - GcpSecretsPrefix: true, HashicorpVaultPrefix: true, AzTableStorePrefix: true, - AzAppConfigPrefix: true, UnknownPrefix: true, - } ErrConfigValidation = errors.New("config validation failed") ) @@ -58,6 +56,7 @@ type GenVarsConfig struct { tokenSeparator string keySeparator string enableEnvSubst bool + enableLaxMode bool // parseAdditionalVars func(token string) TokenConfigVars } @@ -91,12 +90,18 @@ func (c *GenVarsConfig) WithKeySeparator(keySeparator string) *GenVarsConfig { return c } -// WithKeySeparator adds a custom key separotor +// WithEnvSubst adds env subst flag func (c *GenVarsConfig) WithEnvSubst(enabled bool) *GenVarsConfig { c.enableEnvSubst = enabled return c } +// WithLaxMode adds lax mode enabled flag +func (c *GenVarsConfig) WithLaxMode(enabled bool) *GenVarsConfig { + c.enableLaxMode = enabled + return c +} + // OutputPath returns the outpath set in the config func (c *GenVarsConfig) OutputPath() string { return c.outpath @@ -117,6 +122,13 @@ func (c *GenVarsConfig) EnvSubstEnabled() bool { return c.enableEnvSubst } +// LaxModeEnabled returns whether or not lax mode is enabled +// +// It is disabled by default which will break the existing v2 behaviour +func (c *GenVarsConfig) LaxModeEnabled() bool { + return c.enableLaxMode +} + // Config returns the derefed value func (c *GenVarsConfig) Config() GenVarsConfig { cc := *c @@ -144,8 +156,8 @@ type ParsedTokenConfig struct { sanitizedToken string } -// NewToken initialises a *ParsedTokenConfig -func NewToken(prefix ImplementationPrefix, config GenVarsConfig) (*ParsedTokenConfig, error) { +// NewParsedToken initialises a *ParsedTokenConfig +func NewParsedToken(prefix ImplementationPrefix, config GenVarsConfig) (*ParsedTokenConfig, error) { tokenConf := &ParsedTokenConfig{} if err := config.Validate(); err != nil { return nil, err @@ -171,21 +183,9 @@ func (ptc *ParsedTokenConfig) WithSanitizedToken(v string) { } func (t *ParsedTokenConfig) ParseMetadata(metadataTyp any) error { - // crude json like builder from key/val tags - // since we are only ever dealing with a string input - // extracted from the token there is little chance panic would occur here - // WATCH THIS SPACE "¯\_(ツ)_/¯" - metaMap := []string{} - for keyVal := range strings.SplitSeq(t.metadataStr, ",") { - mapKeyVal := strings.Split(keyVal, "=") - if len(mapKeyVal) == 2 { - metaMap = append(metaMap, fmt.Sprintf(`"%s":"%s"`, mapKeyVal[0], mapKeyVal[1])) - } - } - // empty map will be parsed as `{}` still resulting in a valid json // and successful unmarshalling but default value pointer struct - if err := json.Unmarshal(fmt.Appendf(nil, `{%s}`, strings.Join(metaMap, ",")), metadataTyp); err != nil { + if err := json.Unmarshal(fmt.Appendf(nil, "%s", t.parseMetadata()), metadataTyp); err != nil { // It would very hard to test this since // we are forcing the key and value to be strings // return non-filled pointer @@ -243,3 +243,18 @@ func (t *ParsedTokenConfig) Prefix() ImplementationPrefix { func (t *ParsedTokenConfig) TokenSeparator() string { return t.tokenSeparator } + +func (t *ParsedTokenConfig) parseMetadata() string { + // crude json like builder from key/val tags + // since we are only ever dealing with a string input + // extracted from the token there is little chance panic would occur here + // WATCH THIS SPACE "¯\_(ツ)_/¯" + metaMap := []string{} + for keyVal := range strings.SplitSeq(t.metadataStr, ",") { + mapKeyVal := strings.Split(keyVal, "=") + if len(mapKeyVal) == 2 { + metaMap = append(metaMap, fmt.Sprintf(`"%s":"%s"`, mapKeyVal[0], mapKeyVal[1])) + } + } + return fmt.Sprintf(`{%s}`, strings.Join(metaMap, ",")) +} diff --git a/internal/config/config_test.go b/config/config_test.go similarity index 81% rename from internal/config/config_test.go rename to config/config_test.go index 8fc33f0..c6cedaa 100644 --- a/internal/config/config_test.go +++ b/config/config_test.go @@ -3,7 +3,7 @@ package config_test import ( "testing" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/internal/testutils" ) @@ -36,7 +36,7 @@ func Test_MarshalMetadata_with_label_struct_succeeds(t *testing.T) { }{ "when provider expects label on token and label exists": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) + tkn, _ := config.NewParsedToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) tkn.WithKeyPath("d88") tkn.WithMetadata("label=dev") tkn.WithSanitizedToken("basjh/dskjuds/123") @@ -47,7 +47,7 @@ func Test_MarshalMetadata_with_label_struct_succeeds(t *testing.T) { }, "when provider expects label on token and label does not exist": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) + tkn, _ := config.NewParsedToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) tkn.WithKeyPath("d88") tkn.WithMetadata("someother=dev") tkn.WithSanitizedToken("basjh/dskjuds/123") @@ -58,7 +58,7 @@ func Test_MarshalMetadata_with_label_struct_succeeds(t *testing.T) { }, "no metadata found": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) + tkn, _ := config.NewParsedToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) tkn.WithKeyPath("d88") tkn.WithSanitizedToken("basjh/dskjuds/123") return tkn @@ -68,7 +68,7 @@ func Test_MarshalMetadata_with_label_struct_succeeds(t *testing.T) { }, "no metadata found incorrect marker placement": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) + tkn, _ := config.NewParsedToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) tkn.WithKeyPath("d88]asdas=bar[") tkn.WithSanitizedToken("basjh/dskjuds/123") return tkn @@ -78,7 +78,7 @@ func Test_MarshalMetadata_with_label_struct_succeeds(t *testing.T) { }, "no metadata found incorrect marker placement and no key separator": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) + tkn, _ := config.NewParsedToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) tkn.WithSanitizedToken("basjh/dskjuds/123]asdas=bar[") return tkn }, @@ -87,7 +87,7 @@ func Test_MarshalMetadata_with_label_struct_succeeds(t *testing.T) { }, "no start found incorrect marker placement and no key separator": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) + tkn, _ := config.NewParsedToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("://")) tkn.WithKeyPath("d88") tkn.WithMetadata("someother=dev") tkn.WithSanitizedToken("basjh/dskjuds/123]asdas=bar]") @@ -139,7 +139,7 @@ func Test_TokenParser_config(t *testing.T) { for name, tt := range ttests { t.Run(name, func(t *testing.T) { conf := &mockConfAwsSecrMgr{} - got, _ := config.NewToken(tt.expPrefix, *config.NewConfig()) + got, _ := config.NewParsedToken(tt.expPrefix, *config.NewConfig()) got.WithSanitizedToken(tt.rawToken) got.WithKeyPath(tt.keyPath) got.WithMetadata(tt.metadataStr) @@ -164,21 +164,3 @@ func Test_TokenParser_config(t *testing.T) { }) } } - -func TestLookupIdent(t *testing.T) { - ttests := map[string]struct { - char string - expect config.TokenType - }{ - "new line": {"\n", config.NEW_LINE}, - "dash": {"-", config.TEXT}, - } - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - got := config.LookupIdent(tt.char) - if got != tt.expect { - t.Errorf("got %v wanted %v", got, tt.expect) - } - }) - } -} diff --git a/configmanager.go b/configmanager.go index f3f6134..373b71a 100644 --- a/configmanager.go +++ b/configmanager.go @@ -9,16 +9,12 @@ import ( "slices" "strings" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/generator" - "github.com/DevLabFoundry/configmanager/v3/internal/config" "github.com/DevLabFoundry/configmanager/v3/internal/log" "github.com/a8m/envsubst" ) -const ( - TERMINATING_CHAR string = `[^\'\"\s\n\\\,]` // :\@\?\/ -) - // generateAPI type generateAPI interface { Generate(tokens []string) (generator.ReplacedToken, error) @@ -46,7 +42,7 @@ type ConfigManager struct { func New(ctx context.Context) *ConfigManager { cm := &ConfigManager{} cm.Config = config.NewConfig() - cm.generator = generator.NewGenerator(ctx).WithConfig(cm.Config) + cm.generator = generator.New(ctx).WithConfig(cm.Config) cm.logger = log.New(io.Discard) return cm } diff --git a/configmanager_test.go b/configmanager_test.go index 1321232..1179d61 100644 --- a/configmanager_test.go +++ b/configmanager_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/DevLabFoundry/configmanager/v3" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/generator" - "github.com/DevLabFoundry/configmanager/v3/internal/config" "github.com/DevLabFoundry/configmanager/v3/internal/testutils" "github.com/go-test/deep" "gopkg.in/yaml.v3" diff --git a/docs/adding-provider.md b/docs/adding-provider.md index 9c9be84..3c5b362 100644 --- a/docs/adding-provider.md +++ b/docs/adding-provider.md @@ -1,72 +1,9 @@ -# Adding a provider +# Providers -Add Token Prefix +The providers architecture and design is [documented here](../tokenstore/README.md), refer to it for additional details. -```go -const ( - // AWS SecretsManager prefix - SecretMgrPrefix ImplementationPrefix = "AWSSECRETS" - // AWS Parameter Store prefix - ParamStorePrefix ImplementationPrefix = "AWSPARAMSTR" - // Azure Key Vault Secrets prefix - AzKeyVaultSecretsPrefix ImplementationPrefix = "AZKVSECRET" - // Hashicorp Vault prefix - HashicorpVaultPrefix ImplementationPrefix = "VAULT" - // GcpSecrets - GcpSecretsPrefix ImplementationPrefix = "GCPSECRETS" -) -``` +## Adding a provider -```go -var ( - // default varPrefix used by the replacer function - // any token must beging with one of these else - // it will be skipped as not a replaceable token - VarPrefix = map[ImplementationPrefix]bool{SecretMgrPrefix: true, ParamStorePrefix: true, AzKeyVaultSecretsPrefix: true, GcpSecretsPrefix: true, HashicorpVaultPrefix: true} // <-- ADD here -) -``` +Currently there are only the core providers (from v2) and they are stored in independent repositories on github for now. -ensure your implementation satisfy the `genVarsStrategy` interface - -```go -type genVarsStrategy interface { - tokenVal(rs *retrieveStrategy) (s string, e error) - setTokenVal(s string) -} -``` - -Even if the native type is K/V return a marshalled version of the JSON as the rest of the flow will decide how to present it back to the final consumer. - -Custom properties inside the GetValue request, you could specify your own Config struct for the provider, e.g. HashiVault implementation - -```go -// VaultConfig holds the parseable metadata struct -type VaultConfig struct { - Version string `json:"version"` - Role string `json:"iam_role"` -} -``` - -You could then use it on the backingStore object - -```go -type VaultStore struct { - svc hashiVaultApi - ctx context.Context - config *VaultConfig - token string -} -``` - -On initialize of the instance or in the setTokenVal method (see GCPSecrets or AWSSecrets/ParamStore examples). - -```go -storeConf := &VaultConfig{} -initialToken := ParseMetadata(token, storeConf) -imp := &VaultStore{ - ctx: ctx, - config: storeConf, -} -``` - -Where the initialToken is the original Token without the metadata in brackets and the `storeConf` pointer will have been filled with any of the parsed metadata and used in the actual provider implementation, see any of the providers for a sample implementation. +If you want to add an additional provider it needs \ No newline at end of file diff --git a/eirctl.yaml b/eirctl.yaml index f9e4f9c..d4e5204 100644 --- a/eirctl.yaml +++ b/eirctl.yaml @@ -4,16 +4,40 @@ output: prefixed debug: false import: - - https://raw.githubusercontent.com/Ensono/eirctl/refs/tags/0.9.3/shared/build/go/eirctl.yaml + - https://raw.githubusercontent.com/Ensono/eirctl/refs/tags/0.9.17/shared/build/go/eirctl.yaml + - https://raw.githubusercontent.com/Ensono/eirctl/refs/tags/0.9.17/shared/security/eirctl.yaml contexts: bash: container: name: mirror.gcr.io/bash:5.0.18-alpine3.22 + buf: + container: + name: docker.io/bufbuild/buf:1.61 + pull_timeout: 0 + entrypoint: /usr/bin/env + + go1xalpine: + container: + name: mirror.gcr.io/golang:1.26-alpine + envfile: + exclude: + - GO + - CXX + - CGO pipelines: unit:test: - - pipeline: test:unit + - task: go:build:plugin + condition: if [ -z "$(ls -A tokenstore/provider/empty/bin/empty-* 2>/dev/null)" ]; then exit 0; else exit 1; fi; + name: empty + env: + PLUGIN: empty + - task: move:empty:provider + condition: if [ -z "$(ls -A tokenstore/provider/empty/.configmanager/plugins/empty/* 2>/dev/null)" ]; then exit 0; else exit 1; fi; + depends_on: empty + - pipeline: test:unit + depends_on: move:empty:provider env: ROOT_PKG_NAME: github.com/DevLabFoundry @@ -22,9 +46,9 @@ pipelines: - task: sonar:coverage:prep depends_on: unit:test - show_coverage: + code:coverage: - pipeline: unit:test - - task: show:coverage + - task: show_coverage depends_on: unit:test build:bin: @@ -32,11 +56,29 @@ pipelines: - task: go:build:binary depends_on: clean -tasks: - show:coverage: - description: Opens the current coverage viewer for the the configmanager utility. - command: go tool cover -html=.coverage/out + proto:build: + - task: proto:install + - task: proto:generate + depends_on: proto:install + + build:plugins: + - task: go:build:plugin + name: awsparamstr + env: + PLUGIN: awsparamstr + - task: go:build:plugin + name: vault + env: + PLUGIN: vault + - task: go:build:plugin + name: empty + env: + PLUGIN: empty + scan:plugins: + - task: trivy:file:system:sbom + +tasks: show_docs: description: | Opens a webview with godoc running @@ -47,6 +89,45 @@ tasks: open http://localhost:6060/pkg/github.com/DevLabFoundry/configmanager/v2/?m=all godoc -notes "BUG|TODO" -play -http=:6060 + go:build:plugin: + context: go1xalpine + command: + - | + mkdir -p .deps + unset GOTOOLCHAIN + ldflags="-s -w -extldflags -static" + export GOPATH=/eirctl/.deps GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} CGO_ENABLED=0 + go build -C ./tokenstore/provider/$PLUGIN -mod=readonly -buildvcs=false -ldflags="$ldflags" \ + -o bin/$PLUGIN-${BUILD_GOOS}-${BUILD_GOARCH}${BUILD_SUFFIX} main.go + echo "---" + echo "Built: $PLUGIN-${BUILD_GOOS}-${BUILD_GOARCH}${BUILD_SUFFIX}" + reset_context: true + variations: + - BUILD_GOOS: darwin + BUILD_GOARCH: amd64 + BUILD_SUFFIX: "" + - BUILD_GOOS: darwin + BUILD_GOARCH: arm64 + BUILD_SUFFIX: "" + - BUILD_GOOS: linux + BUILD_GOARCH: amd64 + BUILD_SUFFIX: "" + - BUILD_GOOS: linux + BUILD_GOARCH: arm64 + BUILD_SUFFIX: "" + - BUILD_GOOS: windows + BUILD_GOARCH: amd64 + BUILD_SUFFIX: ".exe" + - BUILD_GOOS: windows + BUILD_GOARCH: arm64 + BUILD_SUFFIX: ".exe" + - BUILD_GOOS: windows + BUILD_GOARCH: "386" + BUILD_SUFFIX: ".exe" + required: + env: + - PLUGIN + go:build:binary: context: go1x description: | @@ -93,13 +174,19 @@ tasks: sonar:coverage:prep: context: bash command: - - | - sed -i 's|github.com/DevLabFoundry/configmanager/v2/||g' .coverage/out + - | + sed -i 's|github.com/DevLabFoundry/configmanager/v3/||g' .coverage/out echo "Coverage file first 20 lines after conversion:" head -20 .coverage/out echo "Coverage file line count:" wc -l .coverage/out + move:empty:provider: + command: + - | + mkdir -p ./tokenstore/provider/empty/.configmanager/plugins/empty + cp -r ./tokenstore/provider/empty/bin/* ./tokenstore/provider/empty/.configmanager/plugins/empty/ + tag: description: | Usage `eirctl tag GIT_TAG=2111dsfsdfa REVISION=as2342432` @@ -112,4 +199,15 @@ tasks: - VERSION - REVISION + # currently unused + proto:install: + context: go1xalpine + command: + - GOPATH=$PWD/local/go go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.31.0 + proto:generate: + context: buf + command: + # - PATH=$PATH:$PWD/local/go/bin buf generate + # getting all plugins from the remote registry + - buf generate diff --git a/generator/generator.go b/generator/generator.go index 237c6a6..ced2a4c 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -10,45 +10,55 @@ import ( "strings" "sync" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/internal/lexer" "github.com/DevLabFoundry/configmanager/v3/internal/log" "github.com/DevLabFoundry/configmanager/v3/internal/parser" - "github.com/DevLabFoundry/configmanager/v3/internal/strategy" + "github.com/DevLabFoundry/configmanager/v3/internal/store" ) -// GenVars is the main struct holding the +var ErrTokenNotFound = errors.New("token not found") +var ErrProvidersNotFound = errors.New("providers not initialised") + +type storeIface interface { + GetValue(implemenation *config.ParsedTokenConfig) (string, error) + Init(ctx context.Context, implt []string) error + PluginCleanUp() +} + +// Generator is the main struct holding the // strategy patterns iface // any initialised config if overridded with withers // as well as the final outString and the initial rawMap // which wil be passed in a loop into a goroutine to perform the // relevant strategy network calls to the config store implementations -type GenVars struct { - Logger log.ILogger - strategy strategy.StrategyFuncMap - ctx context.Context - config config.GenVarsConfig +type Generator struct { + Logger log.ILogger + // strategy strategy.StrategyFuncMap + store storeIface + ctx context.Context + config config.GenVarsConfig } -type Opts func(*GenVars) +type Opts func(*Generator) -// NewGenerator returns a new instance of Generator +// New returns a new instance of Generator // with a default strategy pattern wil be overwritten // during the first run of a found tokens map -func NewGenerator(ctx context.Context, opts ...Opts) *GenVars { +func New(ctx context.Context, opts ...Opts) *Generator { // defaultStrategy := NewDefatultStrategy() - return newGenVars(ctx, opts...) + return new(ctx, opts...) } -func newGenVars(ctx context.Context, opts ...Opts) *GenVars { +func new(ctx context.Context, opts ...Opts) *Generator { conf := config.NewConfig() - g := &GenVars{ + g := &Generator{ Logger: log.New(io.Discard), ctx: ctx, // return using default config + store: store.New(ctx), config: *conf, } - g.strategy = nil // now apply additional opts for _, o := range opts { @@ -58,16 +68,16 @@ func newGenVars(ctx context.Context, opts ...Opts) *GenVars { return g } -// WithStrategyMap +// WithStores assigns additional stores to the strategy // // Adds addtional funcs for storageRetrieval used for testing only -func (c *GenVars) WithStrategyMap(sm strategy.StrategyFuncMap) *GenVars { - c.strategy = sm +func (c *Generator) WithStores(sm storeIface) *Generator { + c.store = sm return c } // WithConfig uses custom config -func (c *GenVars) WithConfig(cfg *config.GenVarsConfig) *GenVars { +func (c *Generator) WithConfig(cfg *config.GenVarsConfig) *Generator { // backwards compatibility if cfg != nil { c.config = *cfg @@ -75,34 +85,53 @@ func (c *GenVars) WithConfig(cfg *config.GenVarsConfig) *GenVars { return c } -// WithContext uses caller passed context -func (c *GenVars) WithContext(ctx context.Context) *GenVars { - c.ctx = ctx - return c -} +// // WithContext uses caller passed context +// func (c *Generator) WithContext(ctx context.Context) *Generator { +// c.ctx = ctx +// return c +// } // Config gets Config on the GenVars -func (c *GenVars) Config() *config.GenVarsConfig { +func (c *Generator) Config() *config.GenVarsConfig { return &c.config } +func (c *Generator) InitPlugins(tokenset []string) error { + // initialise pugins here based on discovered tokens + // + // this can only be done once the tokens are known + if err := c.store.Init(c.ctx, tokenset); err != nil { + return fmt.Errorf("%w, %v", ErrProvidersNotFound, err) + } + defer c.store.PluginCleanUp() + return nil +} + // Generate generates a k/v map of the tokens with their corresponding secret/paramstore values // the standard pattern of a token should follow a path like string // // Called only from a slice of tokens -func (c *GenVars) Generate(tokens []string) (ReplacedToken, error) { +func (c *Generator) Generate(tokens []string) (ReplacedToken, error) { ntm, err := c.DiscoverTokens(strings.Join(tokens, "\n")) if err != nil { return nil, err } + // initialise pugins here based on discovered tokens + // + // this can only be done once the tokens are known + if err := c.store.Init(c.ctx, ntm.TokenSet()); err != nil { + return nil, fmt.Errorf("%w, %v", ErrProvidersNotFound, err) + } + defer c.store.PluginCleanUp() // pass in default initialised retrieveStrategy // input should be rt, err := c.generate(ntm) if err != nil { return nil, err } + return rt, nil } @@ -112,7 +141,7 @@ var ErrTokenDiscovery = errors.New("failed to discover tokens") // the standard pattern of a token should follow a path like string // // Called only from a slice of tokens -func (c *GenVars) DiscoverTokens(text string) (NormalizedTokenSafe, error) { +func (c *Generator) DiscoverTokens(text string) (NormalizedTokenSafe, error) { rtm := NewRawTokenConfig() @@ -135,8 +164,7 @@ func (c *GenVars) DiscoverTokens(text string) (NormalizedTokenSafe, error) { // and any characters func IsParsed(v any, trm ReplacedToken) bool { str := fmt.Sprint(v) - err := json.Unmarshal([]byte(str), &trm) - return err == nil + return json.Unmarshal([]byte(str), &trm) == nil } // generate initiates waitGroup to handle 1 or more normalized network calls concurrently to the underlying stores @@ -144,16 +172,14 @@ func IsParsed(v any, trm ReplacedToken) bool { // Captures the response/error in TokenResponse struct // It then denormalizes the NormalizedTokenSafe back to a ReplacedToken map // which stores the values for each token to be returned to the caller -func (c *GenVars) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { - if len(ntm.normalizedTokenMap) < 1 { +func (c *Generator) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { + if len(ntm.m) < 1 { c.Logger.Debug("no replaceable tokens found in input") return nil, nil } wg := &sync.WaitGroup{} - s := strategy.New(c.config, c.Logger, strategy.WithStrategyFuncMap(c.strategy)) - // safe read of normalized token map // this will ensure that we are minimizing // the number of network calls to each underlying store @@ -164,13 +190,14 @@ func (c *GenVars) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { } token := prsdTkn.parsedTokens[0] wg.Go(func() { - prsdTkn.resp = &strategy.TokenResponse{} - storeStrategy, err := s.GetImplementation(c.ctx, token) + prsdTkn.resp = &TokenResponse{} + prsdTkn.resp.WithKey(token) + val, err := c.store.GetValue(token) if err != nil { prsdTkn.resp.Err = err return } - prsdTkn.resp = strategy.ExchangeToken(storeStrategy, token) + prsdTkn.resp.WithValue(val) }) } @@ -179,6 +206,7 @@ func (c *GenVars) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { // now we fan out the normalized value to ReplacedToken map // this will ensure all found tokens will have a value assigned to them replacedToken := make(ReplacedToken) + notfound := []string{} for _, r := range ntm.GetMap() { if r == nil { // defensive as this shouldn't happen @@ -186,12 +214,19 @@ func (c *GenVars) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { } if r.resp.Err != nil { c.Logger.Debug("cr.err %v, for token: %s", r.resp.Err, r.resp.Key().String()) + if !c.config.LaxModeEnabled() { + // we want to collect all the errors + notfound = append(notfound, fmt.Sprintf("token: %s\n", r.resp.Key().String())) + } continue } for _, originalToken := range r.parsedTokens { replacedToken[originalToken.String()] = keySeparatorLookup(originalToken, r.resp.Value()) } } + if len(notfound) > 0 { + return replacedToken, fmt.Errorf("%w\n%v", ErrTokenNotFound, notfound) + } return replacedToken, nil } @@ -200,7 +235,9 @@ func (c *GenVars) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { // The idea is to minimize the number of networks calls to the underlying `store` Implementations // // The merging is based on the implemenentation and sanitized token being the same, -// if the token contains metadata then it must be +// if the token contains metadata then it must be stored uniquely even if the underlying store is the same. +// This is because a token with metadata must be called uniquely +// as it may contain different versions of the same token - hence the value would be different // // # Merging strategy // @@ -209,7 +246,7 @@ type NormalizedToken struct { // all the tokens that can be used to do a replacement parsedTokens []*config.ParsedTokenConfig // will be assigned post generate - resp *strategy.TokenResponse + resp *TokenResponse // // configToken is the last assigned full config in the loop if multip // configToken *config.ParsedTokenConfig } @@ -222,36 +259,49 @@ func (n *NormalizedToken) WithParsedToken(v *config.ParsedTokenConfig) *Normaliz // NormalizedTokenSafe is the map of lowest common denominators // by token.Keypathless or token.String (full token) if metadata is included type NormalizedTokenSafe struct { - mu *sync.Mutex - normalizedTokenMap map[string]*NormalizedToken + mu *sync.Mutex + m map[string]*NormalizedToken + set map[string]struct{} } func (n NormalizedTokenSafe) GetMap() map[string]*NormalizedToken { n.mu.Lock() defer n.mu.Unlock() - return n.normalizedTokenMap + return n.m +} + +func (n NormalizedTokenSafe) TokenSet() []string { + n.mu.Lock() + defer n.mu.Unlock() + ss := []string{} + for key := range n.set { + ss = append(ss, strings.ToLower(key)) + } + return ss } -func (c *GenVars) NormalizeRawToken(rtm *RawTokenConfig) NormalizedTokenSafe { - ntm := NormalizedTokenSafe{mu: &sync.Mutex{}, normalizedTokenMap: make(map[string]*NormalizedToken)} +func (c *Generator) NormalizeRawToken(rtm *RawTokenConfig) NormalizedTokenSafe { + ntm := NormalizedTokenSafe{mu: &sync.Mutex{}, m: make(map[string]*NormalizedToken), set: make(map[string]struct{})} for _, r := range rtm.RawTokenMap() { // if a string contains we need to store it uniquely // future improvements might group all the metadata values together if len(r.Metadata()) > 0 { - if n, found := ntm.normalizedTokenMap[r.String()]; found { + if n, found := ntm.m[r.String()]; found { n.WithParsedToken(r) continue } - ntm.normalizedTokenMap[r.String()] = (&NormalizedToken{}).WithParsedToken(r) + ntm.m[r.String()] = (&NormalizedToken{}).WithParsedToken(r) + ntm.set[string(r.Prefix())] = struct{}{} continue } - if n, found := ntm.normalizedTokenMap[r.Keypathless()]; found { + if n, found := ntm.m[r.Keypathless()]; found { n.WithParsedToken(r) continue } - ntm.normalizedTokenMap[r.Keypathless()] = (&NormalizedToken{}).WithParsedToken(r) + ntm.m[r.Keypathless()] = (&NormalizedToken{}).WithParsedToken(r) + ntm.set[string(r.Prefix())] = struct{}{} continue } return ntm diff --git a/generator/generator_test.go b/generator/generator_test.go index e48e546..f849d71 100644 --- a/generator/generator_test.go +++ b/generator/generator_test.go @@ -1,43 +1,45 @@ package generator_test import ( - "bytes" "context" + "errors" "fmt" "slices" "testing" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/generator" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" - "github.com/DevLabFoundry/configmanager/v3/internal/strategy" "github.com/DevLabFoundry/configmanager/v3/internal/testutils" ) -type mockGenerate struct { - inToken, value string - err error +type mockStore struct { + getVal func(implemenation *config.ParsedTokenConfig) (string, error) + init func(ctx context.Context, implt []string) error } -func (m *mockGenerate) SetToken(s *config.ParsedTokenConfig) { +func (m mockStore) GetValue(implemenation *config.ParsedTokenConfig) (string, error) { + return m.getVal(implemenation) } -func (m *mockGenerate) Value() (s string, e error) { - return m.value, m.err + +func (m mockStore) Init(ctx context.Context, implt []string) error { + if m.init != nil { + return m.init(ctx, implt) + } + return nil } -func TestGenerate(t *testing.T) { +func (m mockStore) PluginCleanUp() {} + +func Test_Generate(t *testing.T) { - t.Run("succeeds with funcMap", func(t *testing.T) { - var custFunc = func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"AWSPARAMSTR://mountPath/token", "bar", nil} - return m, nil + t.Run("succeeds", func(t *testing.T) { + m := &mockStore{} + m.getVal = func(implemenation *config.ParsedTokenConfig) (string, error) { + return `{"foo":"bar","key1":{"key2":"val"}}`, nil } + g := generator.New(context.TODO()) + g.WithStores(m) - g := generator.NewGenerator(context.TODO(), func(gv *generator.GenVars) { - gv.Logger = log.New(&bytes.Buffer{}) - }) - g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) if err != nil { @@ -48,80 +50,122 @@ func TestGenerate(t *testing.T) { } }) - t.Run("errors in retrieval and logs it out", func(t *testing.T) { - var custFunc = func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"AWSPARAMSTR://mountPath/token", "bar", fmt.Errorf("failed to get value")} - return m, nil + t.Run("fails to init providers", func(t *testing.T) { + m := &mockStore{} + m.init = func(ctx context.Context, implt []string) error { + return fmt.Errorf("failed to find providers") } - g := generator.NewGenerator(context.TODO()) - g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) - got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) + g := generator.New(context.TODO()) + g.WithStores(m) - if err != nil { - t.Fatal("errored on generate") + _, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) + + if err == nil { + t.Fatal("got nil, wanted err") } - if len(got) != 0 { - t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) + if !errors.Is(err, generator.ErrProvidersNotFound) { + t.Errorf("got %v, wanted %v", err, generator.ErrProvidersNotFound) + } + }) + t.Run("ignores no tokens", func(t *testing.T) { + m := &mockStore{} + g := generator.New(context.TODO()) + g.WithStores(m) + + _, err := g.Generate([]string{}) + + if err != nil { + t.Fatal("got nil, wanted err") } }) + t.Run("lax mode enabled - maintains v2 behaviour no rerrors in retrieval", func(t *testing.T) { - t.Run("retrieves values correctly from a keylookup inside", func(t *testing.T) { - var custFunc = func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"token-unused", `{"foo":"bar","key1":{"key2":"val"}}`, nil} - return m, nil + m := &mockStore{} + m.getVal = func(implemenation *config.ParsedTokenConfig) (string, error) { + return ``, fmt.Errorf("failed to get value") } - g := generator.NewGenerator(context.TODO()) - g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) - got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token|key1.key2"}) + g := generator.New(context.TODO()) + g.Config().WithLaxMode(true) + g.WithStores(m) + + got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) if err != nil { t.Fatal("errored on generate") } - if len(got) != 1 { + if len(got) != 0 { t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) } - if got["AWSPARAMSTR://mountPath/token|key1.key2"] != "val" { - t.Errorf(testutils.TestPhraseWithContext, "incorrect value returned in parsedMap", got["AWSPARAMSTR://mountPath/token|key1.key2"], "val") + }) + t.Run("errors in retrieval and logs it out", func(t *testing.T) { + m := &mockStore{} + m.getVal = func(implemenation *config.ParsedTokenConfig) (string, error) { + return ``, fmt.Errorf("failed to get value") + } + g := generator.New(context.TODO()) + g.WithStores(m) + + _, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) + + if err == nil { + t.Fatal("got nil, wanted err") + } + if !errors.Is(err, generator.ErrTokenNotFound) { + t.Errorf("got %v, wanted %v", err, generator.ErrTokenNotFound) } }) } func TestGenerate_withKeys_lookup(t *testing.T) { + ttests := map[string]struct { - custFunc strategy.StrategyFunc + store func(t *testing.T) *mockStore token string expectVal string }{ "retrieves string value correctly from a keylookup inside": { - custFunc: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"token", `{"foo":"bar","key1":{"key2":"val"}}`, nil} - return m, nil + store: func(t *testing.T) *mockStore { + m := &mockStore{} + m.getVal = func(implemenation *config.ParsedTokenConfig) (string, error) { + return `{"foo":"bar","key1":{"key2":"val"}}`, nil + } + return m }, token: "AWSPARAMSTR://mountPath/token|key1.key2", expectVal: "val", }, "retrieves number value correctly from a keylookup inside": { - custFunc: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"token", `{"foo":"bar","key1":{"key2":123}}`, nil} - return m, nil + + store: func(t *testing.T) *mockStore { + m := &mockStore{} + m.getVal = func(implemenation *config.ParsedTokenConfig) (string, error) { + return `{"foo":"bar","key1":{"key2":123}}`, nil + } + return m }, token: "AWSPARAMSTR://mountPath/token|key1.key2", expectVal: "123", }, "retrieves nothing as keylookup is incorrect": { - custFunc: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"token", `{"foo":"bar","key1":{"key2":123}}`, nil} - return m, nil + store: func(t *testing.T) *mockStore { + m := &mockStore{} + m.getVal = func(implemenation *config.ParsedTokenConfig) (string, error) { + return `{"foo":"bar","key1":{"key2":123}}`, nil + } + return m }, token: "AWSPARAMSTR://mountPath/token|noprop", expectVal: "", }, "retrieves value as is due to incorrectly stored json in backing store": { - custFunc: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"token", `foo":"bar","key1":{"key2":123}}`, nil} - return m, nil + store: func(t *testing.T) *mockStore { + m := &mockStore{} + m.getVal = func(implemenation *config.ParsedTokenConfig) (string, error) { + return `foo":"bar","key1":{"key2":123}}`, nil + } + return m }, token: "AWSPARAMSTR://mountPath/token|noprop", expectVal: `foo":"bar","key1":{"key2":123}}`, @@ -129,8 +173,8 @@ func TestGenerate_withKeys_lookup(t *testing.T) { } for name, tt := range ttests { t.Run(name, func(t *testing.T) { - g := generator.NewGenerator(context.TODO()) - g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: tt.custFunc}) + g := generator.New(context.TODO()) + g.WithStores(tt.store(t)) got, err := g.Generate([]string{tt.token}) if err != nil { @@ -172,42 +216,6 @@ func Test_IsParsed(t *testing.T) { } } -func TestGenVars_NormalizeRawToken(t *testing.T) { - - t.Run("multiple tokens", func(t *testing.T) { - g := generator.NewGenerator(context.TODO()) - - input := `GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj - GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|a - GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|b - GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|c - AWSPARAMSTR:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj - AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj[version=123] - AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|key1 - AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|key2 - AZKVSECRET:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj - VAULT:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj` - want := []string{"GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", - "AWSPARAMSTR:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", - "AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj[version=123]", - "AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", - "AZKVSECRET:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", - "VAULT:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj"} - got, err := g.DiscoverTokens(input) - if err != nil { - t.Fatal(err) - } - if len(got.GetMap()) != len(want) { - t.Errorf("got %v wanted %d", len(got.GetMap()), len(want)) - } - for key := range got.GetMap() { - if !slices.Contains(want, key) { - t.Errorf("got %s, wanted to be included in %v", key, want) - } - } - }) -} - func Test_ConfigManager_DiscoverTokens(t *testing.T) { ttests := map[string]struct { input string @@ -297,8 +305,8 @@ func Test_ConfigManager_DiscoverTokens(t *testing.T) { } for name, tt := range ttests { t.Run(name, func(t *testing.T) { - config.VarPrefix = map[config.ImplementationPrefix]bool{"AWSPARAMSTR": true} - g := generator.NewGenerator(context.TODO()) + // config.VarPrefix = map[config.ImplementationPrefix]bool{"AWSPARAMSTR": true} + g := generator.New(context.TODO()) g.Config().WithTokenSeparator(tt.separator) gdt, err := g.DiscoverTokens(tt.input) if err != nil { @@ -309,62 +317,62 @@ func Test_ConfigManager_DiscoverTokens(t *testing.T) { if len(got) != len(tt.expect) { t.Errorf("wrong nmber of tokens resolved\ngot (%d) want (%d)", len(got), len(tt.expect)) } - // for _, v := range got { - // if !slices.Contains(tt.expect, v.String()) { - // t.Errorf("got (%s) not found in expected slice (%v)", v, tt.expect) - // } - // } + for key := range got { + if !slices.Contains(tt.expect, key) { + t.Errorf("got (%s) not found in expected slice (%v)", key, tt.expect) + } + } }) } } -func Test_Generate_EnsureRaceFree(t *testing.T) { - g := generator.NewGenerator(context.TODO()) - - input := ` -fg -dfg gdfgfdGCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj -GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|a -GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|b -GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|c -ddsffds AWSPARAMSTR:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj - 'AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj[version=123]' - AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|key1 - AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|key2 - AZKVSECRET:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj gdf gdfgdf - dfg gdf gdf gdf - fdg dgf dgf - VAULT:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj . dfg dfgdf dfg fddf` - - g.WithStrategyMap(strategy.StrategyFuncMap{ - config.GcpSecretsPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"a":"bar","b":{"key2":"val"},"c":123}`, nil} - return m, nil - }, - config.ParamStorePrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"a":"bar","b":{"key2":"val"},"c":123}`, nil} - return m, nil - }, - config.SecretMgrPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"key1":"bar","key2":"val","c":123}`, nil} - return m, nil - }, - config.AzKeyVaultSecretsPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"key1":"bar","key2":"val","c":123}`, nil} - return m, nil - }, - config.HashicorpVaultPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"key1":"bar","key2":"val","c":123}`, nil} - return m, nil - }, - }) +// func Test_Generate_EnsureRaceFree(t *testing.T) { +// g := generator.New(context.TODO()) - got, err := g.Generate([]string{input}) - if err != nil { - t.Fatal(err) - } - if len(got) != 10 { - t.Errorf("got %v wanted %d", len(got), 10) - } +// input := ` +// fg +// dfg gdfgfdGCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj +// GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|a +// GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|b +// GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|c +// ddsffds AWSPARAMSTR:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj +// 'AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj[version=123]' +// AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|key1 +// AWSSECRETS://bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|key2 +// AZKVSECRET:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj gdf gdfgdf +// dfg gdf gdf gdf +// fdg dgf dgf +// VAULT:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj . dfg dfgdf dfg fddf` -} +// g.WithStrategyMap(strategy.StrategyFuncMap{ +// config.GcpSecretsPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { +// m := &mockGenerate{"/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"a":"bar","b":{"key2":"val"},"c":123}`, nil} +// return m, nil +// }, +// config.ParamStorePrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { +// m := &mockGenerate{"/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"a":"bar","b":{"key2":"val"},"c":123}`, nil} +// return m, nil +// }, +// config.SecretMgrPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { +// m := &mockGenerate{"bar/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"key1":"bar","key2":"val","c":123}`, nil} +// return m, nil +// }, +// config.AzKeyVaultSecretsPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { +// m := &mockGenerate{"/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"key1":"bar","key2":"val","c":123}`, nil} +// return m, nil +// }, +// config.HashicorpVaultPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { +// m := &mockGenerate{"/djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj", `{"key1":"bar","key2":"val","c":123}`, nil} +// return m, nil +// }, +// }) + +// got, err := g.Generate([]string{input}) +// if err != nil { +// t.Fatal(err) +// } +// if len(got) != 10 { +// t.Errorf("got %v wanted %d", len(got), 10) +// } + +// } diff --git a/generator/generatorvars.go b/generator/generatorvars.go index 79a56ae..6591d65 100644 --- a/generator/generatorvars.go +++ b/generator/generatorvars.go @@ -5,7 +5,7 @@ import ( "strconv" "sync" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/spyzhov/ajson" ) @@ -43,25 +43,27 @@ func (rtm *RawTokenConfig) RawTokenMap() map[string]*config.ParsedTokenConfig { return rtm.tokenMap } -// type tokenMapSafe struct { -// mu *sync.Mutex -// tokenMap ReplacedToken -// } - -// func (tms *tokenMapSafe) getTokenMap() ReplacedToken { -// tms.mu.Lock() -// defer tms.mu.Unlock() -// return tms.tokenMap -// } - -// func (tms *tokenMapSafe) addKeyVal(key *config.ParsedTokenConfig, val string) { -// tms.mu.Lock() -// defer tms.mu.Unlock() -// // NOTE: still use the metadata in the key -// // there could be different versions / labels for the same token and hence different values -// // However the JSONpath look up -// tms.tokenMap[key.String()] = keySeparatorLookup(key, val) -// } +type TokenResponse struct { + val string + key *config.ParsedTokenConfig + Err error +} + +func (tr *TokenResponse) WithKey(key *config.ParsedTokenConfig) { + tr.key = key +} + +func (tr *TokenResponse) WithValue(val string) { + tr.val = val +} + +func (tr *TokenResponse) Key() *config.ParsedTokenConfig { + return tr.key +} + +func (tr *TokenResponse) Value() string { + return tr.val +} // keySeparatorLookup checks if the key contains // keySeparator character diff --git a/go.mod b/go.mod index 1d37d5a..77b53d8 100644 --- a/go.mod +++ b/go.mod @@ -1,98 +1,43 @@ module github.com/DevLabFoundry/configmanager/v3 -go 1.25.3 +go 1.26 + +toolchain go1.26.1 require ( - cloud.google.com/go/secretmanager v1.16.0 - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 - github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0 - github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.4.0 - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 - github.com/aws/aws-sdk-go-v2 v1.39.6 - github.com/aws/aws-sdk-go-v2/config v1.31.17 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.11 - github.com/aws/aws-sdk-go-v2/service/ssm v1.66.4 github.com/go-test/deep v1.1.1 - github.com/googleapis/gax-go/v2 v2.15.0 - github.com/hashicorp/vault/api v1.22.0 - github.com/hashicorp/vault/api/auth/aws v0.11.0 - github.com/spf13/cobra v1.10.1 + github.com/spf13/cobra v1.10.2 github.com/spyzhov/ajson v0.9.6 + google.golang.org/grpc v1.79.3 + google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) require ( - cloud.google.com/go/auth v0.17.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.5.3 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect + github.com/oklog/run v1.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/stretchr/testify v1.11.1 // indirect + golang.org/x/term v0.41.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect +) + +require ( github.com/a8m/envsubst v1.4.3 - github.com/aws/aws-sdk-go v1.55.8 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.21 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 // indirect - github.com/aws/smithy-go v1.23.2 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/fatih/color v1.18.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-jose/go-jose/v4 v4.1.3 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.0 // indirect - github.com/google/s2a-go v0.1.9 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.7 // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-hclog v1.6.3 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-retryablehttp v0.7.8 // indirect - github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect - github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.7 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/hashicorp/go-plugin v1.7.0 github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/schollz/progressbar/v3 v3.19.0 github.com/spf13/pflag v1.0.10 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect - go.opentelemetry.io/otel v1.38.0 // indirect - go.opentelemetry.io/otel/metric v1.38.0 // indirect - go.opentelemetry.io/otel/trace v1.38.0 // indirect - golang.org/x/crypto v0.43.0 // indirect - golang.org/x/net v0.46.0 // indirect - golang.org/x/oauth2 v0.32.0 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/sys v0.37.0 // indirect - golang.org/x/text v0.30.0 // indirect - golang.org/x/time v0.14.0 // indirect - google.golang.org/api v0.255.0 // indirect - google.golang.org/genproto v0.0.0-20251103181224-f26f9409b101 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 // indirect - google.golang.org/grpc v1.76.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 // indirect ) diff --git a/go.sum b/go.sum index f40c7f4..f19afa2 100644 --- a/go.sum +++ b/go.sum @@ -1,162 +1,46 @@ -cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= -cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= -cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= -cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= -cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= -cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= -cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= -cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc= -cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU= -cloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYzilxVyT+k= -cloud.google.com/go/secretmanager v1.16.0/go.mod h1://C/e4I8D26SDTz1f3TQcddhcmiC3rMEl0S1Cakvs3Q= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= -github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0 h1:uU4FujKFQAz31AbWOO3INV9qfIanHeIUSsGhRlcJJmg= -github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0/go.mod h1:qr3M3Oy6V98VR0c5tCHKUpaeJTRQh6KYzJewRtFWqfc= -github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.4.0 h1:mXlQ+2C8A4KpXTIIYYxgFYqSivjGTBQidq/b0xxZLuk= -github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.4.0/go.mod h1:K//Ck7MUa+r9jpV69WLeWnnju5WJx5120AFsEzvumII= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 h1:/g8S6wk65vfC6m3FIxJ+i5QDyN9JWwXI8Hb0Img10hU= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0/go.mod h1:gpl+q95AzZlKVI3xSoseF9QPrypk0hQqBiJYeB/cR/I= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= -github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= -github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= -github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= -github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY= github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU= -github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= -github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= -github.com/aws/aws-sdk-go-v2 v1.39.6 h1:2JrPCVgWJm7bm83BDwY5z8ietmeJUbh3O2ACnn+Xsqk= -github.com/aws/aws-sdk-go-v2 v1.39.6/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= -github.com/aws/aws-sdk-go-v2/config v1.31.17 h1:QFl8lL6RgakNK86vusim14P2k8BFSxjvUkcWLDjgz9Y= -github.com/aws/aws-sdk-go-v2/config v1.31.17/go.mod h1:V8P7ILjp/Uef/aX8TjGk6OHZN6IKPM5YW6S78QnRD5c= -github.com/aws/aws-sdk-go-v2/credentials v1.18.21 h1:56HGpsgnmD+2/KpG0ikvvR8+3v3COCwaF4r+oWwOeNA= -github.com/aws/aws-sdk-go-v2/credentials v1.18.21/go.mod h1:3YELwedmQbw7cXNaII2Wywd+YY58AmLPwX4LzARgmmA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13 h1:T1brd5dR3/fzNFAQch/iBKeX07/ffu/cLu+q+RuzEWk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.13/go.mod h1:Peg/GBAQ6JDt+RoBf4meB1wylmAipb7Kg2ZFakZTlwk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13 h1:a+8/MLcWlIxo1lF9xaGt3J/u3yOZx+CdSveSNwjhD40= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.13/go.mod h1:oGnKwIYZ4XttyU2JWxFrwvhF6YKiK/9/wmE3v3Iu9K8= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13 h1:HBSI2kDkMdWz4ZM7FjwE7e/pWDEZ+nR95x8Ztet1ooY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.13/go.mod h1:YE94ZoDArI7awZqJzBAZ3PDD2zSfuP7w6P2knOzIn8M= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3 h1:x2Ibm/Af8Fi+BH+Hsn9TXGdT+hKbDd5XOTZxTMxDk7o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.3/go.mod h1:IW1jwyrQgMdhisceG8fQLmQIydcT/jWY21rFhzgaKwo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13 h1:kDqdFvMY4AtKoACfzIGD8A0+hbT41KTKF//gq7jITfM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.13/go.mod h1:lmKuogqSU3HzQCwZ9ZtcqOc5XGMqtDK7OIc2+DxiUEg= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.11 h1:DouhxUREBjfnNJFp1yNn/p1Gk5pzr1YNixcIOIudI2g= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.11/go.mod h1:QgVIY03/XoQs2iFr0MbQuQ/Tf1RwlkOvuySWMh1wph4= -github.com/aws/aws-sdk-go-v2/service/ssm v1.66.4 h1:UmkF0ipNy0Ps6csJl/ZRJ3K+DWe9q0A7LT3xfxoHbgg= -github.com/aws/aws-sdk-go-v2/service/ssm v1.66.4/go.mod h1:uNHuYAQazkHqpD+hVomA2+eDSuKJzerno7Fnha6N6/Y= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.1 h1:0JPwLz1J+5lEOfy/g0SURC9cxhbQ1lIMHMa+AHZSzz0= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.1/go.mod h1:fKvyjJcz63iL/ftA6RaM8sRCtN4r4zl4tjL3qw5ec7k= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5 h1:OWs0/j2UYR5LOGi88sD5/lhN6TDLG6SfA7CqsQO9zF0= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.5/go.mod h1:klO+ejMvYsB4QATfEOIXk8WAEwN4N0aBfJpvC+5SZBo= -github.com/aws/aws-sdk-go-v2/service/sts v1.39.1 h1:mLlUgHn02ue8whiR4BmxxGJLR2gwU6s6ZzJ5wDamBUs= -github.com/aws/aws-sdk-go-v2/service/sts v1.39.1/go.mod h1:E19xDjpzPZC7LS2knI9E6BaRFDK43Eul7vd6rSq2HWk= -github.com/aws/smithy-go v1.23.2 h1:Crv0eatJUQhaManss33hS5r40CG3ZFH+21XSkqMrIUM= -github.com/aws/smithy-go v1.23.2/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= -github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= -github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= -github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= -github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= -github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= -github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= -github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.7 h1:zrn2Ee/nWmHulBx5sAVrGgAa0f2/R35S4DJwfFaUPFQ= -github.com/googleapis/enterprise-certificate-proxy v0.3.7/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= -github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -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 v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= -github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= -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/awsutil v0.3.0 h1:I8bynUKMh9I7JdwtW9voJ0xmHvBpxQtLjrMFDYmhOxY= -github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0/go.mod h1:oKHSQs4ivIfZ3fbXGQOop1XuDfdSb8RIsWTGaAanSfg= -github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= -github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= -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/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= -github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= -github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= -github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= -github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= -github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= -github.com/hashicorp/vault/api/auth/aws v0.11.0 h1:lWdUxrzvPotg6idNr62al4w97BgI9xTDdzMCTViNH2s= -github.com/hashicorp/vault/api/auth/aws v0.11.0/go.mod h1:PWqdH/xqaudapmnnGP9ip2xbxT/kRW2qEgpqiQff6Gc= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= -github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -165,104 +49,69 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -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/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= -github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= -github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= +github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 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/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= -github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc= +github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spyzhov/ajson v0.9.6 h1:iJRDaLa+GjhCDAt1yFtU/LKMtLtsNVKkxqlpvrHHlpQ= github.com/spyzhov/ajson v0.9.6/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= -go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= -go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= -go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= -go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= -go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= -go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= -go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= -golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= -golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/api v0.255.0 h1:OaF+IbRwOottVCYV2wZan7KUq7UeNUQn1BcPc4K7lE4= -google.golang.org/api v0.255.0/go.mod h1:d1/EtvCLdtiWEV4rAEHDHGh2bCnqsWhw+M8y2ECN4a8= -google.golang.org/genproto v0.0.0-20251103181224-f26f9409b101 h1:MgBTzgUJFAmp2PlyqKJecSpZpjFxkYL3nDUIeH/6Q30= -google.golang.org/genproto v0.0.0-20251103181224-f26f9409b101/go.mod h1:bbWg36d7wp3knc0hIlmJAnW5R/CQ2rzpEVb72eH4ex4= -google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 h1:vk5TfqZHNn0obhPIYeS+cxIFKFQgser/M2jnI+9c6MM= -google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101/go.mod h1:E17fc4PDhkr22dE3RgnH2hEubUaky6ZwW4VhANxyspg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101 h1:tRPGkdGHuewF4UisLzzHHr1spKw92qLM98nIzxbC0wY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251103181224-f26f9409b101/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= -google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 h1:sRy++txmErSjyVWlIgQB5nB+U75+Di+AH7eEZ002B/s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -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= -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/internal/cmdutils/cmdutils.go b/internal/cmdutils/cmdutils.go index b86b292..360b7d3 100644 --- a/internal/cmdutils/cmdutils.go +++ b/internal/cmdutils/cmdutils.go @@ -11,8 +11,8 @@ import ( "os" "strings" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/generator" - "github.com/DevLabFoundry/configmanager/v3/internal/config" "github.com/DevLabFoundry/configmanager/v3/internal/log" "github.com/spf13/cobra" ) diff --git a/internal/cmdutils/cmdutils_test.go b/internal/cmdutils/cmdutils_test.go index 7ec1daf..f408f12 100644 --- a/internal/cmdutils/cmdutils_test.go +++ b/internal/cmdutils/cmdutils_test.go @@ -8,9 +8,9 @@ import ( "strings" "testing" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/generator" "github.com/DevLabFoundry/configmanager/v3/internal/cmdutils" - "github.com/DevLabFoundry/configmanager/v3/internal/config" log "github.com/DevLabFoundry/configmanager/v3/internal/log" "github.com/DevLabFoundry/configmanager/v3/internal/testutils" "github.com/spf13/cobra" diff --git a/internal/cmdutils/postprocessor.go b/internal/cmdutils/postprocessor.go index 3b4a33b..c166085 100644 --- a/internal/cmdutils/postprocessor.go +++ b/internal/cmdutils/postprocessor.go @@ -5,8 +5,8 @@ import ( "io" "strings" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/generator" - "github.com/DevLabFoundry/configmanager/v3/internal/config" ) // PostProcessor diff --git a/internal/cmdutils/postprocessor_test.go b/internal/cmdutils/postprocessor_test.go index 5c18e23..2668748 100644 --- a/internal/cmdutils/postprocessor_test.go +++ b/internal/cmdutils/postprocessor_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/generator" "github.com/DevLabFoundry/configmanager/v3/internal/cmdutils" - "github.com/DevLabFoundry/configmanager/v3/internal/config" "github.com/DevLabFoundry/configmanager/v3/internal/testutils" ) diff --git a/internal/lexer/lexer.go b/internal/lexer/lexer.go index a8f5d5a..ff352f3 100644 --- a/internal/lexer/lexer.go +++ b/internal/lexer/lexer.go @@ -4,7 +4,8 @@ package lexer import ( - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/DevLabFoundry/configmanager/v3/internal/token" ) // nonText characters captures all character sets that are _not_ assignable to TEXT @@ -59,93 +60,93 @@ func New(source Source, config config.GenVarsConfig) *Lexer { } // NextToken advances through the source returning a found token -func (l *Lexer) NextToken() config.Token { - var tok config.Token +func (l *Lexer) NextToken() token.Token { + var tok token.Token switch l.ch { // identify the dynamically selected key separator case l.keySeparator: - tok = config.Token{Type: config.CONFIGMANAGER_TOKEN_KEY_PATH_SEPARATOR, Literal: string(l.ch)} + tok = token.Token{Type: token.CONFIGMANAGER_TOKEN_KEY_PATH_SEPARATOR, Literal: string(l.ch)} // Specific cases for BEGIN_CONFIGMANAGER_TOKEN possibilities case 'A': if l.peekChar() == 'W' { // AWS store types l.readChar() if found, literal, imp := l.peekIsBeginOfToken([]config.ImplementationPrefix{config.SecretMgrPrefix, config.ParamStorePrefix}, "AW"); found { - tok = config.Token{Type: config.BEGIN_CONFIGMANAGER_TOKEN, Literal: literal, ImpPrefix: imp} + tok = token.Token{Type: token.BEGIN_CONFIGMANAGER_TOKEN, Literal: literal, ImpPrefix: imp} } else { // it is not a marker AW as text - tok = config.Token{Type: config.TEXT, Literal: "AW"} + tok = token.Token{Type: token.TEXT, Literal: "AW"} } } else if l.peekChar() == 'Z' { // Azure Store Types l.readChar() if found, literal, imp := l.peekIsBeginOfToken([]config.ImplementationPrefix{config.AzKeyVaultSecretsPrefix, config.AzTableStorePrefix, config.AzAppConfigPrefix}, "AZ"); found { - tok = config.Token{Type: config.BEGIN_CONFIGMANAGER_TOKEN, Literal: literal, ImpPrefix: imp} + tok = token.Token{Type: token.BEGIN_CONFIGMANAGER_TOKEN, Literal: literal, ImpPrefix: imp} } else { // it is not a marker AZ as text - tok = config.Token{Type: config.TEXT, Literal: "AZ"} + tok = token.Token{Type: token.TEXT, Literal: "AZ"} } } else { - tok = config.Token{Type: config.TEXT, Literal: "A"} + tok = token.Token{Type: token.TEXT, Literal: "A"} } case 'G': // GCP TOKENS if l.peekChar() == 'C' { l.readChar() if found, literal, imp := l.peekIsBeginOfToken([]config.ImplementationPrefix{config.GcpSecretsPrefix}, "GC"); found { - tok = config.Token{Type: config.BEGIN_CONFIGMANAGER_TOKEN, Literal: literal, ImpPrefix: imp} + tok = token.Token{Type: token.BEGIN_CONFIGMANAGER_TOKEN, Literal: literal, ImpPrefix: imp} } else { // it is not a marker - GC literal as text - tok = config.Token{Type: config.TEXT, Literal: "GC"} + tok = token.Token{Type: token.TEXT, Literal: "GC"} } } else { - tok = config.Token{Type: config.TEXT, Literal: "G"} + tok = token.Token{Type: token.TEXT, Literal: "G"} } case 'V': // HASHI VAULT Tokens if l.peekChar() == 'A' { l.readChar() if found, literal, imp := l.peekIsBeginOfToken([]config.ImplementationPrefix{config.HashicorpVaultPrefix}, "VA"); found { - tok = config.Token{Type: config.BEGIN_CONFIGMANAGER_TOKEN, Literal: literal, ImpPrefix: imp} + tok = token.Token{Type: token.BEGIN_CONFIGMANAGER_TOKEN, Literal: literal, ImpPrefix: imp} } else { // it is not a marker VA as text - tok = config.Token{Type: config.TEXT, Literal: "VA"} + tok = token.Token{Type: token.TEXT, Literal: "VA"} } } else { - tok = config.Token{Type: config.TEXT, Literal: "V"} + tok = token.Token{Type: token.TEXT, Literal: "V"} } case '=': - tok = config.Token{Type: config.EQUALS, Literal: "="} + tok = token.Token{Type: token.EQUALS, Literal: "="} case '.': - tok = config.Token{Type: config.DOT, Literal: "."} + tok = token.Token{Type: token.DOT, Literal: "."} case ',': - tok = config.Token{Type: config.COMMA, Literal: ","} + tok = token.Token{Type: token.COMMA, Literal: ","} case '/': if l.peekChar() == '?' { l.readChar() - tok = config.Token{Type: config.SLASH_QUESTION_MARK, Literal: "/?"} + tok = token.Token{Type: token.SLASH_QUESTION_MARK, Literal: "/?"} } else { - tok = config.Token{Type: config.FORWARD_SLASH, Literal: "/"} + tok = token.Token{Type: token.FORWARD_SLASH, Literal: "/"} } case '\\': - tok = config.Token{Type: config.BACK_SLASH, Literal: "\\"} + tok = token.Token{Type: token.BACK_SLASH, Literal: "\\"} case '?': - tok = config.Token{Type: config.QUESTION_MARK, Literal: "?"} + tok = token.Token{Type: token.QUESTION_MARK, Literal: "?"} case ']': - tok = config.Token{Type: config.END_META_CONFIGMANAGER_TOKEN, Literal: "]"} + tok = token.Token{Type: token.END_META_CONFIGMANAGER_TOKEN, Literal: "]"} case '[': - tok = config.Token{Type: config.BEGIN_META_CONFIGMANAGER_TOKEN, Literal: "["} + tok = token.Token{Type: token.BEGIN_META_CONFIGMANAGER_TOKEN, Literal: "["} case '|': - tok = config.Token{Type: config.PIPE, Literal: "|"} + tok = token.Token{Type: token.PIPE, Literal: "|"} case '@': - tok = config.Token{Type: config.AT_SIGN, Literal: "@"} + tok = token.Token{Type: token.AT_SIGN, Literal: "@"} case ':': - tok = config.Token{Type: config.COLON, Literal: ":"} + tok = token.Token{Type: token.COLON, Literal: ":"} case '"': - tok = config.Token{Type: config.DOUBLE_QUOTE, Literal: "\""} + tok = token.Token{Type: token.DOUBLE_QUOTE, Literal: "\""} case '\'': - tok = config.Token{Type: config.SINGLE_QUOTE, Literal: "'"} + tok = token.Token{Type: token.SINGLE_QUOTE, Literal: "'"} case '\n': l.line = l.line + 1 l.column = 0 // reset column count @@ -155,19 +156,19 @@ func (l *Lexer) NextToken() config.Token { tok = l.setTextSeparatorToken() case 0: tok.Literal = "" - tok.Type = config.EOF + tok.Type = token.EOF default: if isText(l.ch) { tok.Literal = l.readText() - tok.Type = config.TEXT + tok.Type = token.TEXT return tok } - tok = newToken(config.ILLEGAL, l.ch) + tok = newToken(token.ILLEGAL, l.ch) } // add general properties to each token tok.Line = l.line tok.Column = l.column - tok.Source = config.Source{Path: l.source.FullPath, File: l.source.FileName} + tok.Source = token.Source{Path: l.source.FullPath, File: l.source.FileName} l.readChar() return tok } @@ -201,8 +202,8 @@ func (l *Lexer) readText() string { return l.source.Input[position:l.position] } -func (l *Lexer) setTextSeparatorToken() config.Token { - tok := newToken(config.LookupIdent(string(l.ch)), l.ch) +func (l *Lexer) setTextSeparatorToken() token.Token { + tok := newToken(token.LookupIdent(string(l.ch)), l.ch) return tok } @@ -236,6 +237,6 @@ func isText(ch byte) bool { return !nonText[string(ch)] } -func newToken(tokenType config.TokenType, ch byte) config.Token { - return config.Token{Type: tokenType, Literal: string(ch)} +func newToken(tokenType token.TokenType, ch byte) token.Token { + return token.Token{Type: tokenType, Literal: string(ch)} } diff --git a/internal/lexer/lexer_test.go b/internal/lexer/lexer_test.go index 97f6ba0..ec90e35 100644 --- a/internal/lexer/lexer_test.go +++ b/internal/lexer/lexer_test.go @@ -3,8 +3,9 @@ package lexer_test import ( "testing" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/internal/lexer" + "github.com/DevLabFoundry/configmanager/v3/internal/token" ) func Test_Lexer_NextToken(t *testing.T) { @@ -13,60 +14,60 @@ foo=AWSPARAMSTR:///path|keyAWSSECRETS:///foo META_INCLUDED=VAULT://baz/bar/123|key1.prop2[role=arn:aws:iam::1111111:role,version=1082313] ` ttests := []struct { - expectedType config.TokenType + expectedType token.TokenType expectedLiteral string }{ - {config.TEXT, "foo"}, - {config.SPACE, " "}, - {config.TEXT, "stuyfsdfsf"}, - {config.NEW_LINE, "\n"}, - {config.TEXT, "foo"}, - {config.EQUALS, "="}, - {config.BEGIN_CONFIGMANAGER_TOKEN, "AWSPARAMSTR://"}, - {config.FORWARD_SLASH, "/"}, - {config.TEXT, "path"}, - {config.CONFIGMANAGER_TOKEN_KEY_PATH_SEPARATOR, "|"}, - {config.TEXT, "key"}, - {config.BEGIN_CONFIGMANAGER_TOKEN, "AWSSECRETS://"}, - {config.FORWARD_SLASH, "/"}, - {config.TEXT, "foo"}, - {config.NEW_LINE, "\n"}, - {config.TEXT, "MET"}, - {config.TEXT, "A"}, - {config.TEXT, "_INCLUDED"}, - // {config.TEXT, "U"}, - // {config.TEXT, "DED"}, - {config.EQUALS, "="}, - {config.BEGIN_CONFIGMANAGER_TOKEN, "VAULT://"}, - {config.TEXT, "baz"}, - {config.FORWARD_SLASH, "/"}, - {config.TEXT, "bar"}, - {config.FORWARD_SLASH, "/"}, - {config.TEXT, "123"}, - {config.CONFIGMANAGER_TOKEN_KEY_PATH_SEPARATOR, "|"}, - {config.TEXT, "key1"}, - {config.DOT, "."}, - {config.TEXT, "prop2"}, - {config.BEGIN_META_CONFIGMANAGER_TOKEN, "["}, - {config.TEXT, "role"}, - {config.EQUALS, "="}, - {config.TEXT, "arn"}, - {config.COLON, ":"}, - {config.TEXT, "aws"}, - {config.COLON, ":"}, - {config.TEXT, "iam"}, - {config.COLON, ":"}, - {config.COLON, ":"}, - {config.TEXT, "1111111"}, - {config.COLON, ":"}, - {config.TEXT, "role"}, - {config.COMMA, ","}, - {config.TEXT, "version"}, - {config.EQUALS, "="}, - {config.TEXT, "1082313"}, - {config.END_META_CONFIGMANAGER_TOKEN, "]"}, - {config.NEW_LINE, "\n"}, - {config.EOF, ""}, + {token.TEXT, "foo"}, + {token.SPACE, " "}, + {token.TEXT, "stuyfsdfsf"}, + {token.NEW_LINE, "\n"}, + {token.TEXT, "foo"}, + {token.EQUALS, "="}, + {token.BEGIN_CONFIGMANAGER_TOKEN, "AWSPARAMSTR://"}, + {token.FORWARD_SLASH, "/"}, + {token.TEXT, "path"}, + {token.CONFIGMANAGER_TOKEN_KEY_PATH_SEPARATOR, "|"}, + {token.TEXT, "key"}, + {token.BEGIN_CONFIGMANAGER_TOKEN, "AWSSECRETS://"}, + {token.FORWARD_SLASH, "/"}, + {token.TEXT, "foo"}, + {token.NEW_LINE, "\n"}, + {token.TEXT, "MET"}, + {token.TEXT, "A"}, + {token.TEXT, "_INCLUDED"}, + // {token.TEXT, "U"}, + // {token.TEXT, "DED"}, + {token.EQUALS, "="}, + {token.BEGIN_CONFIGMANAGER_TOKEN, "VAULT://"}, + {token.TEXT, "baz"}, + {token.FORWARD_SLASH, "/"}, + {token.TEXT, "bar"}, + {token.FORWARD_SLASH, "/"}, + {token.TEXT, "123"}, + {token.CONFIGMANAGER_TOKEN_KEY_PATH_SEPARATOR, "|"}, + {token.TEXT, "key1"}, + {token.DOT, "."}, + {token.TEXT, "prop2"}, + {token.BEGIN_META_CONFIGMANAGER_TOKEN, "["}, + {token.TEXT, "role"}, + {token.EQUALS, "="}, + {token.TEXT, "arn"}, + {token.COLON, ":"}, + {token.TEXT, "aws"}, + {token.COLON, ":"}, + {token.TEXT, "iam"}, + {token.COLON, ":"}, + {token.COLON, ":"}, + {token.TEXT, "1111111"}, + {token.COLON, ":"}, + {token.TEXT, "role"}, + {token.COMMA, ","}, + {token.TEXT, "version"}, + {token.EQUALS, "="}, + {token.TEXT, "1082313"}, + {token.END_META_CONFIGMANAGER_TOKEN, "]"}, + {token.NEW_LINE, "\n"}, + {token.EOF, ""}, } l := lexer.New(lexer.Source{Input: input, FullPath: "/foo/bar", FileName: "bar"}, *config.NewConfig()) @@ -83,7 +84,7 @@ META_INCLUDED=VAULT://baz/bar/123|key1.prop2[role=arn:aws:iam::1111111:role,vers t.Fatalf("tests[%d] - literal wrong. got=%q, expected=%q", i, tok.Literal, tt.expectedLiteral) } - if tok.Type == config.BEGIN_CONFIGMANAGER_TOKEN { + if tok.Type == token.BEGIN_CONFIGMANAGER_TOKEN { } } @@ -93,7 +94,7 @@ func Test_empty_file(t *testing.T) { input := `` l := lexer.New(lexer.Source{Input: input, FullPath: "/foo/bar", FileName: "bar"}, *config.NewConfig()) tok := l.NextToken() - if tok.Type != config.EOF { + if tok.Type != token.EOF { t.Fatal("expected EOF") } } diff --git a/internal/parser/parser.go b/internal/parser/parser.go index b785dc2..51ae8e4 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -3,11 +3,13 @@ package parser import ( "errors" "fmt" + "os" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/internal/lexer" "github.com/DevLabFoundry/configmanager/v3/internal/log" + "github.com/DevLabFoundry/configmanager/v3/internal/token" ) func wrapErr(incompleteToken *config.ParsedTokenConfig, sanitized string, line, position int, etyp error) error { @@ -20,17 +22,17 @@ var ( ) type ConfigManagerTokenBlock struct { - BeginToken config.Token + BeginToken token.Token ParsedToken config.ParsedTokenConfig - EndToken config.Token + EndToken token.Token } type Parser struct { l *lexer.Lexer errors []error log log.ILogger - currentToken config.Token - peekToken config.Token + currentToken token.Token + peekToken token.Token config *config.GenVarsConfig environ []string } @@ -68,10 +70,10 @@ func (p *Parser) WithLogger(logger log.ILogger) *Parser { func (p *Parser) Parse() ([]ConfigManagerTokenBlock, []error) { stmts := []ConfigManagerTokenBlock{} - for !p.currentTokenIs(config.EOF) { - if p.currentTokenIs(config.BEGIN_CONFIGMANAGER_TOKEN) { + for !p.currentTokenIs(token.EOF) { + if p.currentTokenIs(token.BEGIN_CONFIGMANAGER_TOKEN) { // continues to read the tokens until it hits an end token or errors - configManagerToken, err := config.NewToken(p.currentToken.ImpPrefix, *p.config) + configManagerToken, err := config.NewParsedToken(p.currentToken.ImpPrefix, *p.config) if err != nil { return nil, []error{err} } @@ -90,21 +92,21 @@ func (p *Parser) nextToken() { p.peekToken = p.l.NextToken() } -func (p *Parser) currentTokenIs(t config.TokenType) bool { +func (p *Parser) currentTokenIs(t token.TokenType) bool { return p.currentToken.Type == t } -func (p *Parser) peekTokenIs(t config.TokenType) bool { +func (p *Parser) peekTokenIs(t token.TokenType) bool { return p.peekToken.Type == t } func (p *Parser) peekTokenIsEnd() bool { - endTokens := map[config.TokenType]bool{ - config.AT_SIGN: true, config.QUESTION_MARK: true, config.COLON: true, - config.SLASH_QUESTION_MARK: true, config.EOF: true, + endTokens := map[token.TokenType]bool{ + token.AT_SIGN: true, token.QUESTION_MARK: true, token.COLON: true, + token.SLASH_QUESTION_MARK: true, token.EOF: true, // traditional ends of tokens - config.DOUBLE_QUOTE: true, config.SINGLE_QUOTE: true, config.SPACE: true, - config.NEW_LINE: true, + token.DOUBLE_QUOTE: true, token.SINGLE_QUOTE: true, token.SPACE: true, + token.NEW_LINE: true, } return endTokens[p.peekToken.Type] } @@ -121,11 +123,11 @@ func (p *Parser) buildConfigManagerTokenFromBlocks(configManagerToken *config.Pa sanitizedToken := "" // stop on end of file - for !p.peekTokenIs(config.EOF) { + for !p.peekTokenIs(token.EOF) { // // This is the target state when there is an optional token wrapping // // e.g. `{{ IMP://path }}` // // currently this is untestable - // if p.peekTokenIs(config.END_CONFIGMANAGER_TOKEN) { + // if p.peekTokenIs(token.END_CONFIGMANAGER_TOKEN) { // notFoundEnd = false // fullToken += p.curToken.Literal // sanitizedToken += p.curToken.Literal @@ -135,7 +137,7 @@ func (p *Parser) buildConfigManagerTokenFromBlocks(configManagerToken *config.Pa // when next token is another token // i.e. the tokens are adjacent - if p.peekTokenIs(config.BEGIN_CONFIGMANAGER_TOKEN) { + if p.peekTokenIs(token.BEGIN_CONFIGMANAGER_TOKEN) { sanitizedToken += p.currentToken.Literal stmt.EndToken = p.currentToken break @@ -155,7 +157,7 @@ func (p *Parser) buildConfigManagerTokenFromBlocks(configManagerToken *config.Pa // check key separator this marks the end of a normal token path // // keyLookup and Metadata are optional - is always specified in that order - if p.currentTokenIs(config.CONFIGMANAGER_TOKEN_KEY_PATH_SEPARATOR) { + if p.currentTokenIs(token.CONFIGMANAGER_TOKEN_KEY_PATH_SEPARATOR) { if err := p.buildKeyPathSeparator(configManagerToken); err != nil { p.errors = append(p.errors, wrapErr(configManagerToken, sanitizedToken, currentToken.Line, currentToken.Column, err)) return nil @@ -166,7 +168,7 @@ func (p *Parser) buildConfigManagerTokenFromBlocks(configManagerToken *config.Pa // optionally at the end of the path without key separator // check metadata there can be a metadata bracket `[key=val,k1=v2]` - if p.currentTokenIs(config.BEGIN_META_CONFIGMANAGER_TOKEN) { + if p.currentTokenIs(token.BEGIN_META_CONFIGMANAGER_TOKEN) { if err := p.buildMetadata(configManagerToken); err != nil { p.errors = append(p.errors, wrapErr(configManagerToken, sanitizedToken, currentToken.Line, currentToken.Column, err)) return nil @@ -180,7 +182,7 @@ func (p *Parser) buildConfigManagerTokenFromBlocks(configManagerToken *config.Pa // we want set the current token // else it would be lost once the parser is advanced below p.nextToken() - if p.peekTokenIs(config.EOF) { + if p.peekTokenIs(token.EOF) { sanitizedToken += p.currentToken.Literal stmt.EndToken = p.currentToken break @@ -198,14 +200,14 @@ func (p *Parser) buildKeyPathSeparator(configManagerToken *config.ParsedTokenCon // advance to next token i.e. post the path separator p.nextToken() keyPath := "" - if p.peekTokenIs(config.EOF) { + if p.peekTokenIs(token.EOF) { // if the next token EOF we set the path as current token and exit // otherwise we would never hit the below loop configManagerToken.WithKeyPath(p.currentToken.Literal) return nil } - for !p.peekTokenIs(config.EOF) { - if p.peekTokenIs(config.BEGIN_META_CONFIGMANAGER_TOKEN) { + for !p.peekTokenIs(token.EOF) { + if p.peekTokenIs(token.BEGIN_META_CONFIGMANAGER_TOKEN) { // add current token to the keysPath and move onto the metadata keyPath += p.currentToken.Literal p.nextToken() @@ -215,13 +217,13 @@ func (p *Parser) buildKeyPathSeparator(configManagerToken *config.ParsedTokenCon break } // touching another token or end of token - if p.peekTokenIs(config.BEGIN_CONFIGMANAGER_TOKEN) || p.peekTokenIsEnd() { + if p.peekTokenIs(token.BEGIN_CONFIGMANAGER_TOKEN) || p.peekTokenIsEnd() { keyPath += p.currentToken.Literal break } keyPath += p.currentToken.Literal p.nextToken() - if p.peekTokenIs(config.EOF) { + if p.peekTokenIs(token.EOF) { // check if the next token is EOF once advanced // if it is we want to consume current token else it will be skipped keyPath += p.currentToken.Literal @@ -238,16 +240,16 @@ var ErrMetadataEmpty = errors.New("emtpy metadata") func (p *Parser) buildMetadata(configManagerToken *config.ParsedTokenConfig) error { metadata := "" found := false - if p.peekTokenIs(config.END_META_CONFIGMANAGER_TOKEN) { + if p.peekTokenIs(token.END_META_CONFIGMANAGER_TOKEN) { return fmt.Errorf("%w, metadata brackets must include at least one set of key=value pairs", ErrMetadataEmpty) } p.nextToken() - for !p.peekTokenIs(config.EOF) { + for !p.peekTokenIs(token.EOF) { if p.peekTokenIsEnd() { // next token is an end of token but no closing `]` found return fmt.Errorf("%w, metadata (%s) string has no closing", ErrNoEndTagFound, metadata) } - if p.peekTokenIs(config.END_META_CONFIGMANAGER_TOKEN) { + if p.peekTokenIs(token.END_META_CONFIGMANAGER_TOKEN) { metadata += p.currentToken.Literal found = true p.nextToken() diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 2a97c04..b22b026 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -5,11 +5,10 @@ import ( "os" "testing" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/internal/lexer" "github.com/DevLabFoundry/configmanager/v3/internal/log" "github.com/DevLabFoundry/configmanager/v3/internal/parser" - "github.com/DevLabFoundry/configmanager/v3/internal/store" ) var lexerSource = lexer.Source{FileName: "bar", FullPath: "/foo/bar"} @@ -91,14 +90,14 @@ func Test_ParserBlocks(t *testing.T) { } if len(parsed) != len(tt.expected) { - t.Fatalf("parsed statements count does not match\ngot=%d want=%d\nparsed %q", + t.Fatalf("parsed statements count does not match\ngot=%d want=%d\nparsed %v", len(parsed), len(tt.expected), parsed) } for idx, stmt := range parsed { - if !testHelperGenDocBlock(t, stmt, config.ImplementationPrefix(tt.expected[idx][0]), tt.expected[idx][1], tt.expected[idx][2]) { + if !testHelperParsedBlock(t, stmt, config.ImplementationPrefix(tt.expected[idx][0]), tt.expected[idx][1], tt.expected[idx][2]) { return } } @@ -188,69 +187,71 @@ func Test_Parse_should_pass_with_metadata_end_tag(t *testing.T) { } } -func Test_Parse_ParseMetadata(t *testing.T) { +// func Test_Parse_ParseMetadata(t *testing.T) { - ttests := map[string]struct { - input string - typ *store.SecretsMgrConfig - }{ - "without keysPath": { - `AWSSECRETS:///foo[version=1.2.3]`, - &store.SecretsMgrConfig{}, - }, - "with keysPath": { - `AWSSECRETS:///foo|path.one[version=1.2.3]`, - &store.SecretsMgrConfig{}, - }, - "nestled in text": { - `someQ=AWSPARAMSTR:///path/queryparam|p1[version=1.2.3]&anotherQ`, - &store.SecretsMgrConfig{}, - }, - } - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - lexerSource.Input = tt.input - cfg := config.NewConfig() - l := lexer.New(lexerSource, *cfg) - p := parser.New(l, cfg).WithLogger(log.New(os.Stderr)) - parsed, errs := p.Parse() - if len(errs) > 0 { - t.Fatalf("%v", errs) - } +// ttests := map[string]struct { +// input string +// typ *store.SecretsMgrConfig +// }{ +// "without keysPath": { +// `AWSSECRETS:///foo[version=1.2.3]`, +// &store.SecretsMgrConfig{}, +// }, +// "with keysPath": { +// `AWSSECRETS:///foo|path.one[version=1.2.3]`, +// &store.SecretsMgrConfig{}, +// }, +// "nestled in text": { +// `someQ=AWSPARAMSTR:///path/queryparam|p1[version=1.2.3]&anotherQ`, +// &store.SecretsMgrConfig{}, +// }, +// } +// for name, tt := range ttests { +// t.Run(name, func(t *testing.T) { +// lexerSource.Input = tt.input +// cfg := config.NewConfig() +// l := lexer.New(lexerSource, *cfg) +// p := parser.New(l, cfg).WithLogger(log.New(os.Stderr)) +// parsed, errs := p.Parse() +// if len(errs) > 0 { +// t.Fatalf("%v", errs) +// } - for _, p := range parsed { - if err := p.ParsedToken.ParseMetadata(tt.typ); err != nil { - t.Fatal(err) - } - if tt.typ.Version != "1.2.3" { - t.Errorf("got %v wanted 1.2.3", tt.typ.Version) - } - } - }) - } -} +// for _, p := range parsed { +// if err := p.ParsedToken.ParseMetadata(tt.typ); err != nil { +// t.Fatal(err) +// } +// if tt.typ.Version != "1.2.3" { +// t.Errorf("got %v wanted 1.2.3", tt.typ.Version) +// } +// } +// }) +// } +// } func Test_Parse_Path_Keys_WithParsedMetadat(t *testing.T) { - + type version struct { + Version string + } ttests := map[string]struct { input string - typ *store.SecretsMgrConfig + typ *version wantSanitizedPath string wantKeyPath string }{ "without keysPath": { `AWSSECRETS:///foo[version=1.2.3]`, - &store.SecretsMgrConfig{}, + &version{}, "/foo", "", }, "with keysPath": { `AWSSECRETS:///foo|path.one[version=1.2.3]`, - &store.SecretsMgrConfig{}, + &version{}, "/foo", "path.one", }, "nestled in text": { `someQ=AWSPARAMSTR:///path/queryparam|p1[version=1.2.3]&anotherQ`, - &store.SecretsMgrConfig{}, + &version{}, "/path/queryparam", "p1", }, } @@ -283,7 +284,7 @@ func Test_Parse_Path_Keys_WithParsedMetadat(t *testing.T) { } } -func testHelperGenDocBlock(t *testing.T, stmtBlock parser.ConfigManagerTokenBlock, tokenType config.ImplementationPrefix, tokenValue, keysLookupPath string) bool { +func testHelperParsedBlock(t *testing.T, stmtBlock parser.ConfigManagerTokenBlock, tokenType config.ImplementationPrefix, tokenValue, keysLookupPath string) bool { t.Helper() if stmtBlock.ParsedToken.Prefix() != tokenType { t.Errorf("got=%q, wanted stmtBlock.ImpPrefix = '%v'.", stmtBlock.ParsedToken.Prefix(), tokenType) diff --git a/internal/store/azappconf.go b/internal/store/azappconf.go deleted file mode 100644 index c35f538..0000000 --- a/internal/store/azappconf.go +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Azure App Config implementation -**/ -package store - -import ( - "context" - "fmt" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" -) - -// appConfApi -// uses this package https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig -type appConfApi interface { - GetSetting(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error) -} - -type AzAppConf struct { - svc appConfApi - ctx context.Context - config *AzAppConfConfig - token *config.ParsedTokenConfig - strippedToken string - logger log.ILogger -} - -// AzAppConfConfig is the azure conf service specific config -// and it is parsed from the token metadata -type AzAppConfConfig struct { - Label string `json:"label"` - Etag *azcore.ETag `json:"etag"` - AcceptDateTime *time.Time `json:"acceptedDateTime"` -} - -// NewAzAppConf -func NewAzAppConf(ctx context.Context, token *config.ParsedTokenConfig, logger log.ILogger) (*AzAppConf, error) { - storeConf := &AzAppConfConfig{} - if err := token.ParseMetadata(storeConf); err != nil { - return nil, err - } - - backingStore := &AzAppConf{ - ctx: ctx, - config: storeConf, - token: token, - logger: logger, - } - srvInit := AzServiceFromToken(token.StoreToken(), "https://%s.azconfig.io", 1) - backingStore.strippedToken = srvInit.Token - - cred, err := azidentity.NewDefaultAzureCredential(nil) - if err != nil { - logger.Error("failed to get credentials: %v", err) - return nil, err - } - - c, err := azappconfig.NewClient(srvInit.ServiceUri, cred, nil) - if err != nil { - logger.Error("failed to init the client: %v", err) - return nil, fmt.Errorf("%v\n%w", err, ErrClientInitialization) - } - - backingStore.svc = c - return backingStore, nil - -} - -func (s *AzAppConf) WithSvc(svc appConfApi) { - s.svc = svc -} - -// setTokenVal sets the token -func (implmt *AzAppConf) SetToken(token *config.ParsedTokenConfig) {} - -// tokenVal in AZ App Config -// label can be specified -// From this point then normal rules of configmanager apply, -// including keySeperator and lookup. -func (imp *AzAppConf) Value() (string, error) { - imp.logger.Info("Concrete implementation AzAppConf") - imp.logger.Info("AzAppConf Token: %s", imp.token.String()) - - ctx, cancel := context.WithCancel(imp.ctx) - defer cancel() - opts := &azappconfig.GetSettingOptions{} - - // assign any metadatas from the token - if imp.config.Label != "" { - opts.Label = &imp.config.Label - } - - if imp.config.Etag != nil { - opts.OnlyIfChanged = imp.config.Etag - } - - s, err := imp.svc.GetSetting(ctx, imp.strippedToken, opts) - if err != nil { - imp.logger.Error(implementationNetworkErr, config.AzAppConfigPrefix, err, imp.strippedToken) - return "", fmt.Errorf("token: %s, error: %v. %w", imp.strippedToken, err, ErrRetrieveFailed) - } - if s.Value != nil { - return *s.Value, nil - } - imp.logger.Error("token: %v, %w", imp.token.String(), ErrEmptyResponse) - return "", nil -} diff --git a/internal/store/azappconf_test.go b/internal/store/azappconf_test.go deleted file mode 100644 index 17da526..0000000 --- a/internal/store/azappconf_test.go +++ /dev/null @@ -1,208 +0,0 @@ -package store_test - -import ( - "bytes" - "context" - "errors" - "fmt" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - logger "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" - "github.com/DevLabFoundry/configmanager/v3/internal/testutils" -) - -func azAppConfCommonChecker(t *testing.T, key string, expectedKey string, expectLabel string, opts *azappconfig.GetSettingOptions) { - t.Helper() - if key != expectedKey { - t.Errorf(testutils.TestPhrase, key, expectedKey) - } - - if expectLabel != "" { - if opts == nil { - t.Errorf(testutils.TestPhrase, nil, expectLabel) - } - if *opts.Label != expectLabel { - t.Errorf(testutils.TestPhrase, opts.Label, expectLabel) - } - } -} - -type mockAzAppConfApi func(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error) - -func (m mockAzAppConfApi) GetSetting(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error) { - return m(ctx, key, options) -} - -func Test_AzAppConf_Success(t *testing.T) { - tsuccessParam := "somecvla" - - logr := logger.New(&bytes.Buffer{}) - tests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - mockClient func(t *testing.T) mockAzAppConfApi - }{ - "successVal": { - func() *config.ParsedTokenConfig { - // "AZAPPCONF#/test-app-config-instance/table//token/1", - tkn, _ := config.NewToken(config.AzAppConfigPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-app-config-instance/table//token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - tsuccessParam, - func(t *testing.T) mockAzAppConfApi { - return mockAzAppConfApi(func(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error) { - azAppConfCommonChecker(t, key, "table//token/1", "", options) - resp := azappconfig.GetSettingResponse{} - resp.Value = &tsuccessParam - return resp, nil - }) - }, - }, - "successVal with :// token Separator": { - func() *config.ParsedTokenConfig { - // "AZAPPCONF:///test-app-config-instance/conf_key[label=dev]", - tkn, _ := config.NewToken(config.AzAppConfigPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("://")) - tkn.WithSanitizedToken("/test-app-config-instance/conf_key") - tkn.WithKeyPath("") - tkn.WithMetadata("label=dev") - return tkn - }, - tsuccessParam, - func(t *testing.T) mockAzAppConfApi { - return mockAzAppConfApi(func(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error) { - azAppConfCommonChecker(t, key, "conf_key", "dev", options) - resp := azappconfig.GetSettingResponse{} - resp.Value = &tsuccessParam - return resp, nil - }) - }, - }, - "successVal with :// token Separator and etag specified": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzAppConfigPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-app-config-instance/conf_key") - tkn.WithKeyPath("") - tkn.WithMetadata("label=dev,etag=sometifdsssdsfdi_string01209222") - return tkn - }, - tsuccessParam, - func(t *testing.T) mockAzAppConfApi { - return mockAzAppConfApi(func(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error) { - azAppConfCommonChecker(t, key, "conf_key", "dev", options) - if !options.OnlyIfChanged.Equals("sometifdsssdsfdi_string01209222") { - t.Errorf(testutils.TestPhraseWithContext, "Etag not correctly set", options.OnlyIfChanged, "sometifdsssdsfdi_string01209222") - } - resp := azappconfig.GetSettingResponse{} - resp.Value = &tsuccessParam - return resp, nil - }) - }, - }, - "successVal with keyseparator but no val returned": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzAppConfigPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-app-config-instance/try_to_find") - tkn.WithKeyPath("key_separator.lookup") - tkn.WithMetadata("") - return tkn - }, - "", - func(t *testing.T) mockAzAppConfApi { - return mockAzAppConfApi(func(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error) { - azAppConfCommonChecker(t, key, "try_to_find", "", options) - resp := azappconfig.GetSettingResponse{} - resp.Value = nil - return resp, nil - }) - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - impl, err := store.NewAzAppConf(context.TODO(), tt.token(), logr) - if err != nil { - t.Errorf("failed to init AZAPPCONF") - } - - impl.WithSvc(tt.mockClient(t)) - got, err := impl.Value() - if err != nil { - if err.Error() != tt.expect { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) - } - return - } - - if got != tt.expect { - t.Errorf(testutils.TestPhrase, got, tt.expect) - } - }) - } -} - -func Test_AzAppConf_Error(t *testing.T) { - logr := logger.New(&bytes.Buffer{}) - - tests := map[string]struct { - token func() *config.ParsedTokenConfig - expect error - mockClient func(t *testing.T) mockAzAppConfApi - }{ - "errored on service method call": { - func() *config.ParsedTokenConfig { - // "AZAPPCONF#/test-app-config-instance/table/token/ok", - tkn, _ := config.NewToken(config.AzAppConfigPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-app-config-instance/table/token/ok") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - store.ErrRetrieveFailed, - func(t *testing.T) mockAzAppConfApi { - return mockAzAppConfApi(func(ctx context.Context, key string, options *azappconfig.GetSettingOptions) (azappconfig.GetSettingResponse, error) { - t.Helper() - resp := azappconfig.GetSettingResponse{} - return resp, fmt.Errorf("network error") - }) - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - impl, err := store.NewAzAppConf(context.TODO(), tt.token(), logr) - if err != nil { - t.Fatal("failed to init AZAPPCONF") - } - impl.WithSvc(tt.mockClient(t)) - if _, err := impl.Value(); !errors.Is(err, tt.expect) { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) - } - }) - } -} - -func Test_fail_AzAppConf_Client_init(t *testing.T) { - - logr := logger.New(&bytes.Buffer{}) - - // this is basically a wrap around test for the url.Parse method in the stdlib - // as that is what the client uses under the hood - token, _ := config.NewToken(config.AzAppConfigPrefix, *config.NewConfig()) - token.WithSanitizedToken("/%25%65%6e%301-._~/") - } - if !errors.Is(err, store.ErrClientInitialization) { - t.Fatalf(testutils.TestPhraseWithContext, "azappconf client init", err.Error(), store.ErrClientInitialization.Error()) - } -} diff --git a/internal/store/azhelpers.go b/internal/store/azhelpers.go deleted file mode 100644 index 29e66e8..0000000 --- a/internal/store/azhelpers.go +++ /dev/null @@ -1,37 +0,0 @@ -package store - -import ( - "fmt" - "strings" -) - -/* -Generic Azure Service Init Helpers -*/ - -// AzServiceHelper returns a service URI and the stripped token -type AzServiceHelper struct { - ServiceUri string - Token string -} - -// AzServiceFromToken for azure the first part of the token __must__ always be the -// identifier of the service e.g. the account name for tableStore or the Vault name for KVSecret or -// AppConfig instance -// take parameter specifies the number of elements to take from the start only -// -// e.g. a value of 2 for take will take first 2 elements from the slices -// -// For AppConfig or KeyVault we ONLY need the AppConfig instance or KeyVault instance name -func AzServiceFromToken(token string, formatUri string, take int) AzServiceHelper { - // ensure preceding slash is trimmed - stringToken := strings.Split(strings.TrimPrefix(token, "/"), "/") - splitToken := []any{} - // recast []string slice to an []any - for _, st := range stringToken { - splitToken = append(splitToken, st) - } - - uri := fmt.Sprintf(formatUri, splitToken[0:take]...) - return AzServiceHelper{ServiceUri: uri, Token: strings.Join(stringToken[take:], "/")} -} diff --git a/internal/store/azkeyvault.go b/internal/store/azkeyvault.go deleted file mode 100644 index 781b066..0000000 --- a/internal/store/azkeyvault.go +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Azure KeyVault implementation -**/ -package store - -import ( - "context" - - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" -) - -type kvApi interface { - GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) -} - -type KvScrtStore struct { - svc kvApi - ctx context.Context - logger log.ILogger - token *config.ParsedTokenConfig - config *AzKvConfig - strippedToken string -} - -// AzKvConfig takes any metadata from the token -// Version is the only -type AzKvConfig struct { - Version string `json:"version"` -} - -// NewKvScrtStore returns a KvScrtStore -// requires `AZURE_SUBSCRIPTION_ID` environment variable to be present to successfully work -func NewKvScrtStore(ctx context.Context, token *config.ParsedTokenConfig, logger log.ILogger) (*KvScrtStore, error) { - - storeConf := &AzKvConfig{} - _ = token.ParseMetadata(storeConf) - - backingStore := &KvScrtStore{ - ctx: ctx, - logger: logger, - config: storeConf, - token: token, - } - - srvInit := AzServiceFromToken(token.StoreToken(), "https://%s.vault.azure.net", 1) - backingStore.strippedToken = srvInit.Token - - cred, err := azidentity.NewDefaultAzureCredential(nil) - if err != nil { - logger.Error("failed to get credentials: %v", err) - return nil, err - } - - c, err := azsecrets.NewClient(srvInit.ServiceUri, cred, nil) - if err != nil { - logger.Error("%v\n%w", err, ErrClientInitialization) - return nil, err - } - - backingStore.svc = c - return backingStore, nil - -} - -func (s *KvScrtStore) WithSvc(svc kvApi) { - s.svc = svc -} - -// setToken already happens in AzureKVClient in the constructor -func (implmt *KvScrtStore) SetToken(token *config.ParsedTokenConfig) {} - -func (imp *KvScrtStore) Value() (string, error) { - imp.logger.Info("Concrete implementation AzKeyVault Secret") - imp.logger.Info("AzKeyVault Token: %s", imp.token.String()) - - ctx, cancel := context.WithCancel(imp.ctx) - defer cancel() - - // secretVersion as "" => latest - // imp.config.Version will default `""` if not specified - s, err := imp.svc.GetSecret(ctx, imp.strippedToken, imp.config.Version, nil) - if err != nil { - imp.logger.Error(implementationNetworkErr, imp.token.Prefix(), err, imp.token.String()) - return "", err - } - if s.Value != nil { - return *s.Value, nil - } - imp.logger.Error("value retrieved but empty for token: %v", imp.token) - return "", nil -} diff --git a/internal/store/azkeyvault_test.go b/internal/store/azkeyvault_test.go deleted file mode 100644 index 35b5c7d..0000000 --- a/internal/store/azkeyvault_test.go +++ /dev/null @@ -1,218 +0,0 @@ -package store_test - -import ( - "context" - "fmt" - "io" - "strings" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" - "github.com/DevLabFoundry/configmanager/v3/internal/testutils" -) - -func Test_azSplitToken(t *testing.T) { - tests := []struct { - name string - token string - expect store.AzServiceHelper - }{ - { - name: "simple_with_preceding_slash", - token: "/test-vault/somejsontest", - expect: store.AzServiceHelper{ - ServiceUri: "https://test-vault.vault.azure.net", - Token: "somejsontest", - }, - }, - { - name: "missing_initial_slash", - token: "test-vault/somejsontest", - expect: store.AzServiceHelper{ - ServiceUri: "https://test-vault.vault.azure.net", - Token: "somejsontest", - }, - }, - { - name: "missing_initial_slash_multislash_secretname", - token: "test-vault/some/json/test", - expect: store.AzServiceHelper{ - ServiceUri: "https://test-vault.vault.azure.net", - Token: "some/json/test", - }, - }, - { - name: "with_initial_slash_multislash_secretname", - token: "test-vault//some/json/test", - expect: store.AzServiceHelper{ - ServiceUri: "https://test-vault.vault.azure.net", - Token: "/some/json/test", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := store.AzServiceFromToken(tt.token, "https://%s.vault.azure.net", 1) - if got.Token != tt.expect.Token { - t.Errorf(testutils.TestPhrase, tt.expect.Token, got.Token) - } - if got.ServiceUri != tt.expect.ServiceUri { - t.Errorf(testutils.TestPhrase, tt.expect.ServiceUri, got.ServiceUri) - } - }) - } -} - -func azKvCommonGetSecretChecker(t *testing.T, name, version, expectedName string) { - if name == "" { - t.Errorf("expect name to not be nil") - } - if name != expectedName { - t.Errorf(testutils.TestPhrase, name, expectedName) - } - - if strings.Contains(name, "#") { - t.Errorf("incorrectly stripped token separator") - } - - if strings.Contains(name, string(config.AzKeyVaultSecretsPrefix)) { - t.Errorf("incorrectly stripped prefix") - } - - if version != "" { - t.Fatal("expect version to be \"\" an empty string ") - } -} - -type mockAzKvSecretApi func(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) - -func (m mockAzKvSecretApi) GetSecret(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) { - return m(ctx, name, version, options) -} - -func TestAzKeyVault(t *testing.T) { - tsuccessParam := "dssdfdweiuyh" - tests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - mockClient func(t *testing.T) mockAzKvSecretApi - }{ - "successVal": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-vault//token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - tsuccessParam, func(t *testing.T) mockAzKvSecretApi { - return mockAzKvSecretApi(func(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) { - t.Helper() - azKvCommonGetSecretChecker(t, name, "", "/token/1") - resp := azsecrets.GetSecretResponse{} - resp.Value = &tsuccessParam - return resp, nil - }) - }, - }, - "successVal with version": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-vault//token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("version:123") - return tkn - }, tsuccessParam, func(t *testing.T) mockAzKvSecretApi { - return mockAzKvSecretApi(func(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) { - t.Helper() - azKvCommonGetSecretChecker(t, name, "", "/token/1") - resp := azsecrets.GetSecretResponse{} - resp.Value = &tsuccessParam - return resp, nil - }) - }, - }, - "successVal with keyseparator": { - func() *config.ParsedTokenConfig { - // "AZKVSECRET#/test-vault/token/1|somekey" - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-vault/token/1") - tkn.WithKeyPath("somekey") - tkn.WithMetadata("") - return tkn - }, tsuccessParam, func(t *testing.T) mockAzKvSecretApi { - return mockAzKvSecretApi(func(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) { - t.Helper() - azKvCommonGetSecretChecker(t, name, "", "token/1") - - resp := azsecrets.GetSecretResponse{} - resp.Value = &tsuccessParam - return resp, nil - }) - }, - }, - "errored": { - func() *config.ParsedTokenConfig { - // "AZKVSECRET#/test-vault/token/1|somekey" - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-vault/token/1") - tkn.WithKeyPath("somekey") - tkn.WithMetadata("") - return tkn - }, - "unable to retrieve secret", - func(t *testing.T) mockAzKvSecretApi { - return mockAzKvSecretApi(func(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) { - t.Helper() - azKvCommonGetSecretChecker(t, name, "", "token/1") - - resp := azsecrets.GetSecretResponse{} - return resp, fmt.Errorf("unable to retrieve secret") - }) - }, - }, - "empty": { - func() *config.ParsedTokenConfig { - // "AZKVSECRET#/test-vault/token/1|somekey" - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-vault/token/1") - tkn.WithKeyPath("somekey") - tkn.WithMetadata("") - return tkn - }, "", func(t *testing.T) mockAzKvSecretApi { - return mockAzKvSecretApi(func(ctx context.Context, name string, version string, options *azsecrets.GetSecretOptions) (azsecrets.GetSecretResponse, error) { - t.Helper() - azKvCommonGetSecretChecker(t, name, "", "token/1") - - resp := azsecrets.GetSecretResponse{} - return resp, nil - }) - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - impl, err := store.NewKvScrtStore(context.TODO(), tt.token(), log.New(io.Discard)) - if err != nil { - t.Errorf("failed to init azkvstore") - } - - impl.WithSvc(tt.mockClient(t)) - got, err := impl.Value() - if err != nil { - if err.Error() != tt.expect { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) - } - return - } - - if got != tt.expect { - t.Errorf(testutils.TestPhrase, got, tt.expect) - } - }) - } -} diff --git a/internal/store/aztablestorage.go b/internal/store/aztablestorage.go deleted file mode 100644 index eedef16..0000000 --- a/internal/store/aztablestorage.go +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Azure TableStore implementation -**/ -package store - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "strings" - - "github.com/Azure/azure-sdk-for-go/sdk/azidentity" - "github.com/Azure/azure-sdk-for-go/sdk/data/aztables" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" -) - -var ErrIncorrectlyStructuredToken = errors.New("incorrectly structured token") - -// tableStoreApi -// uses this package https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/data/aztables -type tableStoreApi interface { - GetEntity(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) -} - -type AzTableStore struct { - svc tableStoreApi - ctx context.Context - logger log.ILogger - config *AzTableStrgConfig - token *config.ParsedTokenConfig - // token only without table indicators - // key only - strippedToken string -} - -type AzTableStrgConfig struct { - Format string `json:"format"` -} - -// NewAzTableStore -func NewAzTableStore(ctx context.Context, token *config.ParsedTokenConfig, logger log.ILogger) (*AzTableStore, error) { - - storeConf := &AzTableStrgConfig{} - _ = token.ParseMetadata(storeConf) - // initialToken := config.ParseMetadata(token, storeConf) - backingStore := &AzTableStore{ - ctx: ctx, - logger: logger, - config: storeConf, - token: token, - } - - srvInit := AzServiceFromToken(token.StoreToken(), "https://%s.table.core.windows.net/%s", 2) - backingStore.strippedToken = srvInit.Token - - cred, err := azidentity.NewDefaultAzureCredential(nil) - if err != nil { - logger.Error("failed to get credentials: %v", err) - return nil, err - } - - c, err := aztables.NewClient(srvInit.ServiceUri, cred, nil) - if err != nil { - logger.Error("failed to init the client: %v", err) - return nil, fmt.Errorf("%v\n%w", err, ErrClientInitialization) - } - - backingStore.svc = c - return backingStore, nil -} - -func (s *AzTableStore) WithSvc(svc tableStoreApi) { - s.svc = svc -} - -// setToken already happens in the constructor -func (implmt *AzTableStore) SetToken(token *config.ParsedTokenConfig) {} - -// tokenVal in AZ table storage if an Entity contains the `value` property -// we attempt to extract it and return. -// -// From this point then normal rules of configmanager apply, -// including keySeperator and lookup. -func (imp *AzTableStore) Value() (string, error) { - imp.logger.Info("AzTableSTore Token: %s", imp.token.String()) - imp.logger.Info("Concrete implementation AzTableSTore") - - ctx, cancel := context.WithCancel(imp.ctx) - defer cancel() - - // split the token for partition and rowKey - pKey, rKey, err := azTableStoreTokenSplitter(imp.strippedToken) - if err != nil { - return "", err - } - - s, err := imp.svc.GetEntity(ctx, pKey, rKey, &aztables.GetEntityOptions{}) - if err != nil { - imp.logger.Error(implementationNetworkErr, config.AzTableStorePrefix, err, imp.strippedToken) - return "", fmt.Errorf(implementationNetworkErr+" %w", config.AzTableStorePrefix, err, imp.token.StoreToken(), ErrRetrieveFailed) - } - if len(s.Value) > 0 { - // check for `value` property in entity - checkVal := make(map[string]interface{}) - _ = json.Unmarshal(s.Value, &checkVal) - if checkVal["value"] != nil { - return fmt.Sprintf("%v", checkVal["value"]), nil - } - return string(s.Value), nil - } - imp.logger.Error("value retrieved but empty for token: %v", imp.token) - return "", nil -} - -func azTableStoreTokenSplitter(token string) (partitionKey, rowKey string, err error) { - splitToken := strings.Split(strings.TrimPrefix(token, "/"), "/") - if len(splitToken) < 2 { - return "", "", fmt.Errorf("token: %s - could not be correctly destructured to pluck the partition and row keys\n%w", token, ErrIncorrectlyStructuredToken) - } - partitionKey = splitToken[0] - rowKey = splitToken[1] - // naked return to save having to define another struct - return -} diff --git a/internal/store/aztablestorage_test.go b/internal/store/aztablestorage_test.go deleted file mode 100644 index 892ee9e..0000000 --- a/internal/store/aztablestorage_test.go +++ /dev/null @@ -1,354 +0,0 @@ -package store_test - -import ( - "context" - "errors" - "fmt" - "io" - "strings" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/data/aztables" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" - "github.com/DevLabFoundry/configmanager/v3/internal/testutils" -) - -func azTableStoreCommonChecker(t *testing.T, partitionKey, rowKey, expectedPartitionKey, expectedRowKey string) { - t.Helper() - if partitionKey == "" { - t.Errorf("expect name to not be nil") - } - if partitionKey != expectedPartitionKey { - t.Errorf(testutils.TestPhrase, partitionKey, expectedPartitionKey) - } - - if strings.Contains(partitionKey, string(config.AzTableStorePrefix)) { - t.Errorf("incorrectly stripped prefix") - } - - if rowKey != expectedRowKey { - t.Errorf(testutils.TestPhrase, rowKey, expectedPartitionKey) - } -} - -type mockAzTableStoreApi func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) - -func (m mockAzTableStoreApi) GetEntity(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - return m(ctx, partitionKey, rowKey, options) -} - -func Test_AzTableStore_Success(t *testing.T) { - - tests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - mockClient func(t *testing.T) mockAzTableStoreApi - }{ - "successVal": { - func() *config.ParsedTokenConfig { - // "AZTABLESTORE#/test-account/table//token/1" - tkn, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-account/table//token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, "tsuccessParam", func(t *testing.T) mockAzTableStoreApi { - return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - t.Helper() - azTableStoreCommonChecker(t, partitionKey, rowKey, "token", "1") - resp := aztables.GetEntityResponse{} - resp.Value = []byte("tsuccessParam") - return resp, nil - }) - }, - }, - // "successVal with :// token Separator": {"AZTABLESTORE:///test-account/table//token/1", "tsuccessParam", func(t *testing.T) tableStoreApi { - // return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - // t.Helper() - // azTableStoreCommonChecker(t, partitionKey, rowKey, "token", "1") - // resp := aztables.GetEntityResponse{} - // resp.Value = []byte("tsuccessParam") - // return resp, nil - // }) - // }, config.NewConfig().WithKeySeparator("|").WithTokenSeparator("://"), - // }, - // "successVal with keyseparator but no val returned": {"AZTABLESTORE#/test-account/table/token/1|somekey", "", func(t *testing.T) tableStoreApi { - // return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - // t.Helper() - // azTableStoreCommonChecker(t, partitionKey, rowKey, "token", "1") - - // resp := aztables.GetEntityResponse{} - // resp.Value = nil - // return resp, nil - // }) - // }, - // config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#"), - // }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - impl, err := store.NewAzTableStore(context.TODO(), tt.token(), log.New(io.Discard)) - if err != nil { - t.Errorf("failed to init aztablestore") - } - - impl.WithSvc(tt.mockClient(t)) - - got, err := impl.Value() - if err != nil { - if err.Error() != tt.expect { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) - } - return - } - - if got != tt.expect { - t.Errorf(testutils.TestPhrase, got, tt.expect) - } - }) - } -} - -func Test_azstorage_with_value_property(t *testing.T) { - - conf := config.NewConfig().WithKeySeparator("|").WithTokenSeparator("://") - ttests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - mockClient func(t *testing.T) mockAzTableStoreApi - }{ - "return value property with json like object": { - func() *config.ParsedTokenConfig { - // "AZTABLESTORE:///test-account/table/partitionkey/rowKey|host", - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *conf) - tkn.WithSanitizedToken("/test-account/table/partitionkey/rowKey") - tkn.WithKeyPath("host") - return tkn - }, - "map[bool:true host:foo port:1234]", - func(t *testing.T) mockAzTableStoreApi { - return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - t.Helper() - resp := aztables.GetEntityResponse{Value: []byte(`{"value":{"host":"foo","port":1234,"bool":true}}`)} - return resp, nil - }) - }, - }, - "return value property with string only": { - func() *config.ParsedTokenConfig { - // "AZTABLESTORE:///test-account/table/partitionkey/rowKey", - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *conf) - tkn.WithSanitizedToken("/test-account/table/partitionkey/rowKey") - // tkn.WithKeyPath("host") - // tkn.WithMetadata("version:123]") - return tkn - }, - "foo.bar.com", - func(t *testing.T) mockAzTableStoreApi { - return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - t.Helper() - resp := aztables.GetEntityResponse{Value: []byte(`{"value":"foo.bar.com"}`)} - return resp, nil - }) - }, - }, - "return value property with numeric only": { - func() *config.ParsedTokenConfig { - // "AZTABLESTORE:///test-account/table/partitionkey/rowKey", - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *conf) - tkn.WithSanitizedToken("/test-account/table/partitionkey/rowKey") - // tkn.WithKeyPath("host") - // tkn.WithMetadata("version:123]") - return tkn - }, - "1234", - func(t *testing.T) mockAzTableStoreApi { - return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - t.Helper() - resp := aztables.GetEntityResponse{Value: []byte(`{"value":1234}`)} - return resp, nil - }) - }, - }, - "return value property with boolean only": { - func() *config.ParsedTokenConfig { - // "AZTABLESTORE:///test-account/table/partitionkey/rowKey", - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *conf) - tkn.WithSanitizedToken("/test-account/table/partitionkey/rowKey") - return tkn - }, - "false", - func(t *testing.T) mockAzTableStoreApi { - return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - t.Helper() - resp := aztables.GetEntityResponse{Value: []byte(`{"value":false}`)} - return resp, nil - }) - }, - }, - } - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - // token, _ := config.NewToken(tt.token(), *tt.config) - - impl, err := store.NewAzTableStore(context.TODO(), tt.token(), log.New(io.Discard)) - if err != nil { - t.Fatal("failed to init aztablestore") - } - - impl.WithSvc(tt.mockClient(t)) - - got, err := impl.Value() - if err != nil { - t.Fatalf(testutils.TestPhrase, err.Error(), nil) - } - - if got != tt.expect { - t.Errorf(testutils.TestPhraseWithContext, "AZ Table storage with value property inside entity", fmt.Sprintf("%q", got), fmt.Sprintf("%q", tt.expect)) - } - }) - } -} - -func Test_AzTableStore_Error(t *testing.T) { - - tests := map[string]struct { - token func() *config.ParsedTokenConfig - expect error - mockClient func(t *testing.T) mockAzTableStoreApi - }{ - "errored on token parsing to partiationKey": { - func() *config.ParsedTokenConfig { - // "AZTABLESTORE#/test-vault/token/1|somekey" - tkn, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-vault/token/1") - tkn.WithKeyPath("somekey") - tkn.WithMetadata("") - return tkn - }, store.ErrIncorrectlyStructuredToken, func(t *testing.T) mockAzTableStoreApi { - return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - t.Helper() - resp := aztables.GetEntityResponse{} - return resp, nil - }) - }, - }, - "errored on service method call": { - func() *config.ParsedTokenConfig { - // "AZTABLESTORE#/test-account/table/token/ok", - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-account/table/token/ok") - return tkn - }, - store.ErrRetrieveFailed, - func(t *testing.T) mockAzTableStoreApi { - return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - t.Helper() - resp := aztables.GetEntityResponse{} - return resp, fmt.Errorf("network error") - }) - }, - }, - - "empty": { - func() *config.ParsedTokenConfig { - // "AZTABLESTORE#/test-vault/token/1|somekey", - tkn, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/test-vault/token/1|somekey") - return tkn - }, - store.ErrIncorrectlyStructuredToken, func(t *testing.T) mockAzTableStoreApi { - return mockAzTableStoreApi(func(ctx context.Context, partitionKey string, rowKey string, options *aztables.GetEntityOptions) (aztables.GetEntityResponse, error) { - t.Helper() - resp := aztables.GetEntityResponse{} - return resp, nil - }) - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - impl, err := store.NewAzTableStore(context.TODO(), tt.token(), log.New(io.Discard)) - if err != nil { - t.Fatal("failed to init aztablestore") - } - - impl.WithSvc(tt.mockClient(t)) - if _, err := impl.Value(); !errors.Is(err, tt.expect) { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) - } - }) - } -} - -func Test_fail_AzTable_Client_init(t *testing.T) { - // this is basically a wrap around test for the url.Parse method in the stdlib - // as that is what the client uses under the hood - token, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig()) - // "AZTABLESTORE:///%25%65%6e%301-._~/") - } - if !errors.Is(err, store.ErrClientInitialization) { - t.Fatalf(testutils.TestPhraseWithContext, "aztables client init", err.Error(), store.ErrClientInitialization.Error()) - } -} - -func Test_azSplitTokenTableStore(t *testing.T) { - - tests := []struct { - name string - token string - expect store.AzServiceHelper - }{ - { - name: "simple_with_preceding_slash", - token: "/test-account/tablename/somejsontest", - expect: store.AzServiceHelper{ - ServiceUri: "https://test-account.table.core.windows.net/tablename", - Token: "somejsontest", - }, - }, - { - name: "missing_initial_slash", - token: "test-account/tablename/somejsontest", - expect: store.AzServiceHelper{ - ServiceUri: "https://test-account.table.core.windows.net/tablename", - Token: "somejsontest", - }, - }, - { - name: "missing_initial_slash_multislash_secretname", - token: "test-account/tablename/some/json/test", - expect: store.AzServiceHelper{ - ServiceUri: "https://test-account.table.core.windows.net/tablename", - Token: "some/json/test", - }, - }, - { - name: "with_initial_slash_multislash_secretname", - token: "test-account/tablename//some/json/test", - expect: store.AzServiceHelper{ - ServiceUri: "https://test-account.table.core.windows.net/tablename", - Token: "/some/json/test", - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := store.AzServiceFromToken(tt.token, "https://%s.table.core.windows.net/%s", 2) - if got.Token != tt.expect.Token { - t.Errorf(testutils.TestPhrase, tt.expect.Token, got.Token) - } - if got.ServiceUri != tt.expect.ServiceUri { - t.Errorf(testutils.TestPhrase, tt.expect.ServiceUri, got.ServiceUri) - } - }) - } -} diff --git a/internal/store/gcpsecrets.go b/internal/store/gcpsecrets.go deleted file mode 100644 index 07c43e2..0000000 --- a/internal/store/gcpsecrets.go +++ /dev/null @@ -1,91 +0,0 @@ -package store - -import ( - "context" - "fmt" - - gcpsecrets "cloud.google.com/go/secretmanager/apiv1" - gcpsecretspb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/googleapis/gax-go/v2" -) - -type gcpSecretsApi interface { - AccessSecretVersion(ctx context.Context, req *gcpsecretspb.AccessSecretVersionRequest, opts ...gax.CallOption) (*gcpsecretspb.AccessSecretVersionResponse, error) -} - -type GcpSecrets struct { - svc gcpSecretsApi - logger log.ILogger - ctx context.Context - config *GcpSecretsConfig - close func() error - token *config.ParsedTokenConfig -} - -type GcpSecretsConfig struct { - Version string `json:"version"` -} - -func NewGcpSecrets(ctx context.Context, logger log.ILogger) (*GcpSecrets, error) { - - c, err := gcpsecrets.NewClient(ctx) - if err != nil { - return nil, err - } - return &GcpSecrets{ - svc: c, - logger: logger, - ctx: ctx, - close: c.Close, - }, nil -} - -func (s *GcpSecrets) WithSvc(svc gcpSecretsApi) { - s.svc = svc -} - -func (imp *GcpSecrets) SetToken(token *config.ParsedTokenConfig) { - storeConf := &GcpSecretsConfig{} - _ = token.ParseMetadata(storeConf) - imp.token = token - imp.config = storeConf -} - -func (imp *GcpSecrets) Value() (string, error) { - // Close client currently as new one would be created per iteration - defer func() { - _ = imp.close() - }() - - imp.logger.Info("Concrete implementation GcpSecrets") - imp.logger.Info("GcpSecrets Token: %s", imp.token.String()) - - version := "latest" - if imp.config.Version != "" { - version = imp.config.Version - } - - imp.logger.Info("Getting Secret: %s @version: %s", imp.token, version) - - input := &gcpsecretspb.AccessSecretVersionRequest{ - Name: fmt.Sprintf("%s/versions/%s", imp.token.StoreToken(), version), - } - - ctx, cancel := context.WithCancel(imp.ctx) - defer cancel() - - result, err := imp.svc.AccessSecretVersion(ctx, input) - - if err != nil { - imp.logger.Error(implementationNetworkErr, imp.token.Prefix(), err, imp.token.String()) - return "", err - } - if result.Payload != nil { - return string(result.Payload.Data), nil - } - - imp.logger.Error("value retrieved but empty for token: %v", imp.token) - return "", nil -} diff --git a/internal/store/gcpsecrets_test.go b/internal/store/gcpsecrets_test.go deleted file mode 100644 index 5f859ba..0000000 --- a/internal/store/gcpsecrets_test.go +++ /dev/null @@ -1,184 +0,0 @@ -package store_test - -import ( - "context" - "fmt" - "io" - "os" - "strings" - "testing" - - gcpsecretspb "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" - "github.com/DevLabFoundry/configmanager/v3/internal/testutils" - "github.com/googleapis/gax-go/v2" -) - -type mockGcpSecretsApi func(ctx context.Context, req *gcpsecretspb.AccessSecretVersionRequest, opts ...gax.CallOption) (*gcpsecretspb.AccessSecretVersionResponse, error) - -func (m mockGcpSecretsApi) AccessSecretVersion(ctx context.Context, req *gcpsecretspb.AccessSecretVersionRequest, opts ...gax.CallOption) (*gcpsecretspb.AccessSecretVersionResponse, error) { - return m(ctx, req, opts...) -} - -func (m mockGcpSecretsApi) Close() error { - return nil -} - -var TEST_GCP_CREDS = []byte(`{ - "type": "service_account", - "project_id": "xxxxx", - "private_key_id": "yyyyyyyyyyyy", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDf842hcn5Nvp6e\n7yKARaCVIDfLXpKDhRwUOvHMzJ1ioRgQo/kbv1n4yHGCSUFyY6hKGj0HBjaGj5kE\n79H/6Y3dJNGhnsMnxBhHdo+3FI8QF0CHZh460NMZSAJ41UMQSBGssGVsNfyUzXGH\nLc45sIx/Twx3yr1k2GD3E8FlDcKlZqa3xGHf+aipg2X3NxbYi+Sz7Yed+SOMhNHl\ncX6E/TqG9n1aTyIwjMIHscCYarJqURkJxr24ukDroCeMxAfxYTdMvRU2e8pFEdoY\nrgUC88fYfaVI5txJ6j/ZKauKQX9Pa8tSyXJeGva3JYp4VC7V4IyoVviCUgEGWZDN\n6/i3zoF/AgMBAAECggEAcVBCcVYFIkE48SH+Svjv74SFtpj7eSB4vKO2hPFjEOyB\nyKmu+aMwWvjQtiNqwf46wIPWLR+vpxYxTpYpo1sBNMvUZfp2tEA8KKyMuw3j9ThO\npjO9R/UxWrFcztbZP/u3NbFrH/2Q95mbv9IlbnsuG5xbqqEig0wYg+uzBvaXbig3\n/Jr0vLT2BkRCBKQkYGjVZcHlHVLoF7/J8cghFgkV1PGvknOv6/q7qzn9L4TjQIet\nfhrhN8Z1vgFiSYtpjP6YQEUEPSHmCQeD3WzJcnASPpU2uCUwd/z65ltKPnn+rqMt\n6jt9R1S1Ju2ZSjv+kR5fIXzihdOzncyzDDm33c/QwQKBgQD2QDZuzLjTxnhsfGii\nKJDAts+Jqfs/6SeEJcJKtEngj4m7rgzyEjbKVp8qtRHIzglKRWAe62/qzzy2BkKi\nvAd4+ZzmG2SkgypGsKVfjGXVFixz2gtUdmBOmK/TnYsxNT9yTt+rX9IGqKK60q73\nOWl8VsliLIsfvSH7+bqi7sRcXQKBgQDo0VUebyQHoTAXPdzGy2ysrVPDiHcldH0Y\n/hvhQTZwxYaJr3HpOCGol2Xl6zyawuudEQsoQwJ3Li6yeb0YMGiWX77/t+qX3pSn\nkGuoftGaNDV7sLn9UV2y+InF8EL1CasrhG1k5RIuxyfV0w+QUo+E7LpVR5XkbJqT\n9QNKnDQXiwKBgQDvvEYCCqbp7e/xVhEbxbhfFdro4Cat6tRAz+3egrTlvXhO0jzi\nMp9Kz5f3oP5ma0gaGX5hu75icE1fvKqE+d+ghAqe7w5FJzkyRulJI0tEb2jphN7A\n5NoPypBqyZboWjmhlG4mzouPVf/POCuEnk028truDAWJ6by7Lj3oP+HFNQKBgQCc\n5BQ8QiFBkvnZb7LLtGIzq0n7RockEnAK25LmJRAOxs13E2fsBguIlR3x5qgckqY8\nXjPqmd2bet+1HhyzpEuWqkcIBGRum2wJz2T9UxjklbJE/D8Z2i8OYDZX0SUOA8n5\ntXASwduS8lqB2Y1vcHOO3AhlV6xHFnjEpCPnr4PbKQKBgAhQ9D9MPeuz+5yw3yHg\nkvULZRtud+uuaKrOayprN25RTxr9c0erxqnvM7KHeo6/urOXeEa7x2n21kAT0Nch\nkF2RtWBLZKXGZEVBtw1Fw0UKNh4IDgM26dwlzRfTVHCiw6M6dCiTNk9KkP2vlkim\n3QFDSSUp+eBTXA17WkDAQf7w\n-----END PRIVATE KEY-----\n", - "client_email": "foo@project.iam.gserviceaccount.com", - "client_id": "99999911111111", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/bla" - }`) - -func fixtureInitMockClient(t *testing.T) struct { - name string - close func() error - delete func(name string) error -} { - - cf, err := os.CreateTemp("", "gcp-creds*") - if err != nil { - t.Fatalf(testutils.TestPhraseWithContext, "unable to set up creds file", err.Error(), nil) - } - if _, err := cf.Write(TEST_GCP_CREDS); err != nil { - t.Fatalf(testutils.TestPhraseWithContext, "unable to write mock creds into file", err.Error(), nil) - } - - resp := struct { - name string - close func() error - delete func(name string) error - }{ - name: cf.Name(), - close: cf.Close, - delete: os.Remove, - } - return resp -} - -func gcpSecretsGetChecker(t *testing.T, req *gcpsecretspb.AccessSecretVersionRequest) { - t.Helper() - if req.Name == "" { - t.Fatal("expect name to not be nil") - } - if strings.Contains(req.Name, "#") { - t.Errorf("incorrectly stripped token separator") - } - if strings.Contains(req.Name, string(config.GcpSecretsPrefix)) { - t.Errorf("incorrectly stripped prefix") - } -} - -func Test_GetGcpSecretVarHappy(t *testing.T) { - - tests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - mockClient func(t *testing.T) mockGcpSecretsApi - }{ - "success": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.GcpSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - "someValue", func(t *testing.T) mockGcpSecretsApi { - return mockGcpSecretsApi(func(ctx context.Context, req *gcpsecretspb.AccessSecretVersionRequest, opts ...gax.CallOption) (*gcpsecretspb.AccessSecretVersionResponse, error) { - gcpSecretsGetChecker(t, req) - return &gcpsecretspb.AccessSecretVersionResponse{ - Payload: &gcpsecretspb.SecretPayload{Data: []byte("someValue")}, - }, nil - }) - }, - }, - "success with version": { - func() *config.ParsedTokenConfig { - // "GCPSECRETS#/token/1[version=123]" - tkn, _ := config.NewToken(config.GcpSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("version=123") - return tkn - }, "someValue", func(t *testing.T) mockGcpSecretsApi { - return mockGcpSecretsApi(func(ctx context.Context, req *gcpsecretspb.AccessSecretVersionRequest, opts ...gax.CallOption) (*gcpsecretspb.AccessSecretVersionResponse, error) { - gcpSecretsGetChecker(t, req) - return &gcpsecretspb.AccessSecretVersionResponse{ - Payload: &gcpsecretspb.SecretPayload{Data: []byte("someValue")}, - }, nil - }) - }, - }, - "error": { - func() *config.ParsedTokenConfig { - // "GCPSECRETS#/token/1" - tkn, _ := config.NewToken(config.GcpSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, "unable to retrieve secret", func(t *testing.T) mockGcpSecretsApi { - return mockGcpSecretsApi(func(ctx context.Context, req *gcpsecretspb.AccessSecretVersionRequest, opts ...gax.CallOption) (*gcpsecretspb.AccessSecretVersionResponse, error) { - gcpSecretsGetChecker(t, req) - return nil, fmt.Errorf("unable to retrieve secret") - }) - }, - }, - "found but empty": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.GcpSecretsPrefix, *config.NewConfig().WithKeySeparator("|").WithTokenSeparator("#")) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - "", - func(t *testing.T) mockGcpSecretsApi { - return mockGcpSecretsApi(func(ctx context.Context, req *gcpsecretspb.AccessSecretVersionRequest, opts ...gax.CallOption) (*gcpsecretspb.AccessSecretVersionResponse, error) { - gcpSecretsGetChecker(t, req) - return &gcpsecretspb.AccessSecretVersionResponse{}, nil - }) - }, - }, - } - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - fixture := fixtureInitMockClient(t) - defer fixture.close() - defer fixture.delete(fixture.name) - - os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", fixture.name) - - impl, err := store.NewGcpSecrets(context.TODO(), log.New(io.Discard)) - - if err != nil { - t.Errorf(testutils.TestPhrase, err.Error(), nil) - } - - impl.WithSvc(tt.mockClient(t)) - - impl.SetToken(tt.token()) - got, err := impl.Value() - - if err != nil { - if err.Error() != tt.expect { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) - } - return - } - if got != tt.expect { - t.Errorf(testutils.TestPhrase, got, tt.expect) - } - }) - } -} diff --git a/internal/store/plugin.go b/internal/store/plugin.go new file mode 100644 index 0000000..4c75d1b --- /dev/null +++ b/internal/store/plugin.go @@ -0,0 +1,96 @@ +package store + +import ( + "context" + "errors" + "fmt" + "os/exec" + + "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" +) + +var ErrTokenRetrieval = errors.New("failed to exchange token for value") + +// Plugin is responsible for managing the plugin lifecycle +// within the configmanager flow. Each Implementation will initialise exactly one instance of the plugin +type Plugin struct { + Implementations config.ImplementationPrefix + SourcePath string + Version string + ClientCleanUp func() + tokenStore tokenstore.TokenStore +} + +// NewPlugin Plugin gets called once per implementation +func NewPlugin(ctx context.Context, path string) (*Plugin, error) { + // We're a host. Start by launching the plugin process. + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: tokenstore.Handshake, + Plugins: plugin.PluginSet{"configmanager_token_store": &tokenstore.GRPCPlugin{}}, + Cmd: exec.Command(path), + AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + Logger: hclog.NewNullLogger(), + }) + // Connect via RPC + rpcClient, err := client.Client() + if err != nil { + client.Kill() + return nil, err + } + + // ensure the loaded plugin can dispense the required prefix implementation + raw, err := rpcClient.Dispense("configmanager_token_store") + if err != nil { + client.Kill() + return nil, err + } + + ts := raw.(tokenstore.TokenStore) + + p := &Plugin{ + ClientCleanUp: client.Kill, + tokenStore: ts, + } + return p, nil +} + +func (p *Plugin) WithTokenStore(ts tokenstore.TokenStore) { + p.tokenStore = ts +} + +func (p *Plugin) GetValue(token *config.ParsedTokenConfig) (string, error) { + result, err := p.tokenStore.Value(token.StoreToken(), []byte(token.Metadata())) + if err != nil { + return "", fmt.Errorf("%w - (%s), %v", ErrRetrieveFailed, token.String(), err) + } + return result, nil +} + +type PluginDownloadInfo struct { + BaseUrl string + Name string +} + +const corePluginBaseUrl = "https://github.com/DevLabFoundry/configmanager/releases" + +type PluginDownloadInfoMap map[string]*PluginDownloadInfo + +// corePluginMap are the configmanager maintained plugins +var corePluginMap PluginDownloadInfoMap = map[string]*PluginDownloadInfo{ + "empty": { + BaseUrl: corePluginBaseUrl, + Name: "", + }, + "awsparamstr": { + BaseUrl: corePluginBaseUrl, + Name: "", + }, + "awssecrets": { + BaseUrl: corePluginBaseUrl, + Name: "", + }, + // ... +} diff --git a/internal/store/secretsmanager.go b/internal/store/secretsmanager.go deleted file mode 100644 index 6744d8a..0000000 --- a/internal/store/secretsmanager.go +++ /dev/null @@ -1,93 +0,0 @@ -package store - -import ( - "context" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/aws/aws-sdk-go-v2/aws" - awsconf "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/secretsmanager" -) - -type secretsMgrApi interface { - GetSecretValue(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) -} - -type SecretsMgr struct { - svc secretsMgrApi - ctx context.Context - logger log.ILogger - config *SecretsMgrConfig - token *config.ParsedTokenConfig -} - -type SecretsMgrConfig struct { - Version string `json:"version"` -} - -func NewSecretsMgr(ctx context.Context, logger log.ILogger) (*SecretsMgr, error) { - cfg, err := awsconf.LoadDefaultConfig(ctx) - if err != nil { - logger.Error("unable to load SDK config, %v\n%w", err, ErrClientInitialization) - return nil, err - } - c := secretsmanager.NewFromConfig(cfg) - - return &SecretsMgr{ - svc: c, - logger: logger, - ctx: ctx, - }, nil - -} - -func (s *SecretsMgr) WithSvc(svc secretsMgrApi) { - s.svc = svc -} - -func (imp *SecretsMgr) SetToken(token *config.ParsedTokenConfig) { - storeConf := &SecretsMgrConfig{} - if err := token.ParseMetadata(storeConf); err != nil { - imp.logger.Error("parse token error %v", err) - } - imp.token = token - imp.config = storeConf -} - -func (imp *SecretsMgr) Value() (string, error) { - imp.logger.Info("Concrete implementation SecretsManager") - imp.logger.Debug("SecretsManager Token: %s", imp.token.String()) - - version := "AWSCURRENT" - if imp.config.Version != "" { - version = imp.config.Version - } - - imp.logger.Info("Getting Secret: %s @version: %s", imp.token, version) - - input := &secretsmanager.GetSecretValueInput{ - SecretId: aws.String(imp.token.StoreToken()), - VersionStage: aws.String(version), - } - - ctx, cancel := context.WithCancel(imp.ctx) - defer cancel() - - result, err := imp.svc.GetSecretValue(ctx, input) - if err != nil { - imp.logger.Error(implementationNetworkErr, imp.token.Prefix(), err, imp.token.String()) - return "", err - } - - if result.SecretString != nil { - return *result.SecretString, nil - } - - if len(result.SecretBinary) > 0 { - return string(result.SecretBinary), nil - } - - imp.logger.Error("value retrieved but empty for token: %v", imp.token) - return "", nil -} diff --git a/internal/store/secretsmanager_test.go b/internal/store/secretsmanager_test.go deleted file mode 100644 index 870bb75..0000000 --- a/internal/store/secretsmanager_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package store_test - -import ( - "context" - "fmt" - "io" - "strings" - "testing" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" - "github.com/DevLabFoundry/configmanager/v3/internal/testutils" - "github.com/aws/aws-sdk-go-v2/service/secretsmanager" -) - -type mockSecretsApi func(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) - -func (m mockSecretsApi) GetSecretValue(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) { - return m(ctx, params, optFns...) -} - -func awsSecretsMgrGetChecker(t *testing.T, params *secretsmanager.GetSecretValueInput) { - if params.VersionStage == nil { - t.Fatal("expect name to not be nil") - } - - if strings.Contains(*params.SecretId, "#") { - t.Errorf("incorrectly stripped token separator") - } - - if strings.Contains(*params.SecretId, string(config.SecretMgrPrefix)) { - t.Errorf("incorrectly stripped prefix") - } -} - -func Test_GetSecretMgr(t *testing.T) { - - tsuccessSecret := "dsgkbdsf" - - tests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - mockClient func(t *testing.T) mockSecretsApi - }{ - "success": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.SecretMgrPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, tsuccessSecret, func(t *testing.T) mockSecretsApi { - return mockSecretsApi(func(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) { - t.Helper() - awsSecretsMgrGetChecker(t, params) - return &secretsmanager.GetSecretValueOutput{ - SecretString: &tsuccessSecret, - }, nil - }) - }, - }, - "success with version": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.SecretMgrPrefix, *config.NewConfig().WithTokenSeparator("#")) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("version=123") - return tkn - }, - tsuccessSecret, func(t *testing.T) mockSecretsApi { - return mockSecretsApi(func(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) { - t.Helper() - awsSecretsMgrGetChecker(t, params) - return &secretsmanager.GetSecretValueOutput{ - SecretString: &tsuccessSecret, - }, nil - }) - }, - }, - "success with binary": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.SecretMgrPrefix, *config.NewConfig().WithTokenSeparator("#")) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - tsuccessSecret, func(t *testing.T) mockSecretsApi { - return mockSecretsApi(func(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) { - t.Helper() - awsSecretsMgrGetChecker(t, params) - return &secretsmanager.GetSecretValueOutput{ - SecretBinary: []byte(tsuccessSecret), - }, nil - }) - }, - }, - "errored": { - func() *config.ParsedTokenConfig { - // "AWSSECRETS#/token/1", "|", "#", - tkn, _ := config.NewToken(config.SecretMgrPrefix, *config.NewConfig().WithTokenSeparator("#")) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - "unable to retrieve secret", func(t *testing.T) mockSecretsApi { - return mockSecretsApi(func(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) { - t.Helper() - awsSecretsMgrGetChecker(t, params) - return nil, fmt.Errorf("unable to retrieve secret") - }) - }, - }, - "ok but empty": { - func() *config.ParsedTokenConfig { - // "AWSSECRETS#/token/1", "|", "#", - tkn, _ := config.NewToken(config.SecretMgrPrefix, *config.NewConfig().WithTokenSeparator("#")) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("version=123") - return tkn - }, - "", func(t *testing.T) mockSecretsApi { - return mockSecretsApi(func(ctx context.Context, params *secretsmanager.GetSecretValueInput, optFns ...func(*secretsmanager.Options)) (*secretsmanager.GetSecretValueOutput, error) { - t.Helper() - awsSecretsMgrGetChecker(t, params) - return &secretsmanager.GetSecretValueOutput{ - SecretString: nil, - }, nil - }) - }, - }, - } - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - impl, _ := store.NewSecretsMgr(context.TODO(), log.New(io.Discard)) - impl.WithSvc(tt.mockClient(t)) - - impl.SetToken(tt.token()) - got, err := impl.Value() - if err != nil { - if err.Error() != tt.expect { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) - } - return - } - if got != tt.expect { - t.Errorf(testutils.TestPhrase, got, tt.expect) - } - }) - } -} diff --git a/internal/store/store.go b/internal/store/store.go index eceeb45..4f694ee 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -1,27 +1,261 @@ package store import ( + "context" "errors" + "fmt" + "io" + "maps" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "runtime" + "strings" + "sync" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/schollz/progressbar/v3" ) -const implementationNetworkErr string = "implementation %s error: %v for token: %s" - var ( ErrRetrieveFailed = errors.New("failed to retrieve config item") ErrClientInitialization = errors.New("failed to initialize the client") ErrEmptyResponse = errors.New("value retrieved but empty for token") ErrServiceCallFailed = errors.New("failed to complete the service call") + ErrPluginIssue = errors.New("plugin init failed") + ErrMkdirAllFail = errors.New("unable to create the required directory") +) + +// It includes the following methods +// - fetch plugins from known sources +// - maintains a list of tokens answerable by a specified pluginEngine +type pluginMap struct { + mu *sync.Mutex + // m holds the map of plugins where the key is the lowercased implementation prefix + // e.g. `AWSPARAMSTR://` => `awsparamstr` + m map[string]*Plugin +} + +func (p pluginMap) Add(key string, pl *Plugin) { + p.mu.Lock() + defer p.mu.Unlock() + p.m[key] = pl +} + +const ( + pluginsLocation string = "." + config.SELF_NAME + "/plugins" + namePattern string = "%s-%s-%s" ) -// Strategy iface that all store implementations -// must conform to, in order to be be used by the retrieval implementation +type osOps struct { + UserHomeDir func() (string, error) + Getwd func() (dir string, err error) + MkdirAll func(path string, perm os.FileMode) error + Create func(name string) (io.WriteCloser, error) +} + +type downloadClient interface { + Do(*http.Request) (*http.Response, error) +} +type Store struct { + plugin pluginMap + osOps osOps + downloadInfoMap PluginDownloadInfoMap + downloadClient downloadClient +} + +type StoreOpts func(s *Store) + +func New(ctx context.Context, opts ...StoreOpts) *Store { + pm := pluginMap{ + mu: &sync.Mutex{}, + m: make(map[string]*Plugin), + } + os.Getwd() + s := &Store{ + plugin: pm, + osOps: osOps{ + UserHomeDir: os.UserHomeDir, + Getwd: os.Getwd, + MkdirAll: os.MkdirAll, + Create: func(name string) (io.WriteCloser, error) { + return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0700) + }, + }, + downloadInfoMap: corePluginMap, + downloadClient: &http.Client{}, + } + + for _, o := range opts { + o(s) + } + + return s +} + +func WithOsOps(v osOps) StoreOpts { + return func(s *Store) { + s.osOps = v + } +} + +func WithDownloadClient(v downloadClient) StoreOpts { + return func(s *Store) { + s.downloadClient = v + } +} + +func WithAdditionalPluginInfo(v PluginDownloadInfoMap) StoreOpts { + return func(s *Store) { + maps.Copy(s.downloadInfoMap, v) + } +} + +// Init ensures all the discovered tokens have their implementations initialised +// +// NOTE: it is important to package the providers at a build stage +// if your target deployment environment does not support open network outbound connections +func (s *Store) Init(ctx context.Context, implt []string) error { + dir, err := s.configManagerDir() + if err != nil { + return err + } + + for _, plugin := range implt { + // we first look for existing plugins + plpath, err := s.findPlugin(dir, plugin) + if err != nil { + // try to retrieve from remote source + return fmt.Errorf("configmanager provider: ( %s ) %w\n%v", plugin, ErrPluginIssue, err) + } + // Initialising the plugins will ensure the client (configmanager-core) and the server (token-store-plugin-provider) are at expected versions of each other + p, err := NewPlugin(ctx, plpath) + if err != nil { + // wrap in init error + return err + } + s.plugin.Add(plugin, p) + } + return nil +} + +func (s *Store) GetValue(implemenation *config.ParsedTokenConfig) (string, error) { + plugin, exists := s.plugin.m[strings.ToLower(string(implemenation.Prefix()))] + if !exists { + return "", ErrPluginIssue + } + return plugin.GetValue(implemenation) +} + +// PluginCleanUp ensures the plugins are properly shut down +func (s *Store) PluginCleanUp() { + s.plugin.mu.Lock() + defer s.plugin.mu.Unlock() + for _, plugin := range s.plugin.m { + plugin.ClientCleanUp() + } +} + +// findPlugin ensures the path exists and search the following locations +// +// current dir +// home dir +func (s *Store) findPlugin(pluginDir, plugin string) (string, error) { + fullPluginPath := path.Join(pluginDir, plugin, fmt.Sprintf(namePattern, plugin, runtime.GOOS, runtime.GOARCH)) + if _, err := os.Stat(fullPluginPath); err == nil { + // plugin exists let's use it + return fullPluginPath, nil + } + + // Plugin is not found - will attempt to download and write them to the first specified directory + return s.downloadPlugin(fullPluginPath, plugin) +} + +// downloadPlugin deals with a specific plugin and only gets the current architecture +// +// it will create the required directory +func (s *Store) downloadPlugin(pluginFullPath, plugin string) (string, error) { + + releaseName := filepath.Base(pluginFullPath) + // This is opinionated and all windows plugins must be stored with the `.exe` extension + if runtime.GOOS == "windows" { + releaseName = releaseName + ".exe" + pluginFullPath = pluginFullPath + ".exe" + } + + // as an example + // https://github.com/DevLabFoundry/configmanager/releases/download/v3.0.0/awsparamstr-linux-amd64 + if err := s.osOps.MkdirAll(filepath.Dir(pluginFullPath), 0777); err != nil { + return "", fmt.Errorf("%w, %v", ErrMkdirAllFail, err) + } + + w, err := s.osOps.Create(pluginFullPath) + if err != nil { + return "", err + } + defer w.Close() + + specific := "download/%s" + // latest := "latest/download" + // TODO: need to think about providing these in a more configurable way + // + releasePath := path.Join(fmt.Sprintf(specific, "v3.0.0"), releaseName) + + pluginInfo, found := s.downloadInfoMap[plugin] + if !found { + return "", fmt.Errorf("download info not found for plugin ( %s )", plugin) + } + + link, err := url.Parse(fmt.Sprintf("%s/%s", pluginInfo.BaseUrl, releasePath)) + if err != nil { + return "", err + } + + req := &http.Request{ + URL: link, + Method: http.MethodGet, + } + resp, err := s.downloadClient.Do(req) + if err != nil { + return "", err + } + + defer resp.Body.Close() + + bar := progressbar.DefaultBytes( + resp.ContentLength, + fmt.Sprintf("downloading: %s", releaseName), + ) + + if _, err = io.Copy(io.MultiWriter(w, bar), resp.Body); err != nil { + return "", err + } + + return pluginFullPath, nil + +} + +// configManagerDir ensures the directory exists and returns the correct one based on config // -// Defined on the package for easier re-use across the program -type Strategy interface { - // Value retrieves the underlying value for the token - Value() (s string, e error) - // SetToken - SetToken(s *config.ParsedTokenConfig) +// It will return the full path to /some/dir/.configmanager and and ensures it exists +func (s *Store) configManagerDir() (string, error) { + var err error + + // if env var provided - it takes precendence over current directory + val, exists := os.LookupEnv(config.CONFIGMANAGER_DIR) + if !exists { + // otherwise we default to the current directory - i.e. from which the command is being run + val, err = s.osOps.Getwd() + if err != nil { + return "", err + } + } + + initialConfigPath := filepath.Join(val, pluginsLocation) + if err := s.osOps.MkdirAll(initialConfigPath, 0777); err != nil { + return "", err + } + return initialConfigPath, nil } diff --git a/internal/store/store_test.go b/internal/store/store_test.go index bb1c5b3..c21fcff 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -1 +1,69 @@ package store_test + +import ( + "context" + "errors" + "os" + "testing" + + "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/DevLabFoundry/configmanager/v3/internal/store" +) + +// These tests are more of an integration test as they rely on +func Test_Store(t *testing.T) { + + // Setup test store + os.Setenv(config.CONFIGMANAGER_DIR, "../../tokenstore/provider/empty") + + defer os.Unsetenv(config.CONFIGMANAGER_DIR) + + s := store.New(context.TODO()) + + if err := s.Init(context.TODO(), []string{"empty"}); err != nil { + t.Fatal(err) + } + token, err := config.NewParsedToken("empty", *config.NewConfig()) + if err != nil { + t.Fatal(err) + } + + t.Run("success no metadata", func(t *testing.T) { + token.WithSanitizedToken("/my/token") + got, err := s.GetValue(token) + assertStoreResp(t, got, "/my/token->", err, nil) + }) + + t.Run("succeds with metadata", func(t *testing.T) { + token.WithSanitizedToken("/my/token") + token.WithMetadata(`[version=123]`) + got, err := s.GetValue(token) + assertStoreResp(t, got, "/my/token->[version=123]", err, nil) + + }) + + t.Run("errors on retrieve", func(t *testing.T) { + token.WithSanitizedToken("err") + got, err := s.GetValue(token) + assertStoreResp(t, got, "", err, store.ErrRetrieveFailed) + }) +} + +func assertStoreResp(t *testing.T, got, want string, err, wantErr error) { + t.Helper() + + if err != nil && wantErr == nil { + t.Fatal(err) + } + + if wantErr != nil { + if !errors.Is(err, wantErr) { + t.Errorf("errors don't match, got %v, wanted %v", err, wantErr) + } + } + + if got != want { + t.Errorf("got %s, wanted %s", got, want) + } + +} diff --git a/internal/strategy/strategy.go b/internal/strategy/strategy.go deleted file mode 100644 index ac19a30..0000000 --- a/internal/strategy/strategy.go +++ /dev/null @@ -1,124 +0,0 @@ -// Package strategy is a factory method wrapper around the backing store implementations -package strategy - -import ( - "context" - "errors" - "fmt" - "sync" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" -) - -var ErrTokenInvalid = errors.New("invalid token - cannot get prefix") - -// StrategyFunc -type StrategyFunc func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) - -// StrategyFuncMap -type StrategyFuncMap map[config.ImplementationPrefix]StrategyFunc - -type Strategy struct { - config config.GenVarsConfig - strategyFuncMap strategyFnMap -} - -type Opts func(*Strategy) - -// New -func New(config config.GenVarsConfig, logger log.ILogger, opts ...Opts) *Strategy { - rs := &Strategy{ - config: config, - strategyFuncMap: strategyFnMap{mu: sync.Mutex{}, funcMap: defaultStrategyFuncMap(logger)}, - } - // overwrite or add any options/defaults set above - for _, o := range opts { - o(rs) - } - - return rs -} - -// WithStrategyFuncMap Adds custom implementations for prefix -// -// Mainly used for testing -// NOTE: this may lead to eventual optional configurations by users -func WithStrategyFuncMap(funcMap StrategyFuncMap) Opts { - return func(rs *Strategy) { - rs.strategyFuncMap.mu.Lock() - defer rs.strategyFuncMap.mu.Unlock() - for prefix, implementation := range funcMap { - rs.strategyFuncMap.funcMap[config.ImplementationPrefix(prefix)] = implementation - } - } -} - -// GetImplementation is a factory method returning the concrete implementation for the retrieval of the token value -// i.e. facilitating the exchange of the supplied token for the underlying value -func (rs *Strategy) GetImplementation(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - if token == nil { - return nil, fmt.Errorf("unable to get prefix, %w", ErrTokenInvalid) - } - - if store, found := rs.strategyFuncMap.funcMap[token.Prefix()]; found { - return store(ctx, token) - } - - return nil, fmt.Errorf("implementation not found for input string: %s", token) -} - -func ExchangeToken(s store.Strategy, token *config.ParsedTokenConfig) *TokenResponse { - cr := &TokenResponse{} - cr.Err = nil - cr.key = token - s.SetToken(token) - cr.value, cr.Err = s.Value() - return cr -} - -type TokenResponse struct { - value string - key *config.ParsedTokenConfig - Err error -} - -func (tr *TokenResponse) Key() *config.ParsedTokenConfig { - return tr.key -} - -func (tr *TokenResponse) Value() string { - return tr.value -} - -func defaultStrategyFuncMap(logger log.ILogger) StrategyFuncMap { - return map[config.ImplementationPrefix]StrategyFunc{ - config.AzTableStorePrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - return store.NewAzTableStore(ctx, token, logger) - }, - config.AzAppConfigPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - return store.NewAzAppConf(ctx, token, logger) - }, - config.GcpSecretsPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - return store.NewGcpSecrets(ctx, logger) - }, - config.SecretMgrPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - return store.NewSecretsMgr(ctx, logger) - }, - config.ParamStorePrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - return store.NewParamStore(ctx, logger) - }, - config.AzKeyVaultSecretsPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - return store.NewKvScrtStore(ctx, token, logger) - }, - config.HashicorpVaultPrefix: func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - return store.NewVaultStore(ctx, token, logger) - }, - } -} - -type strategyFnMap struct { - mu sync.Mutex - funcMap StrategyFuncMap -} diff --git a/internal/strategy/strategy_test.go b/internal/strategy/strategy_test.go deleted file mode 100644 index acae5e1..0000000 --- a/internal/strategy/strategy_test.go +++ /dev/null @@ -1,290 +0,0 @@ -package strategy_test - -import ( - "context" - "fmt" - "io" - "os" - "testing" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - log "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" - "github.com/DevLabFoundry/configmanager/v3/internal/strategy" - "github.com/DevLabFoundry/configmanager/v3/internal/testutils" - "github.com/go-test/deep" -) - -type mockGenerate struct { - inToken, value string - err error -} - -func (m mockGenerate) SetToken(s *config.ParsedTokenConfig) { -} - -func (m mockGenerate) Value() (s string, e error) { - return m.value, m.err -} - -var TEST_GCP_CREDS = []byte(`{ - "type": "service_account", - "project_id": "xxxxx", - "private_key_id": "yyyyyyyyyyyy", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDf842hcn5Nvp6e\n7yKARaCVIDfLXpKDhRwUOvHMzJ1ioRgQo/kbv1n4yHGCSUFyY6hKGj0HBjaGj5kE\n79H/6Y3dJNGhnsMnxBhHdo+3FI8QF0CHZh460NMZSAJ41UMQSBGssGVsNfyUzXGH\nLc45sIx/Twx3yr1k2GD3E8FlDcKlZqa3xGHf+aipg2X3NxbYi+Sz7Yed+SOMhNHl\ncX6E/TqG9n1aTyIwjMIHscCYarJqURkJxr24ukDroCeMxAfxYTdMvRU2e8pFEdoY\nrgUC88fYfaVI5txJ6j/ZKauKQX9Pa8tSyXJeGva3JYp4VC7V4IyoVviCUgEGWZDN\n6/i3zoF/AgMBAAECggEAcVBCcVYFIkE48SH+Svjv74SFtpj7eSB4vKO2hPFjEOyB\nyKmu+aMwWvjQtiNqwf46wIPWLR+vpxYxTpYpo1sBNMvUZfp2tEA8KKyMuw3j9ThO\npjO9R/UxWrFcztbZP/u3NbFrH/2Q95mbv9IlbnsuG5xbqqEig0wYg+uzBvaXbig3\n/Jr0vLT2BkRCBKQkYGjVZcHlHVLoF7/J8cghFgkV1PGvknOv6/q7qzn9L4TjQIet\nfhrhN8Z1vgFiSYtpjP6YQEUEPSHmCQeD3WzJcnASPpU2uCUwd/z65ltKPnn+rqMt\n6jt9R1S1Ju2ZSjv+kR5fIXzihdOzncyzDDm33c/QwQKBgQD2QDZuzLjTxnhsfGii\nKJDAts+Jqfs/6SeEJcJKtEngj4m7rgzyEjbKVp8qtRHIzglKRWAe62/qzzy2BkKi\nvAd4+ZzmG2SkgypGsKVfjGXVFixz2gtUdmBOmK/TnYsxNT9yTt+rX9IGqKK60q73\nOWl8VsliLIsfvSH7+bqi7sRcXQKBgQDo0VUebyQHoTAXPdzGy2ysrVPDiHcldH0Y\n/hvhQTZwxYaJr3HpOCGol2Xl6zyawuudEQsoQwJ3Li6yeb0YMGiWX77/t+qX3pSn\nkGuoftGaNDV7sLn9UV2y+InF8EL1CasrhG1k5RIuxyfV0w+QUo+E7LpVR5XkbJqT\n9QNKnDQXiwKBgQDvvEYCCqbp7e/xVhEbxbhfFdro4Cat6tRAz+3egrTlvXhO0jzi\nMp9Kz5f3oP5ma0gaGX5hu75icE1fvKqE+d+ghAqe7w5FJzkyRulJI0tEb2jphN7A\n5NoPypBqyZboWjmhlG4mzouPVf/POCuEnk028truDAWJ6by7Lj3oP+HFNQKBgQCc\n5BQ8QiFBkvnZb7LLtGIzq0n7RockEnAK25LmJRAOxs13E2fsBguIlR3x5qgckqY8\nXjPqmd2bet+1HhyzpEuWqkcIBGRum2wJz2T9UxjklbJE/D8Z2i8OYDZX0SUOA8n5\ntXASwduS8lqB2Y1vcHOO3AhlV6xHFnjEpCPnr4PbKQKBgAhQ9D9MPeuz+5yw3yHg\nkvULZRtud+uuaKrOayprN25RTxr9c0erxqnvM7KHeo6/urOXeEa7x2n21kAT0Nch\nkF2RtWBLZKXGZEVBtw1Fw0UKNh4IDgM26dwlzRfTVHCiw6M6dCiTNk9KkP2vlkim\n3QFDSSUp+eBTXA17WkDAQf7w\n-----END PRIVATE KEY-----\n", - "client_email": "foo@project.iam.gserviceaccount.com", - "client_id": "99999911111111", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/bla" - }`) - -func Test_Strategy_Retrieve_succeeds(t *testing.T) { - ttests := map[string]struct { - impl func(t *testing.T) store.Strategy - config *config.GenVarsConfig - token string - expect string - impPrefix config.ImplementationPrefix - }{ - "with mocked implementation AZTABLESTORAGE": { - func(t *testing.T) store.Strategy { - return &mockGenerate{"mountPath/token", "bar", nil} - }, - config.NewConfig().WithOutputPath("stdout"), - "mountPath/token", - "bar", - config.AzTableStorePrefix, - }, - // "error in retrieval": { - // func(t *testing.T) store.Strategy { - // return &mockGenerate{"SOME://mountPath/token", "bar", fmt.Errorf("unable to perform getTokenValue")} - // }, - // config.NewConfig().WithOutputPath("stdout").WithTokenSeparator("://"), - // []string{"SOME://token"}, - // config.AzAppConfigPrefix, - // "unable to perform getTokenValue", - // }, - } - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - token, _ := config.NewToken(tt.impPrefix, *tt.config) - token.WithSanitizedToken(tt.token) - got := strategy.ExchangeToken(tt.impl(t), token) - if got.Err != nil { - t.Errorf(testutils.TestPhraseWithContext, "Token response errored", got.Err.Error(), tt.expect) - } - if got.Value() != tt.expect { - t.Errorf(testutils.TestPhraseWithContext, "Value not correct", got.Value(), tt.expect) - } - if got.Key().StoreToken() != tt.token { - t.Errorf(testutils.TestPhraseWithContext, "Incorrect Token returned in Key", got.Key().StoreToken(), tt.token) - } - }) - } -} - -func Test_CustomStrategyFuncMap_add_own(t *testing.T) { - - ttests := map[string]struct { - }{ - "default": {}, - } - for name, _ := range ttests { - t.Run(name, func(t *testing.T) { - called := 0 - genVarsConf := config.NewConfig() - token, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig()) - token.WithSanitizedToken("mountPath/token") - - var custFunc = func(ctx context.Context, token *config.ParsedTokenConfig) (store.Strategy, error) { - m := &mockGenerate{"AZTABLESTORE://mountPath/token", "bar", nil} - called++ - return m, nil - } - - s := strategy.New(*genVarsConf, log.New(io.Discard), strategy.WithStrategyFuncMap(strategy.StrategyFuncMap{config.AzTableStorePrefix: custFunc})) - - store, _ := s.GetImplementation(context.TODO(), token) - _ = strategy.ExchangeToken(store, token) - - if called != 1 { - t.Errorf(testutils.TestPhraseWithContext, "custom func not called", called, 1) - } - }) - } -} - -func Test_SelectImpl_With(t *testing.T) { - - ttests := map[string]struct { - setUpTearDown func() func() - token string - config *config.GenVarsConfig - expect func() store.Strategy - expErr error - impPrefix config.ImplementationPrefix - }{ - "unknown": { - func() func() { - return func() { - } - }, - "foo/bar", - config.NewConfig().WithTokenSeparator("#"), - func() store.Strategy { return nil }, - fmt.Errorf("implementation not found for input string: UNKNOWN#foo/bar"), - config.UnknownPrefix, - }, - "success AZTABLESTORE": { - func() func() { - os.Setenv("AZURE_stuff", "foo") - return func() { - os.Clearenv() - } - }, - "foo/bar1", - config.NewConfig().WithTokenSeparator("#"), - func() store.Strategy { - token, _ := config.NewToken(config.AzTableStorePrefix, *config.NewConfig().WithTokenSeparator("#")) - token.WithSanitizedToken("foo/bar1") - - s, _ := store.NewAzTableStore(context.TODO(), token, log.New(io.Discard)) - return s - }, - nil, - config.AzTableStorePrefix, - }, - "success AWSPARAMSTR": { - func() func() { - os.Setenv("AWS_ACCESS_KEY", "AAAAAAAAAAAAAAA") - os.Setenv("AWS_SECRET_ACCESS_KEY", "00000000000000000000111111111") - return func() { - os.Clearenv() - } - }, - "foo/bar1", - config.NewConfig().WithTokenSeparator("#"), - func() store.Strategy { - s, _ := store.NewParamStore(context.TODO(), log.New(io.Discard)) - return s - }, - nil, - config.ParamStorePrefix, - }, - "success AWSSECRETS": { - func() func() { - os.Setenv("AWS_ACCESS_KEY", "AAAAAAAAAAAAAAA") - os.Setenv("AWS_SECRET_ACCESS_KEY", "00000000000000000000111111111") - return func() { - os.Clearenv() - } - }, - "foo/bar1", - config.NewConfig().WithTokenSeparator("#"), - func() store.Strategy { - s, _ := store.NewSecretsMgr(context.TODO(), log.New(io.Discard)) - return s - }, - nil, - config.SecretMgrPrefix, - }, - "success AZKVSECRET": { - func() func() { - os.Setenv("AWS_ACCESS_KEY", "AAAAAAAAAAAAAAA") - os.Setenv("AWS_SECRET_ACCESS_KEY", "00000000000000000000111111111") - return func() { - os.Clearenv() - } - }, - "foo/bar1", - config.NewConfig().WithTokenSeparator("#"), - func() store.Strategy { - token, _ := config.NewToken(config.AzKeyVaultSecretsPrefix, *config.NewConfig().WithTokenSeparator("#")) - token.WithSanitizedToken("foo/bar1") - s, _ := store.NewKvScrtStore(context.TODO(), token, log.New(io.Discard)) - return s - }, - nil, - config.AzKeyVaultSecretsPrefix, - }, - "success AZAPPCONF": { - func() func() { - return func() { - os.Clearenv() - } - }, - "foo/bar1", - config.NewConfig().WithTokenSeparator("#"), - func() store.Strategy { - token, _ := config.NewToken(config.AzAppConfigPrefix, *config.NewConfig().WithTokenSeparator("#")) - token.WithSanitizedToken("foo/bar1") - s, _ := store.NewAzAppConf(context.TODO(), token, log.New(io.Discard)) - return s - }, - nil, - config.AzAppConfigPrefix, - }, - "success VAULT": { - func() func() { - os.Setenv("VAULT_", "AAAAAAAAAAAAAAA") - return func() { - os.Clearenv() - } - }, - "foo/bar1", - config.NewConfig().WithTokenSeparator("#"), - func() store.Strategy { - token, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig().WithTokenSeparator("#")) - token.WithSanitizedToken("foo/bar1") - s, _ := store.NewVaultStore(context.TODO(), token, log.New(io.Discard)) - return s - }, - nil, - config.HashicorpVaultPrefix, - }, - "success GCPSECRETS": { - func() func() { - cf, _ := os.CreateTemp(".", "*") - cf.Write(TEST_GCP_CREDS) - os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", cf.Name()) - return func() { - os.Remove(cf.Name()) - os.Clearenv() - } - }, - "foo/bar1", - config.NewConfig().WithTokenSeparator("#"), - func() store.Strategy { - s, _ := store.NewGcpSecrets(context.TODO(), log.New(io.Discard)) - return s - }, - nil, - config.GcpSecretsPrefix, - }, - } - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - tearDown := tt.setUpTearDown() - defer tearDown() - want := tt.expect() - rs := strategy.New(*tt.config, log.New(io.Discard)) - token, _ := config.NewToken(tt.impPrefix, *tt.config) - token.WithSanitizedToken(tt.token) - got, err := rs.GetImplementation(context.TODO(), token) - - if err != nil { - if err.Error() != tt.expErr.Error() { - t.Errorf(testutils.TestPhraseWithContext, "uncaught error", err.Error(), tt.expErr.Error()) - } - return - } - - diff := deep.Equal(got, want) - if diff != nil { - t.Errorf(testutils.TestPhraseWithContext, "reflection of initialised implentations", fmt.Sprintf("%q", got), fmt.Sprintf("%q", want)) - } - }) - } -} diff --git a/internal/config/token.go b/internal/token/token.go similarity index 95% rename from internal/config/token.go rename to internal/token/token.go index 00b13a6..c03de69 100644 --- a/internal/config/token.go +++ b/internal/token/token.go @@ -1,4 +1,6 @@ -package config +package token + +import "github.com/DevLabFoundry/configmanager/v3/config" // TokenType is the lexer parsed TokenType type TokenType string @@ -58,7 +60,7 @@ type Source struct { type Token struct { Type TokenType Literal string - ImpPrefix ImplementationPrefix + ImpPrefix config.ImplementationPrefix Line int Column int Source Source diff --git a/internal/token/token_test.go b/internal/token/token_test.go new file mode 100644 index 0000000..e25146f --- /dev/null +++ b/internal/token/token_test.go @@ -0,0 +1,25 @@ +package token_test + +import ( + "testing" + + "github.com/DevLabFoundry/configmanager/v3/internal/token" +) + +func TestLookupIdent(t *testing.T) { + ttests := map[string]struct { + char string + expect token.TokenType + }{ + "new line": {"\n", token.NEW_LINE}, + "dash": {"-", token.TEXT}, + } + for name, tt := range ttests { + t.Run(name, func(t *testing.T) { + got := token.LookupIdent(tt.char) + if got != tt.expect { + t.Errorf("got %v wanted %v", got, tt.expect) + } + }) + } +} diff --git a/sonar-project.properties b/sonar-project.properties index b070476..818232b 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,9 +1,8 @@ -sonar.projectKey=dnitsch_configmanager -sonar.organization=dnitsch +sonar.projectKey=DevLabFoundry_configmanager +sonar.organization=devlabfoundry # This is the name and version displayed in the SonarCloud UI. sonar.projectName=configmanager -# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. sonar.sources=. sonar.exclusions=**/*_test.go,**/*_generated*.go,**/*_generated/**,**/vendor/**,**/examples/** sonar.inclusions=**/*.go @@ -13,7 +12,8 @@ sonar.test.inclusions=**/*_test.go sonar.test.exclusions=**/*_generated*.go,**/*_generated/**,**/vendor/** sonar.sourceEncoding=UTF-8 -sonar.qualitygate.wait=true +# until we start PR'ing into main - this needs to stay as false +sonar.qualitygate.wait=false # go sonar.go.coverage.reportPaths=.coverage/out diff --git a/tokenstore/README.md b/tokenstore/README.md new file mode 100644 index 0000000..d574a6f --- /dev/null +++ b/tokenstore/README.md @@ -0,0 +1,257 @@ +# Configmanager Plugin System + +The plugin architecture for configmanager is built using the [go-plugin](https://github.com/hashicorp/go-plugin?tab=readme-ov-file#go-plugin-system-over-rpc) from hashicorp. + +The existing implementations are converted into plugins using the gRPC model and are built using +gRPC [go-plugin](https://github.com/hashicorp/go-plugin?tab=readme-ov-file#go-plugin-system-over-rpc) and generated/updated with the [buf cli](https://buf.build/docs/cli/). + +You can run the generator with the below task in eirctl + +`eirctl run pipeline proto:build` + +## Plugin Architecture + + +### Diagram + +```mermaid +``` + +### Local dependencies + +The plugins will need to be downloaded into any one of these locations on disk, they will be checked in this order + +- IF PRESENT IT WILL TAKE PRECEDENCE -> `$CONFIGMANAGER_DIR` environment variable that points to the root of configmanager managed/used local dir/files + - e.g. given a path to an AWSPARAMSTR implementation under this path `/my/path/.configmanager/plugins/awsparamstr/awsparamstr-linux-amd64` + the corresponding value would be `CONFIGMANAGER_DIR=/my/path` - the application will create the `.configmanager/plugins/$PLUGIN_PREFIX_LOWERCASE/...` +- Curren tDirectory (directory from which the configmanager executable is run) +- Current Users Home directory + +The plugin is expected to be found under this path in the above locations +> `.configmanager/plugins/$PLUGIN_PREFIX_LOWERCASE/$PLUGIN_PREFIX_LOWERCASE-$GOOS-$GOARCH` + +e.g. in case of the AWS Parameter Store plugin `.configmanager/plugins/awsparamstr/awsparamstr-linux-amd64` + +> NB: `CONFIGMANAGER_DIR` is a global variable to the application and may have other uses in the future beyond the `plugins` directory + +### API Flow + +The existing Go-API flows can stil be used in the same way, however, you will need to pre-download the provider plugins you plan on using as part of your flow. + +> When using something like an AWS Lambda environment, one can leverage AWS Lambda layers to achieve the above folder structure. + +## Alternate architecture explored + +As part of the decision on which pluging architecture to use we also explored an alternate architecture using WASIP1. + +```go +import ( + "context" + "encoding/binary" + "encoding/json" + "sync" + "unicode/utf8" + "unsafe" + + "github.com/DevLabFoundry/configmanager/v3/plugins" +) + +// ==================== +// Bump allocator +// ==================== + +const heapSize = 64 * 1024 // 64 KiB arena; tune as needed + +type bumpAllocator struct { + mu sync.Mutex + heap []byte + used uint32 +} + +var alloc = bumpAllocator{ + heap: make([]byte, heapSize), +} + +// round allocation up to 8 bytes for basic alignment. +func roundUp(n uint32) uint32 { + const align = 8 + return (n + align - 1) &^ (align - 1) +} + +//go:wasmexport allocate +func Allocate(size uint32) uint32 { + if size == 0 { + return 0 + } + size = roundUp(size) + + alloc.mu.Lock() + defer alloc.mu.Unlock() + + if alloc.used+size > uint32(len(alloc.heap)) { + // Out of memory in our arena. + return 0 + } + + offset := alloc.used + alloc.used += size + + // Return pointer into linear memory for &heap[offset]. + return uint32(uintptr(unsafe.Pointer(&alloc.heap[offset]))) +} + +//go:wasmexport deallocate +func Deallocate(ptr, size uint32) { + // For a simple bump allocator, deallocate is a no-op. + // Memory is reclaimed when the module instance is destroyed. + _ = ptr + _ = size +} + +type Hdr struct { + Data uintptr + Len int + Cap int +} + +// ==================== +// Helpers +// ==================== + +// bytesFromPtrLen reinterprets a (ptr,len) pair in wasm linear memory +// as a Go []byte without copying. +func bytesFromPtrLen(ptr, length uint32) []byte { + if length == 0 { + return nil + } + + hdr := Hdr{ + Data: uintptr(ptr), + Len: int(length), + Cap: int(length), + } + + return *(*[]byte)(unsafe.Pointer(&hdr)) +} + +// ==================== +// strategy_token_value +// ==================== +// +// ABI: +// +// strategy_token_value( +// inPtr, inLen, outPtr, outCap, outLenPtr uint32, +// ) int32 +// +// Host contract: +// - Input bytes are at (inPtr, inLen) +// - Output buffer is [outPtr : outPtr+outCap) +// - outLenPtr points to 4 bytes where we write the required length +// +// Behaviour: +// - If input length == 0 => ERR_EMPTY_INPUT +// - If invalid UTF-8 => ERR_INVALID_UTF8 +// - Always write required length to *outLenPtr (little-endian) +// - If required > outCap => ERR_BUF_TOO_SMALL +// - Else copy into caller buffer and return OK +// +//go:wasmexport strategy_token_value +func StrategyTokenValue(tokenPtr, tokenLen, outPtr, outCap, outLenPtr uint32) int32 { + defer func() { + // Make sure panics don't leak as traps. + if r := recover(); r != nil { + if outLenPtr != 0 { + if lenCell := bytesFromPtrLen(outLenPtr, 4); len(lenCell) == 4 { + binary.LittleEndian.PutUint32(lenCell, 0) + } + } + } + }() + + if tokenLen == 0 { + // Mark required length as 0 and signal error. + if outLenPtr != 0 { + if lenCell := bytesFromPtrLen(outLenPtr, 4); len(lenCell) == 4 { + binary.LittleEndian.PutUint32(lenCell, 0) + } + } + return plugins.ERR_EMPTY_INPUT + } + + tokenBytes := bytesFromPtrLen(tokenPtr, tokenLen) + if !utf8.Valid(tokenBytes) { + if outLenPtr != 0 { + if lenCell := bytesFromPtrLen(outLenPtr, 4); len(lenCell) == 4 { + binary.LittleEndian.PutUint32(lenCell, uint32(len(tokenBytes))) + } + } + return plugins.ERR_INVALID_UTF8 + } + + // --- Business logic (replace with your real token strategy) --- + // unmarshal string into an object + token := &plugins.MessagExchange{} + if err := json.Unmarshal(tokenBytes, token); err != nil { + return plugins.ERR_FAILED_UNMARSHAL_MESSAGE + } + + // logger := log.New(os.Stdout) + // logger.SetLevel(log.DebugLvl) + + store, err := NewParamStore(context.Background()) + if err != nil { + return plugins.ERR_INIT_STORE + } + + outStr, err := store.Value(token) + if err != nil { + return plugins.ERR_FAILED_VALUE_RETRIEVAL + } + + outBytes := []byte(outStr) + // -------------------------------------------------------------- + // BEGIN RETURN Allocation + // -------------------------------------------------------------- + required := uint32(len(outBytes)) + + // Always write required length. + if outLenPtr != 0 { + lenCell := bytesFromPtrLen(outLenPtr, 4) + if len(lenCell) != 4 { + return plugins.ERR_INTERNAL + } + binary.LittleEndian.PutUint32(lenCell, required) + } + + if required > outCap { + return plugins.ERR_BUF_TOO_SMALL + } + + if required == 0 { + return plugins.OK + } + + outSlice := bytesFromPtrLen(outPtr, outCap) + if uint32(len(outSlice)) < required { + return plugins.ERR_INTERNAL + } + + copy(outSlice, outBytes) + return plugins.OK +} + +// main is required for wasip1 +// scaffolds the `_initialize` method +func main() {} +``` + +### Build notes + +build using the `-buildmode=c-shared` which will convert the module to a reactor module + +`GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o awsparams.wasm` + +> the outcome of an approach to this was fundamentally flawed in that any WASM code is heavily sandboxed and performing any kind of network calls is impossible in the OOTB set up and only slightly possible by using alternate WASM runtimes or add-ons + +> Good fun to explore though :smiley: diff --git a/tokenstore/grpc.go b/tokenstore/grpc.go new file mode 100644 index 0000000..ed618ca --- /dev/null +++ b/tokenstore/grpc.go @@ -0,0 +1,34 @@ +package tokenstore + +import ( + "context" + + "github.com/DevLabFoundry/configmanager/v3/tokenstore/proto" +) + +// GRPCClient is the host process talking to the plugins +// i.e. the GRPCServer implementation of the TokenStore +type GRPCClient struct{ client proto.TokenStoreClient } + +func (m *GRPCClient) Value(key string, metadata []byte) (string, error) { + resp, err := m.client.Value(context.Background(), &proto.TokenValueRequest{ + Token: key, + Metadata: metadata, + }) + if err != nil { + return "", err + } + + return resp.Value, nil +} + +// Here is the gRPC server that GRPCClient talks to. +type GRPCServer struct { + // This is the real implementation + Impl TokenStore +} + +func (m *GRPCServer) Value(ctx context.Context, req *proto.TokenValueRequest) (*proto.TokenValueResponse, error) { + v, err := m.Impl.Value(req.Token, req.Metadata) + return &proto.TokenValueResponse{Value: v}, err +} diff --git a/tokenstore/interface.go b/tokenstore/interface.go new file mode 100644 index 0000000..e403934 --- /dev/null +++ b/tokenstore/interface.go @@ -0,0 +1,46 @@ +package tokenstore + +import ( + "context" + + "google.golang.org/grpc" + + "github.com/DevLabFoundry/configmanager/v3/tokenstore/proto" + "github.com/hashicorp/go-plugin" +) + +// Handshake is a common handshake that is shared by plugin and host. +var Handshake = plugin.HandshakeConfig{ + // This isn't required when using VersionedPlugins + ProtocolVersion: 1, + MagicCookieKey: "CONFIGMANAGER_PLUGIN", + MagicCookieValue: "configmanager-plugin-hello", +} + +// // PluginMap is the map of plugins we can dispense. +var PluginMap = map[string]plugin.Plugin{ + "configmanager_token_store": &GRPCPlugin{}, +} + +// TokenStore is the interface that we're exposing as a plugin. +type TokenStore interface { + Value(token string, metadata []byte) (string, error) +} + +// This is the implementation of plugin.GRPCPlugin so we can serve/consume this. +type GRPCPlugin struct { + // GRPCPlugin must still implement the Plugin interface + plugin.Plugin + // Concrete implementation, written in Go. This is only used for plugins + // that are written in Go. + Impl TokenStore +} + +func (p *GRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + proto.RegisterTokenStoreServer(s, &GRPCServer{Impl: p.Impl}) + return nil +} + +func (p *GRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCClient{client: proto.NewTokenStoreClient(c)}, nil +} diff --git a/tokenstore/proto/token_store.pb.go b/tokenstore/proto/token_store.pb.go new file mode 100644 index 0000000..db5e957 --- /dev/null +++ b/tokenstore/proto/token_store.pb.go @@ -0,0 +1,183 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: token_store.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TokenValueRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + Metadata []byte `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TokenValueRequest) Reset() { + *x = TokenValueRequest{} + mi := &file_token_store_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TokenValueRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenValueRequest) ProtoMessage() {} + +func (x *TokenValueRequest) ProtoReflect() protoreflect.Message { + mi := &file_token_store_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenValueRequest.ProtoReflect.Descriptor instead. +func (*TokenValueRequest) Descriptor() ([]byte, []int) { + return file_token_store_proto_rawDescGZIP(), []int{0} +} + +func (x *TokenValueRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *TokenValueRequest) GetMetadata() []byte { + if x != nil { + return x.Metadata + } + return nil +} + +type TokenValueResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TokenValueResponse) Reset() { + *x = TokenValueResponse{} + mi := &file_token_store_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TokenValueResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TokenValueResponse) ProtoMessage() {} + +func (x *TokenValueResponse) ProtoReflect() protoreflect.Message { + mi := &file_token_store_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TokenValueResponse.ProtoReflect.Descriptor instead. +func (*TokenValueResponse) Descriptor() ([]byte, []int) { + return file_token_store_proto_rawDescGZIP(), []int{1} +} + +func (x *TokenValueResponse) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +var File_token_store_proto protoreflect.FileDescriptor + +const file_token_store_proto_rawDesc = "" + + "\n" + + "\x11token_store.proto\x12\x05proto\"E\n" + + "\x11TokenValueRequest\x12\x14\n" + + "\x05token\x18\x01 \x01(\tR\x05token\x12\x1a\n" + + "\bmetadata\x18\x02 \x01(\fR\bmetadata\"*\n" + + "\x12TokenValueResponse\x12\x14\n" + + "\x05value\x18\x01 \x01(\tR\x05value2J\n" + + "\n" + + "TokenStore\x12<\n" + + "\x05Value\x12\x18.proto.TokenValueRequest\x1a\x19.proto.TokenValueResponseB\tZ\a./protob\x06proto3" + +var ( + file_token_store_proto_rawDescOnce sync.Once + file_token_store_proto_rawDescData []byte +) + +func file_token_store_proto_rawDescGZIP() []byte { + file_token_store_proto_rawDescOnce.Do(func() { + file_token_store_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_token_store_proto_rawDesc), len(file_token_store_proto_rawDesc))) + }) + return file_token_store_proto_rawDescData +} + +var file_token_store_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_token_store_proto_goTypes = []any{ + (*TokenValueRequest)(nil), // 0: proto.TokenValueRequest + (*TokenValueResponse)(nil), // 1: proto.TokenValueResponse +} +var file_token_store_proto_depIdxs = []int32{ + 0, // 0: proto.TokenStore.Value:input_type -> proto.TokenValueRequest + 1, // 1: proto.TokenStore.Value:output_type -> proto.TokenValueResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_token_store_proto_init() } +func file_token_store_proto_init() { + if File_token_store_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_token_store_proto_rawDesc), len(file_token_store_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_token_store_proto_goTypes, + DependencyIndexes: file_token_store_proto_depIdxs, + MessageInfos: file_token_store_proto_msgTypes, + }.Build() + File_token_store_proto = out.File + file_token_store_proto_goTypes = nil + file_token_store_proto_depIdxs = nil +} diff --git a/tokenstore/proto/token_store.proto b/tokenstore/proto/token_store.proto new file mode 100644 index 0000000..a22eb02 --- /dev/null +++ b/tokenstore/proto/token_store.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +package proto; +option go_package = "./proto"; + +message TokenValueRequest { + string token = 1; + bytes metadata = 2; +} + +message TokenValueResponse { + string value = 1; +} + +service TokenStore { + rpc Value(TokenValueRequest) returns (TokenValueResponse); +} diff --git a/tokenstore/proto/token_store_grpc.pb.go b/tokenstore/proto/token_store_grpc.pb.go new file mode 100644 index 0000000..6ea7faf --- /dev/null +++ b/tokenstore/proto/token_store_grpc.pb.go @@ -0,0 +1,107 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.3.0 +// - protoc (unknown) +// source: token_store.proto + +package proto + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +const ( + TokenStore_Value_FullMethodName = "/proto.TokenStore/Value" +) + +// TokenStoreClient is the client API for TokenStore service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type TokenStoreClient interface { + Value(ctx context.Context, in *TokenValueRequest, opts ...grpc.CallOption) (*TokenValueResponse, error) +} + +type tokenStoreClient struct { + cc grpc.ClientConnInterface +} + +func NewTokenStoreClient(cc grpc.ClientConnInterface) TokenStoreClient { + return &tokenStoreClient{cc} +} + +func (c *tokenStoreClient) Value(ctx context.Context, in *TokenValueRequest, opts ...grpc.CallOption) (*TokenValueResponse, error) { + out := new(TokenValueResponse) + err := c.cc.Invoke(ctx, TokenStore_Value_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TokenStoreServer is the server API for TokenStore service. +// All implementations should embed UnimplementedTokenStoreServer +// for forward compatibility +type TokenStoreServer interface { + Value(context.Context, *TokenValueRequest) (*TokenValueResponse, error) +} + +// UnimplementedTokenStoreServer should be embedded to have forward compatible implementations. +type UnimplementedTokenStoreServer struct { +} + +func (UnimplementedTokenStoreServer) Value(context.Context, *TokenValueRequest) (*TokenValueResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Value not implemented") +} + +// UnsafeTokenStoreServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TokenStoreServer will +// result in compilation errors. +type UnsafeTokenStoreServer interface { + mustEmbedUnimplementedTokenStoreServer() +} + +func RegisterTokenStoreServer(s grpc.ServiceRegistrar, srv TokenStoreServer) { + s.RegisterService(&TokenStore_ServiceDesc, srv) +} + +func _TokenStore_Value_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TokenValueRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TokenStoreServer).Value(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TokenStore_Value_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TokenStoreServer).Value(ctx, req.(*TokenValueRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// TokenStore_ServiceDesc is the grpc.ServiceDesc for TokenStore service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TokenStore_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "proto.TokenStore", + HandlerType: (*TokenStoreServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Value", + Handler: _TokenStore_Value_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "token_store.proto", +} diff --git a/tokenstore/provider/awsparamstr/README.md b/tokenstore/provider/awsparamstr/README.md new file mode 100644 index 0000000..8f4fda3 --- /dev/null +++ b/tokenstore/provider/awsparamstr/README.md @@ -0,0 +1,4 @@ +# AWS PARAM STORE Plugin + +This is the `awsparamstr` implementation plugin built using the go-plugin architecture from hashicorp... + diff --git a/tokenstore/provider/awsparamstr/go.mod b/tokenstore/provider/awsparamstr/go.mod new file mode 100644 index 0000000..c3d1a37 --- /dev/null +++ b/tokenstore/provider/awsparamstr/go.mod @@ -0,0 +1,46 @@ +module github.com/DevLabFoundry/configmanager-plugin/awsparamstr + +go 1.26 + +toolchain go1.26.1 + +require ( + github.com/DevLabFoundry/configmanager/v3 v3.0.0 + github.com/aws/aws-sdk-go-v2/service/ssm v1.68.3 + github.com/hashicorp/go-hclog v1.6.3 +) + +require ( + github.com/aws/aws-sdk-go-v2/credentials v1.19.12 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/oklog/run v1.2.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) + +require ( + github.com/aws/aws-sdk-go-v2 v1.41.4 + github.com/aws/aws-sdk-go-v2/config v1.32.12 + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 // indirect + github.com/aws/smithy-go v1.24.2 // indirect + github.com/hashicorp/go-plugin v1.7.0 +) + +replace github.com/DevLabFoundry/configmanager/v3 v3.0.0 => ../../../ diff --git a/tokenstore/provider/awsparamstr/go.sum b/tokenstore/provider/awsparamstr/go.sum new file mode 100644 index 0000000..20a3784 --- /dev/null +++ b/tokenstore/provider/awsparamstr/go.sum @@ -0,0 +1,109 @@ +github.com/aws/aws-sdk-go-v2 v1.41.4 h1:10f50G7WyU02T56ox1wWXq+zTX9I1zxG46HYuG1hH/k= +github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= +github.com/aws/aws-sdk-go-v2/config v1.32.12 h1:O3csC7HUGn2895eNrLytOJQdoL2xyJy0iYXhoZ1OmP0= +github.com/aws/aws-sdk-go-v2/config v1.32.12/go.mod h1:96zTvoOFR4FURjI+/5wY1vc1ABceROO4lWgWJuxgy0g= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12 h1:oqtA6v+y5fZg//tcTWahyN9PEn5eDU/Wpvc2+kJ4aY8= +github.com/aws/aws-sdk-go-v2/credentials v1.19.12/go.mod h1:U3R1RtSHx6NB0DvEQFGyf/0sbrpJrluENHdPy1j/3TE= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20 h1:zOgq3uezl5nznfoK3ODuqbhVg1JzAGDUhXOsU0IDCAo= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.20/go.mod h1:z/MVwUARehy6GAg/yQ1GO2IMl0k++cu1ohP9zo887wE= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20 h1:CNXO7mvgThFGqOFgbNAP2nol2qAWBOGfqR/7tQlvLmc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20 h1:tN6W/hg+pkM+tf9XDkWUbDEjGLb+raoBMFsTodcoYKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20 h1:2HvVAIq+YqgGotK6EkMf+KIEqTISmTYh5zLpYyeTo1Y= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 h1:0GFOLzEbOyZABS3PhYfBIx2rNBACYcKty+XGkTgw1ow= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.8/go.mod h1:LXypKvk85AROkKhOG6/YEcHFPoX+prKTowKnVdcaIxE= +github.com/aws/aws-sdk-go-v2/service/ssm v1.68.3 h1:bBoWhx8lsFLTXintRX64ZBXcmFZbGqUmaPUrjXECqIc= +github.com/aws/aws-sdk-go-v2/service/ssm v1.68.3/go.mod h1:rcRkKbUJ2437WuXdq9fbj+MjTudYWzY9Ct8kiBbN8a8= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13 h1:kiIDLZ005EcKomYYITtfsjn7dtOwHDOFy7IbPXKek2o= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.13/go.mod h1:2h/xGEowcW/g38g06g3KpRWDlT+OTfxxI0o1KqayAB8= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17 h1:jzKAXIlhZhJbnYwHbvUQZEB8KfgAEuG0dc08Bkda7NU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.17/go.mod h1:Al9fFsXjv4KfbzQHGe6V4NZSZQXecFcvaIF4e70FoRA= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9 h1:Cng+OOwCHmFljXIxpEVXAGMnBia8MSU6Ch5i9PgBkcU= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.9/go.mod h1:LrlIndBDdjA/EeXeyNBle+gyCwTlizzW5ycgWnvIxkk= +github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= +github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= +github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 h1:sRy++txmErSjyVWlIgQB5nB+U75+Di+AH7eEZ002B/s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/internal/store/paramstore.go b/tokenstore/provider/awsparamstr/impl/paramstore.go similarity index 58% rename from internal/store/paramstore.go rename to tokenstore/provider/awsparamstr/impl/paramstore.go index aa45ace..58ffbb5 100644 --- a/internal/store/paramstore.go +++ b/tokenstore/provider/awsparamstr/impl/paramstore.go @@ -1,13 +1,14 @@ -package store +package impl import ( "context" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" + "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" "github.com/aws/aws-sdk-go-v2/aws" awsConf "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/service/ssm" + "github.com/hashicorp/go-hclog" ) type paramStoreApi interface { @@ -17,19 +18,18 @@ type paramStoreApi interface { type ParamStore struct { svc paramStoreApi ctx context.Context - logger log.ILogger config *ParamStrConfig token *config.ParsedTokenConfig + logger hclog.Logger } type ParamStrConfig struct { // reserved for potential future use } -func NewParamStore(ctx context.Context, logger log.ILogger) (*ParamStore, error) { +func NewParamStore(ctx context.Context, logger hclog.Logger) (*ParamStore, error) { cfg, err := awsConf.LoadDefaultConfig(ctx) if err != nil { - logger.Error("unable to load SDK config, %v\n%w", err, ErrClientInitialization) return nil, err } c := ssm.NewFromConfig(cfg) @@ -45,19 +45,12 @@ func (s *ParamStore) WithSvc(svc paramStoreApi) { s.svc = svc } -func (imp *ParamStore) SetToken(token *config.ParsedTokenConfig) { - storeConf := &ParamStrConfig{} - _ = token.ParseMetadata(storeConf) - imp.token = token - imp.config = storeConf -} - -func (imp *ParamStore) Value() (string, error) { - imp.logger.Info("%s", "Concrete implementation ParameterStore") - imp.logger.Info("ParamStore Token: %s", imp.token.String()) +func (imp *ParamStore) Value(token string, metadata []byte) (string, error) { + imp.logger.Info("Concrete implementation ParameterStore") + imp.logger.Info("ParamStore Token: %s", token) input := &ssm.GetParameterInput{ - Name: aws.String(imp.token.StoreToken()), + Name: aws.String(token), WithDecryption: aws.Bool(true), } ctx, cancel := context.WithCancel(imp.ctx) @@ -65,7 +58,7 @@ func (imp *ParamStore) Value() (string, error) { result, err := imp.svc.GetParameter(ctx, input) if err != nil { - imp.logger.Error(implementationNetworkErr, config.ParamStorePrefix, err, imp.token.StoreToken()) + imp.logger.Error(tokenstore.ImplementationNetworkErr, "config.ParamStorePrefix", err, token) return "", err } diff --git a/internal/store/paramstore_test.go b/tokenstore/provider/awsparamstr/impl/paramstore_test.go similarity index 81% rename from internal/store/paramstore_test.go rename to tokenstore/provider/awsparamstr/impl/paramstore_test.go index 8fc11d4..0979e6a 100644 --- a/internal/store/paramstore_test.go +++ b/tokenstore/provider/awsparamstr/impl/paramstore_test.go @@ -1,18 +1,21 @@ -package store_test +package impl_test import ( "context" "fmt" - "io" "strings" "testing" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" - "github.com/DevLabFoundry/configmanager/v3/internal/testutils" + "github.com/DevLabFoundry/configmanager-plugin/awsparamstr/impl" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/aws/aws-sdk-go-v2/service/ssm" "github.com/aws/aws-sdk-go-v2/service/ssm/types" + "github.com/hashicorp/go-hclog" +) + +const ( + TestPhrase string = "got: %v want: %v\n" + TestPhraseWithContext string = "%s\n got: %v\n\n want: %v\n" ) type mockParamApi func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) @@ -52,7 +55,7 @@ func Test_GetParamStore(t *testing.T) { "successVal": { func() *config.ParsedTokenConfig { // "VAULT://secret___/demo/configmanager" - tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.ParamStorePrefix, *config.NewConfig()) tkn.WithSanitizedToken("/token/1") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -71,7 +74,7 @@ func Test_GetParamStore(t *testing.T) { "successVal with keyseparator": { func() *config.ParsedTokenConfig { // "AWSPARAMSTR#/token/1|somekey", - tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.ParamStorePrefix, *config.NewConfig()) tkn.WithSanitizedToken("/token/1") tkn.WithKeyPath("somekey") tkn.WithMetadata("") @@ -95,7 +98,7 @@ func Test_GetParamStore(t *testing.T) { "errored": { func() *config.ParsedTokenConfig { // "AWSPARAMSTR#/token/1", - tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.ParamStorePrefix, *config.NewConfig()) tkn.WithSanitizedToken("/token/1") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -112,7 +115,7 @@ func Test_GetParamStore(t *testing.T) { "nil to empty": { func() *config.ParsedTokenConfig { // "AWSPARAMSTR#/token/1", - tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.ParamStorePrefix, *config.NewConfig()) tkn.WithSanitizedToken("/token/1") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -131,21 +134,21 @@ func Test_GetParamStore(t *testing.T) { } for name, tt := range tests { t.Run(name, func(t *testing.T) { - impl, err := store.NewParamStore(context.TODO(), log.New(io.Discard)) + impl, err := impl.NewParamStore(context.TODO(), hclog.NewNullLogger()) if err != nil { - t.Errorf(testutils.TestPhrase, err.Error(), nil) + t.Errorf(TestPhrase, err.Error(), nil) } impl.WithSvc(tt.mockClient(t)) - impl.SetToken(tt.token()) - got, err := impl.Value() + + got, err := impl.Value(tt.token().StoreToken(), []byte{}) if err != nil { if err.Error() != tt.expect { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) + t.Errorf(TestPhrase, err.Error(), tt.expect) } return } if got != tt.expect { - t.Errorf(testutils.TestPhrase, got, tt.expect) + t.Errorf(TestPhrase, got, tt.expect) } }) } diff --git a/tokenstore/provider/awsparamstr/main.go b/tokenstore/provider/awsparamstr/main.go new file mode 100644 index 0000000..14ab277 --- /dev/null +++ b/tokenstore/provider/awsparamstr/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + "os" + + "github.com/DevLabFoundry/configmanager-plugin/awsparamstr/impl" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" +) + +type implIface interface { + Value(token string, metadata []byte) (string, error) +} +type TokenStorePlugin struct { + impl implIface // Value(token string, metadata []byte) (string, error) +} + +func (ts TokenStorePlugin) Value(key string, metadata []byte) (string, error) { + return ts.impl.Value(key, metadata) +} + +func main() { + log := hclog.New(hclog.DefaultOptions) + log.SetLevel(hclog.LevelFromString("error")) + + i, err := impl.NewParamStore(context.Background(), log) + if err != nil { + log.Error("error", err) + os.Exit(1) + } + + ts := TokenStorePlugin{impl: i} + + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: tokenstore.Handshake, + Plugins: map[string]plugin.Plugin{ + "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: ts}, + }, + VersionedPlugins: map[int]plugin.PluginSet{ + 1: { + "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: ts}, + }, + }, + // A non-nil value here enables gRPC serving for this plugin... + GRPCServer: plugin.DefaultGRPCServer, + }) +} diff --git a/tokenstore/provider/awssecrets/.gitkeep b/tokenstore/provider/awssecrets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tokenstore/provider/empty/go.mod b/tokenstore/provider/empty/go.mod new file mode 100644 index 0000000..9fe9cf3 --- /dev/null +++ b/tokenstore/provider/empty/go.mod @@ -0,0 +1,28 @@ +module github.com/DevLabFoundry/configmanager-plugin/empty + +go 1.26 + +toolchain go1.26.1 + +require ( + github.com/DevLabFoundry/configmanager/v3 v3.0.0 + github.com/hashicorp/go-plugin v1.7.0 +) + +require ( + github.com/fatih/color v1.18.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/oklog/run v1.2.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) + +replace github.com/DevLabFoundry/configmanager/v3 v3.0.0 => ../../../ diff --git a/tokenstore/provider/empty/go.sum b/tokenstore/provider/empty/go.sum new file mode 100644 index 0000000..3241034 --- /dev/null +++ b/tokenstore/provider/empty/go.sum @@ -0,0 +1,79 @@ +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= +github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 h1:sRy++txmErSjyVWlIgQB5nB+U75+Di+AH7eEZ002B/s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/tokenstore/provider/empty/main.go b/tokenstore/provider/empty/main.go new file mode 100644 index 0000000..31abf0d --- /dev/null +++ b/tokenstore/provider/empty/main.go @@ -0,0 +1,37 @@ +// Package main of empty implementation is used for "unit" testing +// +// The TokenStore Value implementation returns the key and metadata passed +// in the case of key being `err` a simulated error is returned +package main + +import ( + "errors" + "fmt" + + "github.com/DevLabFoundry/configmanager/v3/tokenstore" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" +) + +// TokenStorePlugin here is a sample plugin we can use in tests +// It handles some basic error scenarios +type TokenStorePlugin struct{} + +func (ts TokenStorePlugin) Value(key string, metadata []byte) (string, error) { + if key == "err" { + return "", errors.New("token store implementation simulated error") + } + return fmt.Sprintf("%s->%s", key, metadata), nil +} + +func main() { + plugin.Serve(&plugin.ServeConfig{ + Logger: hclog.NewNullLogger(), + HandshakeConfig: tokenstore.Handshake, + Plugins: map[string]plugin.Plugin{ + "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: &TokenStorePlugin{}}, + }, + // A non-nil value here enables gRPC serving for this plugin... + GRPCServer: plugin.DefaultGRPCServer, + }) +} diff --git a/tokenstore/provider/vault/README.md b/tokenstore/provider/vault/README.md new file mode 100644 index 0000000..89622a7 --- /dev/null +++ b/tokenstore/provider/vault/README.md @@ -0,0 +1,2 @@ +# Hashicorp Vault + diff --git a/tokenstore/provider/vault/go.mod b/tokenstore/provider/vault/go.mod new file mode 100644 index 0000000..6fbe8e4 --- /dev/null +++ b/tokenstore/provider/vault/go.mod @@ -0,0 +1,53 @@ +module github.com/DevLabFoundry/configmanager-plugin/vault + +go 1.26 + +toolchain go1.26.1 + +require ( + github.com/DevLabFoundry/configmanager/v3 v3.0.0 + github.com/hashicorp/go-hclog v1.6.3 + github.com/hashicorp/go-plugin v1.7.0 +) + +require ( + github.com/aws/aws-sdk-go v1.55.8 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + golang.org/x/time v0.15.0 // indirect +) + +require ( + github.com/fatih/color v1.18.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/hashicorp/vault/api v1.22.0 + github.com/hashicorp/vault/api/auth/aws v0.11.0 + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/oklog/run v1.2.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) + +replace github.com/DevLabFoundry/configmanager/v3 v3.0.0 => ../../../ diff --git a/tokenstore/provider/vault/go.sum b/tokenstore/provider/vault/go.sum new file mode 100644 index 0000000..fc08a5f --- /dev/null +++ b/tokenstore/provider/vault/go.sum @@ -0,0 +1,155 @@ +github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= +github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +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 v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +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/awsutil v0.3.0 h1:I8bynUKMh9I7JdwtW9voJ0xmHvBpxQtLjrMFDYmhOxY= +github.com/hashicorp/go-secure-stdlib/awsutil v0.3.0/go.mod h1:oKHSQs4ivIfZ3fbXGQOop1XuDfdSb8RIsWTGaAanSfg= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= +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/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= +github.com/hashicorp/vault/api/auth/aws v0.11.0 h1:lWdUxrzvPotg6idNr62al4w97BgI9xTDdzMCTViNH2s= +github.com/hashicorp/vault/api/auth/aws v0.11.0/go.mod h1:PWqdH/xqaudapmnnGP9ip2xbxT/kRW2qEgpqiQff6Gc= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= +github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +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/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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= +go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= +go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= +go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= +go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= +go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= +go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= +go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= +go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 h1:sRy++txmErSjyVWlIgQB5nB+U75+Di+AH7eEZ002B/s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +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= +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/internal/store/hashivault.go b/tokenstore/provider/vault/impl/hashivault.go similarity index 89% rename from internal/store/hashivault.go rename to tokenstore/provider/vault/impl/hashivault.go index 558c9b2..dd4ef31 100644 --- a/internal/store/hashivault.go +++ b/tokenstore/provider/vault/impl/hashivault.go @@ -1,4 +1,4 @@ -package store +package impl import ( "context" @@ -8,9 +8,10 @@ import ( "strconv" "strings" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" + "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" + "github.com/hashicorp/go-hclog" vault "github.com/hashicorp/vault/api" auth "github.com/hashicorp/vault/api/auth/aws" ) @@ -29,7 +30,7 @@ type hashiVaultApi interface { type VaultStore struct { svc hashiVaultApi ctx context.Context - logger log.ILogger + logger hclog.Logger config *VaultConfig token *config.ParsedTokenConfig strippedToken string @@ -41,7 +42,7 @@ type VaultConfig struct { Role string `json:"iam_role"` } -func NewVaultStore(ctx context.Context, token *config.ParsedTokenConfig, logger log.ILogger) (*VaultStore, error) { +func NewVaultStore(ctx context.Context, token *config.ParsedTokenConfig, logger hclog.Logger) (*VaultStore, error) { storeConf := &VaultConfig{} _ = token.ParseMetadata(storeConf) imp := &VaultStore{ @@ -56,7 +57,7 @@ func NewVaultStore(ctx context.Context, token *config.ParsedTokenConfig, logger imp.strippedToken = vt.Token client, err := vault.NewClient(config) if err != nil { - return nil, fmt.Errorf("%v\n%w", err, ErrClientInitialization) + return nil, fmt.Errorf("%v\n%w", err, tokenstore.ErrClientInitialization) } if strings.HasPrefix(os.Getenv("VAULT_TOKEN"), "aws_iam") { @@ -84,13 +85,13 @@ func newVaultStoreWithAWSAuthIAM(client *vault.Client, role string) (*vault.Clie auth.WithRole(role), ) if err != nil { - return nil, fmt.Errorf("unable to initialize AWS auth method: %s. %w", err, ErrClientInitialization) + return nil, fmt.Errorf("unable to initialize AWS auth method: %s. %w", err, tokenstore.ErrClientInitialization) } authInfo, err := client.Auth().Login(context.Background(), awsAuth) if err != nil { - return nil, fmt.Errorf("unable to login to AWS auth method: %s. %w", err, ErrClientInitialization) + return nil, fmt.Errorf("unable to login to AWS auth method: %s. %w", err, tokenstore.ErrClientInitialization) } if authInfo == nil { return nil, fmt.Errorf("no auth info was returned after login") @@ -120,7 +121,7 @@ func (imp *VaultStore) Value() (string, error) { secret, err := imp.getSecret(ctx, imp.strippedToken, imp.config.Version) if err != nil { - imp.logger.Error(implementationNetworkErr, imp.token.Prefix(), err, imp.token.String()) + imp.logger.Error(tokenstore.ImplementationNetworkErr, imp.token.Prefix(), err, imp.token.String()) return "", err } diff --git a/internal/store/hashivault_test.go b/tokenstore/provider/vault/impl/hashivault_test.go similarity index 84% rename from internal/store/hashivault_test.go rename to tokenstore/provider/vault/impl/hashivault_test.go index 8c9aed8..f4c437b 100644 --- a/internal/store/hashivault_test.go +++ b/tokenstore/provider/vault/impl/hashivault_test.go @@ -1,22 +1,25 @@ -package store_test +package impl_test import ( "context" "fmt" - "io" "net/http" "net/http/httptest" "os" "strings" "testing" - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - "github.com/DevLabFoundry/configmanager/v3/internal/store" - "github.com/DevLabFoundry/configmanager/v3/internal/testutils" + "github.com/DevLabFoundry/configmanager-plugin/vault/impl" + "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/hashicorp/go-hclog" vault "github.com/hashicorp/vault/api" ) +const ( + TestPhrase string = "got: %v want: %v\n" + TestPhraseWithContext string = "%s\n got: %v\n\n want: %v\n" +) + func TestMountPathExtract(t *testing.T) { ttests := map[string]struct { token func() *config.ParsedTokenConfig @@ -25,7 +28,7 @@ func TestMountPathExtract(t *testing.T) { "without leading slash": { func() *config.ParsedTokenConfig { // "VAULT://secret___/demo/configmanager" - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/demo/configmanager") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -34,7 +37,7 @@ func TestMountPathExtract(t *testing.T) { "with leading slash": { func() *config.ParsedTokenConfig { // "VAULT:///secret___/demo/configmanager", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("/secret___/demo/configmanager") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -42,7 +45,7 @@ func TestMountPathExtract(t *testing.T) { }, "secret"}, "with underscore in path name": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("_secret___/demo/configmanager") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -50,7 +53,7 @@ func TestMountPathExtract(t *testing.T) { }, "_secret"}, "with double underscore in path name": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("__secret___/demo/configmanager") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -58,7 +61,7 @@ func TestMountPathExtract(t *testing.T) { }, "__secret"}, "with multiple paths in mountpath": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret/bar/path___/demo/configmanager") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -67,7 +70,7 @@ func TestMountPathExtract(t *testing.T) { } for name, tt := range ttests { t.Run(name, func(t *testing.T) { - got := store.SplitHashiVaultToken(tt.token().StoreToken()) + got := impl.SplitHashiVaultToken(tt.token().StoreToken()) if got.Path != tt.expect { t.Errorf("got %q, expected %q", got, tt.expect) } @@ -98,7 +101,7 @@ func TestVaultScenarios(t *testing.T) { }{ "happy return": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/foo") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -128,7 +131,7 @@ func TestVaultScenarios(t *testing.T) { "incorrect json": { func() *config.ParsedTokenConfig { // "VAULT://secret___/foo", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/foo") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -157,7 +160,7 @@ func TestVaultScenarios(t *testing.T) { }, "another return": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret/engine1___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -188,7 +191,7 @@ func TestVaultScenarios(t *testing.T) { "not found": { func() *config.ParsedTokenConfig { // "VAULT://secret___/foo", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/foo") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -216,7 +219,7 @@ func TestVaultScenarios(t *testing.T) { "403": { func() *config.ParsedTokenConfig { // "VAULT://secret___/some/other/foo2", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -244,7 +247,7 @@ func TestVaultScenarios(t *testing.T) { "found but empty": { func() *config.ParsedTokenConfig { // "VAULT://secret___/some/other/foo2", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -273,7 +276,7 @@ func TestVaultScenarios(t *testing.T) { }, "found but nil returned": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -285,7 +288,7 @@ func TestVaultScenarios(t *testing.T) { mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { t.Helper() if secretPath != "some/other/foo2" { - t.Errorf(testutils.TestPhrase, secretPath, `some/other/foo2`) + t.Errorf(TestPhrase, secretPath, `some/other/foo2`) } return &vault.KVSecret{Data: nil}, nil } @@ -301,7 +304,7 @@ func TestVaultScenarios(t *testing.T) { "version provided correctly": { func() *config.ParsedTokenConfig { // "VAULT://secret___/some/other/foo2[version=1]", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("version=1") @@ -313,7 +316,7 @@ func TestVaultScenarios(t *testing.T) { mv.gv = func(ctx context.Context, secretPath string, version int) (*vault.KVSecret, error) { t.Helper() if secretPath != "some/other/foo2" { - t.Errorf(testutils.TestPhrase, secretPath, `some/other/foo2`) + t.Errorf(TestPhrase, secretPath, `some/other/foo2`) } m := make(map[string]interface{}) m["foo2"] = "dsfsdf3454456" @@ -331,7 +334,7 @@ func TestVaultScenarios(t *testing.T) { "version provided but unable to parse": { func() *config.ParsedTokenConfig { // "VAULT://secret___/some/other/foo2[version=1a]", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("version=1a") @@ -343,7 +346,7 @@ func TestVaultScenarios(t *testing.T) { mv.gv = func(ctx context.Context, secretPath string, version int) (*vault.KVSecret, error) { t.Helper() if secretPath != "some/other/foo2" { - t.Errorf(testutils.TestPhrase, secretPath, `some/other/foo2`) + t.Errorf(TestPhrase, secretPath, `some/other/foo2`) } return nil, nil } @@ -358,7 +361,7 @@ func TestVaultScenarios(t *testing.T) { }, "vault rate limit incorrect": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("") @@ -371,7 +374,7 @@ failed to initialize the client`, mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { t.Helper() if secretPath != "some/other/foo2" { - t.Errorf(testutils.TestPhrase, secretPath, `some/other/foo2`) + t.Errorf(TestPhrase, secretPath, `some/other/foo2`) } return &vault.KVSecret{Data: nil}, nil } @@ -392,7 +395,7 @@ failed to initialize the client`, tearDown := tt.setupEnv() defer tearDown() - impl, err := store.NewVaultStore(context.TODO(), tt.token(), log.New(io.Discard)) + i, err := impl.NewVaultStore(context.TODO(), tt.token(), hclog.NewNullLogger()) if err != nil { if err.Error() != tt.expect { t.Fatalf("failed to init hashivault, %v", err.Error()) @@ -400,16 +403,16 @@ failed to initialize the client`, return } - impl.WithSvc(tt.mockClient(t)) - got, err := impl.Value() + i.WithSvc(tt.mockClient(t)) + got, err := i.Value() if err != nil { if err.Error() != tt.expect { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) + t.Errorf(TestPhrase, err.Error(), tt.expect) } return } if got != tt.expect { - t.Errorf(testutils.TestPhrase, got, tt.expect) + t.Errorf(TestPhrase, got, tt.expect) } }) } @@ -425,7 +428,7 @@ func TestAwsIamAuth(t *testing.T) { }{ "aws_iam auth no role specified": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("version=1") @@ -437,7 +440,7 @@ func TestAwsIamAuth(t *testing.T) { mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { t.Helper() if secretPath != "some/other/foo2" { - t.Errorf(testutils.TestPhrase, secretPath, `some/other/foo2`) + t.Errorf(TestPhrase, secretPath, `some/other/foo2`) } return &vault.KVSecret{Data: nil}, nil } @@ -459,7 +462,7 @@ func TestAwsIamAuth(t *testing.T) { }, "aws_iam auth incorrectly formatted request": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("version=1,iam_role=not_a_role") @@ -476,7 +479,7 @@ incorrect values supplied. failed to initialize the client`, mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { t.Helper() if secretPath != "some/other/foo2" { - t.Errorf(testutils.TestPhrase, secretPath, `some/other/foo2`) + t.Errorf(TestPhrase, secretPath, `some/other/foo2`) } return &vault.KVSecret{Data: nil}, nil } @@ -506,7 +509,7 @@ incorrect values supplied. failed to initialize the client`, }, "aws_iam auth success": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("iam_role=arn:aws:iam::1111111:role/i-orchestration") @@ -519,7 +522,7 @@ incorrect values supplied. failed to initialize the client`, mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { t.Helper() if secretPath != "some/other/foo2" { - t.Errorf(testutils.TestPhrase, secretPath, `some/other/foo2`) + t.Errorf(TestPhrase, secretPath, `some/other/foo2`) } m := make(map[string]any) m["foo2"] = "dsfsdf3454456" @@ -550,7 +553,7 @@ incorrect values supplied. failed to initialize the client`, }, "aws_iam auth no token returned": { func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn, _ := config.NewParsedToken(config.HashicorpVaultPrefix, *config.NewConfig()) tkn.WithSanitizedToken("secret___/some/other/foo2") tkn.WithKeyPath("") tkn.WithMetadata("iam_role=arn:aws:iam::1111111:role/i-orchestration") @@ -562,7 +565,7 @@ incorrect values supplied. failed to initialize the client`, mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { t.Helper() if secretPath != "some/other/foo2" { - t.Errorf(testutils.TestPhrase, secretPath, `some/other/foo2`) + t.Errorf(TestPhrase, secretPath, `some/other/foo2`) } m := make(map[string]interface{}) m["foo2"] = "dsfsdf3454456" @@ -598,26 +601,26 @@ incorrect values supplied. failed to initialize the client`, ts := httptest.NewServer(tt.mockHanlder(t)) tearDown := tt.setupEnv(ts.URL) defer tearDown() - impl, err := store.NewVaultStore(context.TODO(), tt.token(), log.New(io.Discard)) + i, err := impl.NewVaultStore(context.TODO(), tt.token(), hclog.NewNullLogger()) if err != nil { // WHAT A CRAP way to do this... if err.Error() != strings.Split(fmt.Sprintf(tt.expect, ts.URL), `%!`)[0] { - t.Errorf(testutils.TestPhraseWithContext, "aws iam auth", err.Error(), strings.Split(fmt.Sprintf(tt.expect, ts.URL), `%!`)[0]) + t.Errorf(TestPhraseWithContext, "aws iam auth", err.Error(), strings.Split(fmt.Sprintf(tt.expect, ts.URL), `%!`)[0]) t.Fatalf("failed to init hashivault, %v", err.Error()) } return } - impl.WithSvc(tt.mockClient(t)) - got, err := impl.Value() + i.WithSvc(tt.mockClient(t)) + got, err := i.Value() if err != nil { if err.Error() != tt.expect { - t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) + t.Errorf(TestPhrase, err.Error(), tt.expect) } return } if got != tt.expect { - t.Errorf(testutils.TestPhrase, got, tt.expect) + t.Errorf(TestPhrase, got, tt.expect) } }) } diff --git a/tokenstore/provider/vault/main.go b/tokenstore/provider/vault/main.go new file mode 100644 index 0000000..cdf20e4 --- /dev/null +++ b/tokenstore/provider/vault/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "github.com/DevLabFoundry/configmanager/v3/tokenstore" + "github.com/hashicorp/go-plugin" +) + +type TokenStorePlugin struct{} + +func (ts TokenStorePlugin) Value(key string, metadata []byte) (string, error) { + // srv, err := impl.NewParamStore(context.Background(), log.New(os.Stderr)) + // if err != nil { + // return "", err + // } + // return srv.Value(key, metadata) + return "", nil +} + +func main() { + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: tokenstore.Handshake, + Plugins: map[string]plugin.Plugin{ + "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: &TokenStorePlugin{}}, + }, + // A non-nil value here enables gRPC serving for this plugin... + GRPCServer: plugin.DefaultGRPCServer, + }) +} diff --git a/tokenstore/scaffolding.go b/tokenstore/scaffolding.go new file mode 100644 index 0000000..72ee236 --- /dev/null +++ b/tokenstore/scaffolding.go @@ -0,0 +1,24 @@ +package tokenstore + +import "errors" + +// Error codes shared with the host. +const ( + OK int32 = 0 + ERR_BUF_TOO_SMALL int32 = -1 + ERR_INVALID_UTF8 int32 = -2 + ERR_EMPTY_INPUT int32 = -3 + ERR_INTERNAL int32 = -4 + ERR_FAILED_UNMARSHAL_MESSAGE int32 = -5 + ERR_INIT_STORE int32 = -6 + ERR_FAILED_VALUE_RETRIEVAL int32 = -7 +) + +const ImplementationNetworkErr string = "implementation %s error: %v for token: %s" + +var ( + ErrRetrieveFailed = errors.New("failed to retrieve config item") + ErrClientInitialization = errors.New("failed to initialize the client") + ErrEmptyResponse = errors.New("value retrieved but empty for token") + ErrServiceCallFailed = errors.New("failed to complete the service call") +)