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..557ea84 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" ) @@ -73,7 +73,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/internal/config/config.go b/config/config.go similarity index 85% rename from internal/config/config.go rename to config/config.go index d1d05ed..aeec5ff 100644 --- a/internal/config/config.go +++ b/config/config.go @@ -9,6 +9,10 @@ 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" ) const ( @@ -41,14 +45,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 +54,7 @@ type GenVarsConfig struct { tokenSeparator string keySeparator string enableEnvSubst bool + enableLaxMode bool // parseAdditionalVars func(token string) TokenConfigVars } @@ -91,12 +88,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 +120,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 +154,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 +181,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 +241,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..ff6c3ea 100644 --- a/configmanager.go +++ b/configmanager.go @@ -9,8 +9,8 @@ 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" ) @@ -46,7 +46,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/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..f489682 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -10,45 +10,54 @@ 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 +} + +// 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 +67,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,14 +84,14 @@ 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 } @@ -90,19 +99,27 @@ func (c *GenVars) Config() *config.GenVarsConfig { // 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) + } + // pass in default initialised retrieveStrategy // input should be rt, err := c.generate(ntm) if err != nil { return nil, err } + return rt, nil } @@ -112,7 +129,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 +152,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 +160,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 +178,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 +194,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 +202,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 +223,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 +234,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 +247,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..86aa3ea 100644 --- a/generator/generator_test.go +++ b/generator/generator_test.go @@ -1,43 +1,43 @@ 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 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 +48,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 +171,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 +214,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 +303,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 +315,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..dff52b3 100644 --- a/go.mod +++ b/go.mod @@ -1,98 +1,39 @@ 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/oklog/run v1.2.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect + github.com/stretchr/testify v1.11.1 // 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/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..eeb042f 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,61 @@ 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/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/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/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/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..2c87cc0 --- /dev/null +++ b/internal/store/plugin.go @@ -0,0 +1,70 @@ +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 +} 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..e834880 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -1,27 +1,127 @@ package store import ( + "context" "errors" + "fmt" + "os" + "path" + "runtime" + "strings" + "sync" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" ) -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") + ErrPluginNotFound = errors.New("plugin does not exist") ) -// Strategy iface that all store implementations -// must conform to, in order to be be used by the retrieval implementation +// 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 ( + loc string = ".configmanager/plugins" + namePattern string = "%s-%s-%s" +) + +type osOps struct { + UserHomeDir func() (string, error) + Getwd func() (dir string, err error) +} + +type Store struct { + plugin pluginMap + osOps osOps +} + +func New(ctx context.Context) *Store { + pm := pluginMap{mu: &sync.Mutex{}, m: make(map[string]*Plugin)} + s := &Store{ + plugin: pm, + osOps: osOps{UserHomeDir: os.UserHomeDir, Getwd: os.Getwd}, + } + return s +} + +// Init ensures all the discovered tokens have their implementations initialised +func (s *Store) Init(ctx context.Context, implt []string) error { + + for _, plugin := range implt { + plpath, err := s.findPlugin(plugin) + if err != nil { + return err + } + 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 "", ErrPluginNotFound + } + 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 // -// 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) +// current dir +// home dir +func (s *Store) findPlugin(plugin string) (string, error) { + // fallback locations + // current dir + cwd, err := os.Getwd() + if err != nil { + return "", err + } + hd, err := os.UserHomeDir() + if err != nil { + return "", err + } + + fallbackPath := []string{cwd, hd} + if val, exists := os.LookupEnv(config.CONFIGMANAGER_DIR); exists { + fallbackPath = append([]string{val}, fallbackPath...) + } + for _, p := range fallbackPath { + ff := path.Join(p, loc, plugin, fmt.Sprintf(namePattern, plugin, runtime.GOOS, runtime.GOARCH)) + if _, err := os.Stat(ff); err == nil { + // break on first non nil error + return ff, nil + } + } + return "", fmt.Errorf("configmanager provider: ( %s ) %w", plugin, ErrPluginNotFound) } 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..5ed18b5 --- /dev/null +++ b/tokenstore/README.md @@ -0,0 +1,240 @@ +# 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/). + + +## Plugin Architecture + + +```mermaid +``` + +The plugins will need to be downloaded into any one of these locations on disk, they will be checked in this order + +- currentDirectory (directory from which the configmanager executable is run) +- 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` + + + +## 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` 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..faaa4c5 --- /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: "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..a01ecda --- /dev/null +++ b/tokenstore/provider/awsparamstr/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "context" + + "github.com/DevLabFoundry/configmanager-plugin/awsparamstr/impl" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" +) + +type TokenStorePlugin struct { + log hclog.Logger +} + +func (ts TokenStorePlugin) Value(key string, metadata []byte) (string, error) { + srv, err := impl.NewParamStore(context.Background(), ts.log) + if err != nil { + return "", err + } + return srv.Value(key, metadata) +} + +func main() { + log := hclog.New(hclog.DefaultOptions) + log.SetLevel(hclog.LevelFromString("error")) + + // if os.Getenv("CONFIGMANAGER_LOG") + ts := TokenStorePlugin{log: log} + plugin.Serve(&plugin.ServeConfig{ + // Logger: , + HandshakeConfig: tokenstore.Handshake, + Plugins: map[string]plugin.Plugin{ + "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") +)