From 258473228356477d8d75d0cdbd67afad1665a951 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Mon, 24 Nov 2025 17:44:52 +0000 Subject: [PATCH 01/20] fix: experiment with WASI NOTE: it is a no go - but still valuable insight into sandboxing by the runtime --- cmd/configmanager/configmanager.go | 2 +- configmanager.go | 2 +- generator/generator.go | 32 ++-- generator/generator_test.go | 14 +- generator/generatorvars.go | 20 -- go.mod | 57 +++--- go.sum | 60 ++++++ internal/config/config.go | 47 ++++- internal/plugin/plugin.go | 16 ++ internal/plugin/tester/main.go | 46 +++++ internal/plugin/wasip1.go | 263 +++++++++++++++++++++++++++ internal/plugin/wasip1_test.go | 47 +++++ plugins/awsparams/main.go | 203 +++++++++++++++++++++ plugins/awsparams/paramstore.go | 67 +++++++ plugins/awsparams/paramstore_test.go | 152 ++++++++++++++++ plugins/awssecrets/main.go | 197 ++++++++++++++++++++ plugins/scaffolding.go | 30 +++ 17 files changed, 1173 insertions(+), 82 deletions(-) create mode 100644 internal/plugin/plugin.go create mode 100644 internal/plugin/tester/main.go create mode 100644 internal/plugin/wasip1.go create mode 100644 internal/plugin/wasip1_test.go create mode 100644 plugins/awsparams/main.go create mode 100644 plugins/awsparams/paramstore.go create mode 100644 plugins/awsparams/paramstore_test.go create mode 100644 plugins/awssecrets/main.go create mode 100644 plugins/scaffolding.go diff --git a/cmd/configmanager/configmanager.go b/cmd/configmanager/configmanager.go index 1eed6cb..383d229 100644 --- a/cmd/configmanager/configmanager.go +++ b/cmd/configmanager/configmanager.go @@ -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/configmanager.go b/configmanager.go index f3f6134..4da0d1b 100644 --- a/configmanager.go +++ b/configmanager.go @@ -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/generator/generator.go b/generator/generator.go index 237c6a6..8dcef19 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -17,32 +17,32 @@ import ( "github.com/DevLabFoundry/configmanager/v3/internal/strategy" ) -// GenVars is the main struct holding the +// 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 { +type Generator struct { Logger log.ILogger strategy strategy.StrategyFuncMap 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 @@ -61,13 +61,13 @@ func newGenVars(ctx context.Context, opts ...Opts) *GenVars { // WithStrategyMap // // Adds addtional funcs for storageRetrieval used for testing only -func (c *GenVars) WithStrategyMap(sm strategy.StrategyFuncMap) *GenVars { +func (c *Generator) WithStrategyMap(sm strategy.StrategyFuncMap) *Generator { c.strategy = 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 @@ -76,13 +76,13 @@ func (c *GenVars) WithConfig(cfg *config.GenVarsConfig) *GenVars { } // WithContext uses caller passed context -func (c *GenVars) WithContext(ctx context.Context) *GenVars { +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,7 +90,7 @@ 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 { @@ -112,7 +112,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() @@ -144,7 +144,7 @@ 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) { +func (c *Generator) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { if len(ntm.normalizedTokenMap) < 1 { c.Logger.Debug("no replaceable tokens found in input") return nil, nil @@ -232,7 +232,7 @@ func (n NormalizedTokenSafe) GetMap() map[string]*NormalizedToken { return n.normalizedTokenMap } -func (c *GenVars) NormalizeRawToken(rtm *RawTokenConfig) NormalizedTokenSafe { +func (c *Generator) NormalizeRawToken(rtm *RawTokenConfig) NormalizedTokenSafe { ntm := NormalizedTokenSafe{mu: &sync.Mutex{}, normalizedTokenMap: make(map[string]*NormalizedToken)} for _, r := range rtm.RawTokenMap() { diff --git a/generator/generator_test.go b/generator/generator_test.go index e48e546..d391d86 100644 --- a/generator/generator_test.go +++ b/generator/generator_test.go @@ -34,7 +34,7 @@ func TestGenerate(t *testing.T) { return m, nil } - g := generator.NewGenerator(context.TODO(), func(gv *generator.GenVars) { + g := generator.New(context.TODO(), func(gv *generator.Generator) { gv.Logger = log.New(&bytes.Buffer{}) }) g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) @@ -54,7 +54,7 @@ func TestGenerate(t *testing.T) { return m, nil } - g := generator.NewGenerator(context.TODO()) + g := generator.New(context.TODO()) g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) @@ -72,7 +72,7 @@ func TestGenerate(t *testing.T) { return m, nil } - g := generator.NewGenerator(context.TODO()) + g := generator.New(context.TODO()) g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token|key1.key2"}) @@ -129,7 +129,7 @@ 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 := generator.New(context.TODO()) g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: tt.custFunc}) got, err := g.Generate([]string{tt.token}) @@ -175,7 +175,7 @@ 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()) + g := generator.New(context.TODO()) input := `GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj GCPSECRETS:///djsfsdkjvfjkhfdvibdfinjdsfnjvdsflj|a @@ -298,7 +298,7 @@ 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()) + g := generator.New(context.TODO()) g.Config().WithTokenSeparator(tt.separator) gdt, err := g.DiscoverTokens(tt.input) if err != nil { @@ -319,7 +319,7 @@ func Test_ConfigManager_DiscoverTokens(t *testing.T) { } func Test_Generate_EnsureRaceFree(t *testing.T) { - g := generator.NewGenerator(context.TODO()) + g := generator.New(context.TODO()) input := ` fg diff --git a/generator/generatorvars.go b/generator/generatorvars.go index 79a56ae..d2ac986 100644 --- a/generator/generatorvars.go +++ b/generator/generatorvars.go @@ -43,26 +43,6 @@ 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) -// } - // keySeparatorLookup checks if the key contains // keySeparator character // If it does contain one then it tries to parse diff --git a/go.mod b/go.mod index 1d37d5a..bf22cb0 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,18 @@ module github.com/DevLabFoundry/configmanager/v3 -go 1.25.3 +go 1.25.4 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/azcore v1.20.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 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/data/aztables v1.4.1 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/aws/aws-sdk-go-v2 v1.40.0 + github.com/aws/aws-sdk-go-v2/config v1.32.0 + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1 + github.com/aws/aws-sdk-go-v2/service/ssm v1.67.3 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 @@ -22,6 +22,8 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) +require github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect + require ( cloud.google.com/go/auth v0.17.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect @@ -32,16 +34,16 @@ require ( github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect 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/credentials v1.19.0 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // 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/aws-sdk-go-v2/service/internal/presigned-url v1.13.14 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.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 @@ -76,23 +78,24 @@ require ( 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 + github.com/tetratelabs/wazero v1.10.1 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/crypto v0.45.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.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/api v0.256.0 // indirect + google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect + google.golang.org/grpc v1.77.0 // indirect google.golang.org/protobuf v1.36.10 // indirect ) diff --git a/go.sum b/go.sum index f40c7f4..a8b3951 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,7 @@ 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 v1.49.1 h1:KYKIG0+pfpAWaAYayFkE/KPrAVCge0Hu82bPraAmsCk= 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= @@ -12,14 +13,20 @@ cloud.google.com/go/secretmanager v1.16.0 h1:19QT7ZsLJ8FSP1k+4esQvuCD7npMJml6hYz 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/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/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 v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= 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/data/aztables v1.4.1 h1:j0hhYS006eJ54vusoap0f2NVZ1YY3QnaAEnLM68f0SQ= +github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.4.1/go.mod h1:AdtInaXmK8eYmbjezRWgLz+Qs46nc9Up9GWGwteWNfw= 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= @@ -37,46 +44,75 @@ 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 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= +github.com/aws/aws-sdk-go-v2 v1.40.0/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/config v1.32.0 h1:T5WWJYnam9SzBLbsVYDu2HscLDe+GU1AUJtfcDAc/vA= +github.com/aws/aws-sdk-go-v2/config v1.32.0/go.mod h1:pSRm/+D3TxBixGMXlgtX4+MPO9VNtEEtiFmNpxksoxw= 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/credentials v1.19.0 h1:7zm+ez+qEqLaNsCSRaistkvJRJv8sByDOVuCnyHbP7M= +github.com/aws/aws-sdk-go-v2/credentials v1.19.0/go.mod h1:pHKPblrT7hqFGkNLxqoS3FlGoPrQg4hMIa+4asZzBfs= 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/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= 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/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= 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/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= 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/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= 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/secretsmanager v1.40.1 h1:w6a0H79HrHf3lr+zrw+pSzR5B+caiQFAKiNHlrUcnoc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1/go.mod h1:c6Vg0BRiU7v0MVhHupw90RyL120QBwAMLbDCzptGeMk= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= 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/ssm v1.67.3 h1:ofiQvKwka2E3T8FXBsU1iWj7Yvk2wd1p4ZCdS6qGiKQ= +github.com/aws/aws-sdk-go-v2/service/ssm v1.67.3/go.mod h1:+nlWvcgDPQ56mChEBzTC0puAMck+4onOFaHg5cE+Lgg= 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/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= 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/ssooidc v1.35.8 h1:MvlNs/f+9eM0mOjD9JzBUbf5jghyTk3p+O9yHMXX94Y= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= 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/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= 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/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= 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 v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= 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/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= 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= @@ -200,6 +236,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO 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= +github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8= +github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU= 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= @@ -219,13 +257,21 @@ go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42s 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/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= 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/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.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/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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= @@ -236,23 +282,37 @@ 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/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.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/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 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/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= +google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= 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 v0.0.0-20251111163417-95abcf5c77ba h1:Ze6qXW0j37YCqZdCD2LkzVSxgEWez0cO4NUyd44DiDY= +google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:4FLPzLA8eGAktPOTemJGDgDYRpLYwrNu4u2JtWINhnI= 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/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= +google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= 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/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/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/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/config/config.go b/internal/config/config.go index d1d05ed..a1898fc 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,6 +5,8 @@ import ( "errors" "fmt" "strings" + + "github.com/DevLabFoundry/configmanager/v3/plugins" ) const ( @@ -171,6 +173,18 @@ func (ptc *ParsedTokenConfig) WithSanitizedToken(v string) { } func (t *ParsedTokenConfig) ParseMetadata(metadataTyp any) error { + // 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, 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 + return err + } + return nil +} + +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 @@ -182,16 +196,7 @@ func (t *ParsedTokenConfig) ParseMetadata(metadataTyp any) error { 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 { - // It would very hard to test this since - // we are forcing the key and value to be strings - // return non-filled pointer - return err - } - return nil + return fmt.Sprintf(`{%s}`, strings.Join(metaMap, ",")) } // StoreToken returns the sanitized token without: @@ -243,3 +248,25 @@ func (t *ParsedTokenConfig) Prefix() ImplementationPrefix { func (t *ParsedTokenConfig) TokenSeparator() string { return t.tokenSeparator } + +func (t *ParsedTokenConfig) JSONMessagExchange() (*plugins.MessagExchange, error) { + md := map[string]any{} + if err := json.Unmarshal([]byte(t.parseMetadata()), &md); err != nil { + return nil, err + } + + jme := &plugins.MessagExchange{ + Token: t.StoreToken(), + Metadata: md, + } + + return jme, nil +} + +func (t *ParsedTokenConfig) JSONMessagExchangeBytes() ([]byte, error) { + j, err := t.JSONMessagExchange() + if err != nil { + return nil, err + } + return json.Marshal(j) +} diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go new file mode 100644 index 0000000..5eb80c7 --- /dev/null +++ b/internal/plugin/plugin.go @@ -0,0 +1,16 @@ +package plugin + +import "github.com/DevLabFoundry/configmanager/v3/internal/config" + +// Plugin is responsible for managing plugins within configmanager +// +// It includes the following methods +// - fetch plugins from known sources +// - maintains a list of tokens answerable by a specified pluginEngine +type Plugin struct { + Implementations config.ImplementationPrefix + SourcePath string + Version string + fallbackPaths []string + engineInstance *Engine +} diff --git a/internal/plugin/tester/main.go b/internal/plugin/tester/main.go new file mode 100644 index 0000000..b17e481 --- /dev/null +++ b/internal/plugin/tester/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/internal/plugin" +) + +func main() { + inputReader, err := os.Open("/Users/dusannitschneider/git/dnitsch/configmanager/plugins/awsparams/awsparams.wasm") + if err != nil { + log.Fatal(fmt.Errorf("open plugin.wasm: %w", err)) + } + + ctx := context.Background() + + // Load the compiled WASI plugin. + engine, err := plugin.NewEngine(ctx, inputReader) + if err != nil { + log.Fatal(err) + } + defer engine.Close(ctx) + + inst, err := engine.NewApiInstance(ctx) + if err != nil { + log.Fatal(err) + } + defer inst.Close(ctx) + + // os.Setenv("AWS_PROFILE", "anabode_terraform_dev") + // os.Setenv("AWS_REGION", "eu-west-1") + t1, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + t1.WithSanitizedToken("/int-test/pocketbase/admin-pwd") + val, err := inst.TokenValue(ctx, t1) + if err != nil { + log.Fatal(err) + } + + fmt.Println(string(val)) + + os.Exit(0) +} diff --git a/internal/plugin/wasip1.go b/internal/plugin/wasip1.go new file mode 100644 index 0000000..268371f --- /dev/null +++ b/internal/plugin/wasip1.go @@ -0,0 +1,263 @@ +// Package plugin +// provides reactor style module +// we can explore the plugin provided host module +package plugin + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "io" + + "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/plugins" + "github.com/tetratelabs/wazero" + "github.com/tetratelabs/wazero/api" + "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" +) + +var ( + ErrMissingMethod = errors.New("missing method on the wasiLib instance") + ErrAllocMemForParam = errors.New("failed to allocate memory for property") + ErrAllocateOutPtrZeroLen = errors.New("allocate returned 0 for output pointer") + ErrMemoryReadFailed = errors.New("mem.Read(out) failed") + ErrEmptyToken = errors.New("token must not be empty") +) + +// ==================== +// Engine & ApiInstance +// ==================== + +type Engine struct { + r wazero.Runtime + compiledModule wazero.CompiledModule +} + +// NewEngine compiles the WASI module once for the lifetime of the program. +func NewEngine(ctx context.Context, ps io.ReadCloser) (*Engine, error) { + r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig()) + if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil { + _ = r.Close(ctx) + return nil, fmt.Errorf("instantiate WASI: %w", err) + } + + defer ps.Close() + wasiLib, err := io.ReadAll(ps) + if err != nil { + _ = r.Close(ctx) + return nil, fmt.Errorf("read plugin: %w", err) + } + + cm, err := r.CompileModule(ctx, wasiLib) + if err != nil { + _ = r.Close(ctx) + return nil, fmt.Errorf("compile module: %w", err) + } + + return &Engine{ + r: r, + compiledModule: cm, + }, nil +} + +// Close shuts down the runtime. +func (e *Engine) Close(ctx context.Context) error { + return e.r.Close(ctx) +} + +type ApiInstance struct { + mod api.Module + mem api.Memory + // exported alloc helpers + allocate api.Function + deallocate api.Function + // exported business function + tokenValue api.Function + // scratch output buffers + outPtr uint32 + outCap uint32 + outLenPtr uint32 // 4-byte cell for required length +} + +// NewApiInstance instantiates a fresh module instance. +func (e *Engine) NewApiInstance(ctx context.Context) (*ApiInstance, error) { + mod, err := e.r.InstantiateModule(ctx, e.compiledModule, wazero.NewModuleConfig().WithStartFunctions("_initialize")) + if err != nil { + return nil, fmt.Errorf("instantiate module: %w", err) + } + + inst := &ApiInstance{ + mod: mod, + mem: mod.Memory(), + allocate: mod.ExportedFunction("allocate"), + deallocate: mod.ExportedFunction("deallocate"), + tokenValue: mod.ExportedFunction("strategy_token_value"), + } + + for name, exported := range map[string]api.Function{ + "allocate": inst.allocate, + "deallocate": inst.deallocate, + "strategy_token_value": inst.tokenValue, + } { + if exported == nil { + return nil, fmt.Errorf("%w, method %q not found on exports", ErrMissingMethod, name) + } + } + + return inst, nil +} + +// Close instance (optional). +func (i *ApiInstance) Close(ctx context.Context) { + i.freeScratch(ctx) + _ = i.mod.Close(ctx) +} + +// put allocates module memory and writes bytes into it. +// returns (ptr, size). caller must deallocate(ptr, size). +func (i *ApiInstance) put(ctx context.Context, b []byte) (uint32, uint32, error) { + if len(b) == 0 { + return 0, 0, ErrEmptyToken + } + + res, err := i.allocate.Call(ctx, uint64(len(b))) + if err != nil { + return 0, 0, fmt.Errorf("allocate: %w", err) + } + ptr := uint32(res[0]) + if ptr == 0 { + return 0, 0, fmt.Errorf("allocate returned 0: %w", ErrAllocMemForParam) + } + + if ok := i.mem.Write(ptr, b); !ok { + _, _ = i.deallocate.Call(ctx, uint64(ptr), uint64(len(b))) + return 0, 0, fmt.Errorf("mem.Write failed: %w", ErrAllocMemForParam) + } + + return ptr, uint32(len(b)), nil +} + +// ensureOut ensures the scratch output buffer has at least `need` bytes. +// allocates outLenPtr (4 bytes) once. +func (i *ApiInstance) ensureOut(ctx context.Context, need uint32) error { + // outLenPtr is a 4-byte cell for required length + if i.outLenPtr == 0 { + res, err := i.allocate.Call(ctx, 4) + if err != nil { + return fmt.Errorf("allocate outLenPtr: %w", err) + } + i.outLenPtr = uint32(res[0]) + if i.outLenPtr == 0 { + return ErrAllocateOutPtrZeroLen + } + } + + if need <= i.outCap { + return nil + } + + // grow if needed - free old and alloc new + if i.outPtr != 0 { + _, _ = i.deallocate.Call(ctx, uint64(i.outPtr), uint64(i.outCap)) + i.outPtr, i.outCap = 0, 0 + } + + res, err := i.allocate.Call(ctx, uint64(need)) + if err != nil { + return fmt.Errorf("allocate outPtr: %w", err) + } + i.outPtr, i.outCap = uint32(res[0]), need + if i.outPtr == 0 { + return ErrAllocateOutPtrZeroLen + } + return nil +} + +// freeScratch frees the reusable output buffers (call once per instance). +func (i *ApiInstance) freeScratch(ctx context.Context) { + if i.outPtr != 0 { + _, _ = i.deallocate.Call(ctx, uint64(i.outPtr), uint64(i.outCap)) + i.outPtr, i.outCap = 0, 0 + } + if i.outLenPtr != 0 { + _, _ = i.deallocate.Call(ctx, uint64(i.outLenPtr), 4) + i.outLenPtr = 0 + } +} + +// TokenValue is the nice host-side API: string in, []byte out. +func (i *ApiInstance) TokenValue(ctx context.Context, token *config.ParsedTokenConfig) ([]byte, error) { + if token.StoreToken() == "" { + return nil, ErrEmptyToken + } + tokenBytes, err := token.JSONMessagExchangeBytes() + tokenPtr, tokenLen, err := i.put(ctx, tokenBytes) + if err != nil { + return nil, fmt.Errorf("put input: %w", err) + } + defer i.deallocate.Call(ctx, uint64(tokenPtr), uint64(tokenLen)) + + // start with a smallish buffer; plugin will ask for more if needed + if err := i.ensureOut(ctx, 64); err != nil { + return nil, fmt.Errorf("ensureOut: %w", err) + } + + call := func() (int32, uint32, error) { + res, err := i.tokenValue.Call( + ctx, + uint64(tokenPtr), uint64(tokenLen), // sanitizedToken + uint64(i.outPtr), uint64(i.outCap), // outPtr, outCap + uint64(i.outLenPtr), // outLenPtr + ) + if err != nil { + return 0, 0, fmt.Errorf("call strategy_token_value: %w", err) + } + + lenBytes, ok := i.mem.Read(i.outLenPtr, 4) + if !ok { + return int32(res[0]), 0, ErrMemoryReadFailed + } + + need := binary.LittleEndian.Uint32(lenBytes) + return int32(res[0]), need, nil + } + + rc, need, err := call() + if err != nil { + return nil, err + } + + if rc == plugins.ERR_BUF_TOO_SMALL { + if err := i.ensureOut(ctx, need); err != nil { + return nil, fmt.Errorf("ensureOut resize: %w", err) + } + rc, need, err = call() + if err != nil { + return nil, err + } + } + + if rc != plugins.OK { + switch rc { + case plugins.ERR_INVALID_UTF8: + return nil, errors.New("token value: invalid UTF-8 in input") + case plugins.ERR_EMPTY_INPUT: + return nil, ErrEmptyToken + case plugins.ERR_BUF_TOO_SMALL: + return nil, fmt.Errorf("token value: buffer too small even after resize (need=%d)", need) + default: + return nil, fmt.Errorf("token value: unknown error code %d", rc) + } + } + + out, ok := i.mem.Read(i.outPtr, need) + if !ok { + return nil, ErrMemoryReadFailed + } + + // Detach from wasm memory. + result := make([]byte, need) + copy(result, out) + return result, nil +} diff --git a/internal/plugin/wasip1_test.go b/internal/plugin/wasip1_test.go new file mode 100644 index 0000000..5442eb1 --- /dev/null +++ b/internal/plugin/wasip1_test.go @@ -0,0 +1,47 @@ +package plugin_test + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/internal/plugin" +) + +func Test_FullFlow(t *testing.T) { + inputReader, err := os.Open("/Users/dusannitschneider/git/dnitsch/configmanager/plugins/awsparams/awsparams.wasm") + if err != nil { + t.Fatal(fmt.Errorf("open plugin.wasm: %w", err)) + } + ctx := context.Background() + + // Load the compiled WASI plugin. + engine, err := plugin.NewEngine(ctx, inputReader) + if err != nil { + t.Fatal(err) + } + defer engine.Close(ctx) + + inst, err := engine.NewApiInstance(ctx) + if err != nil { + t.Fatal(err) + } + defer inst.Close(ctx) + + os.Setenv("AWS_PROFILE", "anabode_terraform_dev") + os.Setenv("AWS_REGION", "eu-west-1") + t1, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + t1.WithSanitizedToken("/int-test/pocketbase/admin-pwd") + val, err := inst.TokenValue(ctx, t1) + if err != nil { + t.Fatal(err) + } + fmt.Printf("TokenValue(\"foo\") => %q\n", string(val)) + + // Zero-length test (should error) + t2, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + _, err = inst.TokenValue(ctx, t2) + fmt.Printf("TokenValue(\"\") error: %v\n", err) +} diff --git a/plugins/awsparams/main.go b/plugins/awsparams/main.go new file mode 100644 index 0000000..4bd4ec6 --- /dev/null +++ b/plugins/awsparams/main.go @@ -0,0 +1,203 @@ +package main + +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() {} + +// GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o awsparams.wasm diff --git a/plugins/awsparams/paramstore.go b/plugins/awsparams/paramstore.go new file mode 100644 index 0000000..9001e53 --- /dev/null +++ b/plugins/awsparams/paramstore.go @@ -0,0 +1,67 @@ +package main + +import ( + "context" + + "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/plugins" + "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" +) + +type paramStoreApi interface { + GetParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) +} + +type ParamStore struct { + svc paramStoreApi + ctx context.Context + config *ParamStrConfig + token *config.ParsedTokenConfig +} + +type ParamStrConfig struct { + // reserved for potential future use +} + +func NewParamStore(ctx context.Context) (*ParamStore, error) { + cfg, err := awsConf.LoadDefaultConfig(ctx) + if err != nil { + return nil, err + } + c := ssm.NewFromConfig(cfg) + + return &ParamStore{ + svc: c, + ctx: ctx, + }, nil +} + +func (s *ParamStore) WithSvc(svc paramStoreApi) { + s.svc = svc +} + +func (imp *ParamStore) Value(token *plugins.MessagExchange) (string, error) { + // imp.logger.Info("%s", "Concrete implementation ParameterStore") + // imp.logger.Info("ParamStore Token: %s", token.Token) + + input := &ssm.GetParameterInput{ + Name: aws.String(token.Token), + WithDecryption: aws.Bool(true), + } + ctx, cancel := context.WithCancel(imp.ctx) + defer cancel() + + result, err := imp.svc.GetParameter(ctx, input) + if err != nil { + // imp.logger.Error(plugins.ImplementationNetworkErr, config.ParamStorePrefix, err, token) + return "", err + } + + if result.Parameter.Value != nil { + return *result.Parameter.Value, nil + } + // imp.logger.Error("value retrieved but empty for token: %v", imp.token) + return "", nil +} diff --git a/plugins/awsparams/paramstore_test.go b/plugins/awsparams/paramstore_test.go new file mode 100644 index 0000000..84a0d23 --- /dev/null +++ b/plugins/awsparams/paramstore_test.go @@ -0,0 +1,152 @@ +package main_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/ssm" + "github.com/aws/aws-sdk-go-v2/service/ssm/types" +) + +type mockParamApi func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) + +func (m mockParamApi) GetParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { + return m(ctx, params, optFns...) +} + +func awsParamtStoreCommonGetChecker(t *testing.T, params *ssm.GetParameterInput) { + if params.Name == nil { + t.Fatal("expect name to not be nil") + } + + if strings.Contains(*params.Name, "#") { + t.Errorf("incorrectly stripped token separator") + } + + if strings.Contains(*params.Name, string(config.ParamStorePrefix)) { + t.Errorf("incorrectly stripped prefix") + } + + if !*params.WithDecryption { + t.Fatal("expect WithDecryption to not be false") + } +} + +func Test_GetParamStore(t *testing.T) { + var ( + tsuccessParam = "someVal" + // tsuccessObj map[string]string = map[string]string{"AWSPARAMSTR#/token/1": "someVal"} + ) + tests := map[string]struct { + token func() *config.ParsedTokenConfig + expect string + mockClient func(t *testing.T) mockParamApi + }{ + "successVal": { + func() *config.ParsedTokenConfig { + // "VAULT://secret___/demo/configmanager" + tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + tkn.WithSanitizedToken("/token/1") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + tsuccessParam, func(t *testing.T) mockParamApi { + return mockParamApi(func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { + t.Helper() + awsParamtStoreCommonGetChecker(t, params) + return &ssm.GetParameterOutput{ + Parameter: &types.Parameter{Value: &tsuccessParam}, + }, nil + }) + }, + }, + "successVal with keyseparator": { + func() *config.ParsedTokenConfig { + // "AWSPARAMSTR#/token/1|somekey", + tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + tkn.WithSanitizedToken("/token/1") + tkn.WithKeyPath("somekey") + tkn.WithMetadata("") + return tkn + }, + tsuccessParam, func(t *testing.T) mockParamApi { + return mockParamApi(func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { + t.Helper() + awsParamtStoreCommonGetChecker(t, params) + + if strings.Contains(*params.Name, "|somekey") { + t.Errorf("incorrectly stripped key separator") + } + + return &ssm.GetParameterOutput{ + Parameter: &types.Parameter{Value: &tsuccessParam}, + }, nil + }) + }, + }, + "errored": { + func() *config.ParsedTokenConfig { + // "AWSPARAMSTR#/token/1", + tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + tkn.WithSanitizedToken("/token/1") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + "unable to retrieve", func(t *testing.T) mockParamApi { + return mockParamApi(func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { + t.Helper() + awsParamtStoreCommonGetChecker(t, params) + return nil, fmt.Errorf("unable to retrieve") + }) + }, + }, + "nil to empty": { + func() *config.ParsedTokenConfig { + // "AWSPARAMSTR#/token/1", + tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + tkn.WithSanitizedToken("/token/1") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + "", func(t *testing.T) mockParamApi { + return mockParamApi(func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { + t.Helper() + awsParamtStoreCommonGetChecker(t, params) + return &ssm.GetParameterOutput{ + Parameter: &types.Parameter{Value: nil}, + }, nil + }) + }, + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + impl, err := store.NewParamStore(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/plugins/awssecrets/main.go b/plugins/awssecrets/main.go new file mode 100644 index 0000000..8a03b12 --- /dev/null +++ b/plugins/awssecrets/main.go @@ -0,0 +1,197 @@ +package main + +import ( + "encoding/binary" + "strings" + "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, metadataStrPtr, metadataStrLen, 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 + } + + if metadataStrLen > 0 { + metadataStrBytes := bytesFromPtrLen(metadataStrPtr, metadataStrLen) + if !utf8.Valid(metadataStrBytes) { + if outLenPtr != 0 { + if lenCell := bytesFromPtrLen(metadataStrPtr, 4); len(lenCell) == 4 { + binary.LittleEndian.PutUint32(lenCell, uint32(len(tokenBytes))) + } + } + return plugins.ERR_INVALID_UTF8 + } + } + + // --- Business logic (replace with your real token strategy) --- + + inStr := string(tokenBytes) + outStr := "TOKEN_VALUE:" + strings.ToUpper(inStr) + outBytes := []byte(outStr) + // -------------------------------------------------------------- + + 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 `_instantiate` method +func main() {} + +// GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o awssecrets.wasm diff --git a/plugins/scaffolding.go b/plugins/scaffolding.go new file mode 100644 index 0000000..f4bdce0 --- /dev/null +++ b/plugins/scaffolding.go @@ -0,0 +1,30 @@ +package plugins + +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") +) + +type MessagExchange struct { + Token string `json:"token"` + Metadata map[string]any `json:"metadata,omitempty"` + Version string `json:"version"` +} From 6b8689c05de0d39fe45f6e69ac61cba73840308c Mon Sep 17 00:00:00 2001 From: dnitsch Date: Mon, 24 Nov 2025 17:45:55 +0000 Subject: [PATCH 02/20] fix: release container --- .github/workflows/release_container.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release_container.yml b/.github/workflows/release_container.yml index 9a1c45d..3ac8556 100644 --- a/.github/workflows/release_container.yml +++ b/.github/workflows/release_container.yml @@ -2,7 +2,7 @@ name: Publish Container on: workflow_run: - workflows: ['Lint and Test'] + workflows: ['CI'] types: - completed branches: From f0582ec9a4e8cdd5cab93c5ea655410ec9030733 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Wed, 26 Nov 2025 09:29:15 +0000 Subject: [PATCH 03/20] fix: interim goplugin from hashicorp --- go.mod | 8 ++++- go.sum | 6 ++++ internal/plugin/plugin.go | 62 ++++++++++++++++++++++++++++++++++++- internal/plugin/provider.go | 62 +++++++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 internal/plugin/provider.go diff --git a/go.mod b/go.mod index bf22cb0..a8074af 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,12 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect +require ( + github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/oklog/run v1.1.0 // indirect +) require ( cloud.google.com/go/auth v0.17.0 // indirect @@ -59,6 +64,7 @@ require ( 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-plugin v1.7.0 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 diff --git a/go.sum b/go.sum index a8b3951..f7638e0 100644 --- a/go.sum +++ b/go.sum @@ -154,6 +154,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 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= @@ -174,6 +176,8 @@ github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicH 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/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= @@ -205,6 +209,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG 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.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= 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= diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go index 5eb80c7..24c2b48 100644 --- a/internal/plugin/plugin.go +++ b/internal/plugin/plugin.go @@ -1,6 +1,11 @@ package plugin -import "github.com/DevLabFoundry/configmanager/v3/internal/config" +import ( + "net/rpc" + + "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/hashicorp/go-plugin" +) // Plugin is responsible for managing plugins within configmanager // @@ -14,3 +19,58 @@ type Plugin struct { fallbackPaths []string engineInstance *Engine } + +// ValueProvider is the interface that we're exposing as a plugin. +type ValueProvider interface { + Value(token string, metadata string) (string, error) +} + +// Here is an implementation that talks over RPC +type StorePluginRPC struct{ client *rpc.Client } + +func (g *StorePluginRPC) Greet() string { + var resp string + err := g.client.Call("Plugin.Greet", new(interface{}), &resp) + if err != nil { + // You usually want your interfaces to return errors. If they don't, + // there isn't much other choice here. + panic(err) + } + + return resp +} + +// Here is the RPC server that GreeterRPC talks to, conforming to +// the requirements of net/rpc +type GreeterRPCServer struct { + // This is the real implementation + Impl ValueProvider +} + +func (s *GreeterRPCServer) Greet(args interface{}, resp *string) error { + *resp = s.Impl.Value() + return nil +} + +// This is the implementation of plugin.Plugin so we can serve/consume this +// +// This has two methods: Server must return an RPC server for this plugin +// type. We construct a GreeterRPCServer for this. +// +// Client must return an implementation of our interface that communicates +// over an RPC client. We return GreeterRPC for this. +// +// Ignore MuxBroker. That is used to create more multiplexed streams on our +// plugin connection and is a more advanced use case. +type GreeterPlugin struct { + // Impl Injection + Impl ValueProvider +} + +func (p *GreeterPlugin) Server(*plugin.MuxBroker) (interface{}, error) { + return &GreeterRPCServer{Impl: p.Impl}, nil +} + +func (GreeterPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { + return &StorePluginRPC{client: c}, nil +} diff --git a/internal/plugin/provider.go b/internal/plugin/provider.go new file mode 100644 index 0000000..9a9e236 --- /dev/null +++ b/internal/plugin/provider.go @@ -0,0 +1,62 @@ +package plugin + +import ( + "fmt" + "log" + "os" + "os/exec" + + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/basic/shared" +) + +// handshakeConfigs are used to just do a basic handshake between +// a plugin and host. If the handshake fails, a user friendly error is shown. +// This prevents users from executing bad plugins or executing a plugin +// directory. It is a UX feature, not a security feature. +var handshakeConfig = plugin.HandshakeConfig{ + ProtocolVersion: 1, + MagicCookieKey: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +// pluginMap is the map of plugins we can dispense. +var pluginMap = map[string]plugin.Plugin{ + "greeter": &shared.GreeterPlugin{}, +} + +func Init() { + // Create an hclog.Logger + logger := hclog.New(&hclog.LoggerOptions{ + Name: "plugin", + Output: os.Stdout, + Level: hclog.Debug, + }) + + // We're a host! Start by launching the plugin process. + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: handshakeConfig, + Plugins: pluginMap, + Cmd: exec.Command("./plugin/greeter"), + Logger: logger, + }) + defer client.Kill() + + // Connect via RPC + rpcClient, err := client.Client() + if err != nil { + log.Fatal(err) + } + + // Request the plugin + raw, err := rpcClient.Dispense("greeter") + if err != nil { + log.Fatal(err) + } + + // We should have a Greeter now! This feels like a normal interface + // implementation but is in fact over an RPC connection. + greeter := raw.(shared.Greeter) + fmt.Println(greeter.Greet()) +} From 4f7aeccfd1494de348187a7edc89fb03f774dcb9 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Sat, 29 Nov 2025 11:44:48 +0000 Subject: [PATCH 04/20] fix: interim commit shared lib needs more work --- .gitignore | 1 + buf.gen.yaml | 15 ++ buf.yaml | 9 ++ eirctl.yaml | 34 ++++- go.mod | 32 ++-- go.sum | 219 +++++++++++++++------------ plugins/grpc.go | 35 +++++ plugins/interface.go | 54 +++++++ plugins/proto/token_store.pb.go | 183 ++++++++++++++++++++++ plugins/proto/token_store.proto | 16 ++ plugins/proto/token_store_grpc.pb.go | 107 +++++++++++++ 11 files changed, 588 insertions(+), 117 deletions(-) create mode 100644 buf.gen.yaml create mode 100644 buf.yaml create mode 100644 plugins/grpc.go create mode 100644 plugins/interface.go create mode 100644 plugins/proto/token_store.pb.go create mode 100644 plugins/proto/token_store.proto create mode 100644 plugins/proto/token_store_grpc.pb.go diff --git a/.gitignore b/.gitignore index 573d3c6..81bfbd1 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ dist # local testers and local/ +.bin diff --git a/buf.gen.yaml b/buf.gen.yaml new file mode 100644 index 0000000..eb64d58 --- /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: plugins/proto + opt: + - paths=source_relative + - remote: buf.build/grpc/go:v1.3.0 + out: plugins/proto + opt: + - paths=source_relative + - require_unimplemented_servers=false +inputs: + - directory: plugins/proto diff --git a/buf.yaml b/buf.yaml new file mode 100644 index 0000000..d055ccd --- /dev/null +++ b/buf.yaml @@ -0,0 +1,9 @@ +version: v2 +modules: + - path: ./plugins/proto +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/eirctl.yaml b/eirctl.yaml index f9e4f9c..7c64937 100644 --- a/eirctl.yaml +++ b/eirctl.yaml @@ -10,10 +10,24 @@ 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.25-alpine + envfile: + exclude: + - GO + - CXX + - CGO pipelines: unit:test: - - pipeline: test:unit + - pipeline: test:unit env: ROOT_PKG_NAME: github.com/DevLabFoundry @@ -32,6 +46,11 @@ pipelines: - task: go:build:binary depends_on: clean + proto:build: + - task: proto:install + - task: proto:generate + depends_on: proto:install + tasks: show:coverage: description: Opens the current coverage viewer for the the configmanager utility. @@ -93,7 +112,7 @@ tasks: sonar:coverage:prep: context: bash command: - - | + - | sed -i 's|github.com/DevLabFoundry/configmanager/v2/||g' .coverage/out echo "Coverage file first 20 lines after conversion:" head -20 .coverage/out @@ -112,4 +131,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/go.mod b/go.mod index a8074af..4049a5c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/DevLabFoundry/configmanager/v3 go 1.25.4 require ( - cloud.google.com/go/secretmanager v1.16.0 + cloud.google.com/go/secretmanager v1.11.4 github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 github.com/Azure/azure-sdk-for-go/sdk/data/azappconfig v1.2.0 @@ -14,7 +14,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1 github.com/aws/aws-sdk-go-v2/service/ssm v1.67.3 github.com/go-test/deep v1.1.1 - github.com/googleapis/gax-go/v2 v2.15.0 + github.com/googleapis/gax-go/v2 v2.12.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 @@ -24,16 +24,16 @@ require ( require ( github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect - github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/oklog/run v1.1.0 // indirect + go.opencensus.io v0.24.0 // indirect ) 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 + cloud.google.com/go/iam v1.1.5 // 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 @@ -57,14 +57,14 @@ require ( 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/s2a-go v0.1.7 // 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-hclog v1.6.3 github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.7.0 + github.com/hashicorp/go-plugin v1.6.2 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 @@ -86,7 +86,7 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/tetratelabs/wazero v1.10.1 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/google.golang.org/grpc/otelgrpc v0.46.1 // 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 @@ -98,10 +98,10 @@ require ( golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/api v0.256.0 // indirect - google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect - google.golang.org/grpc v1.77.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/api v0.155.0 // indirect + google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect + google.golang.org/grpc v1.61.2 + google.golang.org/protobuf v1.31.0 ) diff --git a/go.sum b/go.sum index f7638e0..3008268 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,20 @@ -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 v1.49.1 h1:KYKIG0+pfpAWaAYayFkE/KPrAVCge0Hu82bPraAmsCk= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= +cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= 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= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/secretmanager v1.11.4 h1:krnX9qpG2kR2fJ+u+uNyNo+ACVhplIAS4Pu7u+4gd+k= +cloud.google.com/go/secretmanager v1.11.4/go.mod h1:wreJlbS9Zdq21lMzWmJ0XhWW2ZxgPeahsqeV/vZoJ3w= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/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 v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= 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/data/aztables v1.4.1 h1:j0hhYS006eJ54vusoap0f2NVZ1YY3QnaAEnLM68f0SQ= github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.4.1/go.mod h1:AdtInaXmK8eYmbjezRWgLz+Qs46nc9Up9GWGwteWNfw= github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= @@ -37,84 +27,64 @@ github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJ 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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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 v1.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= github.com/aws/aws-sdk-go-v2 v1.40.0/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/config v1.32.0 h1:T5WWJYnam9SzBLbsVYDu2HscLDe+GU1AUJtfcDAc/vA= github.com/aws/aws-sdk-go-v2/config v1.32.0/go.mod h1:pSRm/+D3TxBixGMXlgtX4+MPO9VNtEEtiFmNpxksoxw= -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/credentials v1.19.0 h1:7zm+ez+qEqLaNsCSRaistkvJRJv8sByDOVuCnyHbP7M= github.com/aws/aws-sdk-go-v2/credentials v1.19.0/go.mod h1:pHKPblrT7hqFGkNLxqoS3FlGoPrQg4hMIa+4asZzBfs= -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/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= -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/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= -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/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= 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/internal/presigned-url v1.13.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= -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/secretsmanager v1.40.1 h1:w6a0H79HrHf3lr+zrw+pSzR5B+caiQFAKiNHlrUcnoc= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1/go.mod h1:c6Vg0BRiU7v0MVhHupw90RyL120QBwAMLbDCzptGeMk= github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g= github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= -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/ssm v1.67.3 h1:ofiQvKwka2E3T8FXBsU1iWj7Yvk2wd1p4ZCdS6qGiKQ= github.com/aws/aws-sdk-go-v2/service/ssm v1.67.3/go.mod h1:+nlWvcgDPQ56mChEBzTC0puAMck+4onOFaHg5cE+Lgg= -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/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as= github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= -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/ssooidc v1.35.8 h1:MvlNs/f+9eM0mOjD9JzBUbf5jghyTk3p+O9yHMXX94Y= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= -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/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA= github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= 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/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= 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/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= +github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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 v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM= -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/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo= -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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= 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= @@ -132,18 +102,41 @@ 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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= +github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= 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= @@ -154,8 +147,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 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-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= 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= @@ -180,6 +173,8 @@ 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/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= 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= @@ -215,10 +210,9 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmd 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= @@ -239,15 +233,18 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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= github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8= github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 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/google.golang.org/grpc/otelgrpc v0.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= 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= @@ -261,66 +258,88 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689Cbtr 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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-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/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= golang.org/x/oauth2 v0.33.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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20200930185726-fdedc70b468f/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/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -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/api v0.256.0 h1:u6Khm8+F9sxbCTYNoBHg6/Hwv0N/i+V94MvkOSor6oI= -google.golang.org/api v0.256.0/go.mod h1:KIgPhksXADEKJlnEoRa9qAII4rXcy40vfI8HRqcU964= -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 v0.0.0-20251111163417-95abcf5c77ba h1:Ze6qXW0j37YCqZdCD2LkzVSxgEWez0cO4NUyd44DiDY= -google.golang.org/genproto v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:4FLPzLA8eGAktPOTemJGDgDYRpLYwrNu4u2JtWINhnI= -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/api v0.0.0-20251111163417-95abcf5c77ba h1:B14OtaXuMaCQsl2deSvNkyPKIzq3BjfxQp8d00QyWx4= -google.golang.org/genproto/googleapis/api v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:G5IanEx8/PgI9w6CFcYQf7jMtHQhZruvfM1i3qOqk5U= -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/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/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/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 h1:EWIeHfGuUf00zrVZGEgYFxok7plSAXBGcH7NNdMAWvA= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.61.2 h1:TzJay21lXCf7BiNFKl7mSskt5DlkKAumAYTs52SpJeo= +google.golang.org/grpc v1.61.2/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 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= @@ -332,3 +351,5 @@ 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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/plugins/grpc.go b/plugins/grpc.go new file mode 100644 index 0000000..9d7ffc5 --- /dev/null +++ b/plugins/grpc.go @@ -0,0 +1,35 @@ +package plugins + +import ( + "context" + + "github.com/DevLabFoundry/configmanager/v3/plugins/proto" +) + +// GRPCClient is an implementation of KV that talks over RPC. +type GRPCClient struct{ client proto.TokenStoreClient } + +func (m *GRPCClient) Get(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) Get( + ctx context.Context, + req *proto.TokenValueRequest) (*proto.TokenValueResponse, error) { + v, err := m.Impl.Get(req.) + return &proto.GetResponse{Value: v}, err +} diff --git a/plugins/interface.go b/plugins/interface.go new file mode 100644 index 0000000..ee33294 --- /dev/null +++ b/plugins/interface.go @@ -0,0 +1,54 @@ +package plugins + +import ( + "context" + + "google.golang.org/grpc" + + "github.com/hashicorp/go-plugin" + "github.com/hashicorp/go-plugin/examples/grpc/proto" +) + +// 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: "BASIC_PLUGIN", + MagicCookieValue: "hello", +} + +// PluginMap is the map of plugins we can dispense. +var PluginMap = map[string]plugin.Plugin{ + "kv_grpc": &TokenStoreGRPCPlugin{}, + // "kv": &KVPlugin{}, +} + +// TokenStore is the interface that we're exposing as a plugin. +type TokenStore interface { + Get(token string, metadata []byte) (string, error) +} + +// This is the implementation of plugin.Plugin so we can serve/consume this. +type TokenStorePlugin struct { + // Concrete implementation, written in Go. This is only used for plugins + // that are written in Go. + Impl TokenStore +} + +// This is the implementation of plugin.GRPCPlugin so we can serve/consume this. +type TokenStoreGRPCPlugin 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 *TokenStoreGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { + proto.RegisterKVServer(s, &GRPCServer{Impl: p.Impl}) + return nil +} + +func (p *TokenStoreGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { + return &GRPCClient{client: proto(c)}, nil +} diff --git a/plugins/proto/token_store.pb.go b/plugins/proto/token_store.pb.go new file mode 100644 index 0000000..aa4eff0 --- /dev/null +++ b/plugins/proto/token_store.pb.go @@ -0,0 +1,183 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.10 +// 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/plugins/proto/token_store.proto b/plugins/proto/token_store.proto new file mode 100644 index 0000000..a22eb02 --- /dev/null +++ b/plugins/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/plugins/proto/token_store_grpc.pb.go b/plugins/proto/token_store_grpc.pb.go new file mode 100644 index 0000000..6ea7faf --- /dev/null +++ b/plugins/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", +} From b1f304641297318a1e046b892135fa68a28971ed Mon Sep 17 00:00:00 2001 From: dnitsch Date: Sun, 30 Nov 2025 10:42:37 +0000 Subject: [PATCH 05/20] fix: plugin architecture working NOTE: needs a lot more polishing --- internal/config/config.go | 50 +- internal/parser/parser_test.go | 179 +++-- internal/plugin/plugin.go | 76 --- internal/plugin/provider.go | 62 -- internal/plugin/tester/main.go | 46 -- internal/plugin/wasip1.go | 263 -------- internal/plugin/wasip1_test.go | 47 -- internal/store/azappconf.go | 112 ---- internal/store/azappconf_test.go | 208 ------ internal/store/azhelpers.go | 37 -- internal/store/azkeyvault.go | 94 --- internal/store/azkeyvault_test.go | 218 ------ internal/store/aztablestorage.go | 126 ---- internal/store/aztablestorage_test.go | 354 ---------- internal/store/gcpsecrets.go | 91 --- internal/store/gcpsecrets_test.go | 184 ------ internal/store/hashivault.go | 172 ----- internal/store/hashivault_test.go | 624 ------------------ internal/store/paramstore_test.go | 152 ----- internal/store/plugin.go | 64 ++ internal/store/plugin_test.go | 34 + internal/store/secretsmanager.go | 93 --- internal/store/secretsmanager_test.go | 154 ----- internal/store/store.go | 2 +- internal/strategy/strategy.go | 42 +- internal/strategy/strategy_test.go | 339 +++++----- plugins/awsparams/paramstore.go | 67 -- .../main.go => awsparamstr/README.md} | 17 +- .../awsparamstr/impl}/paramstore.go | 23 +- .../impl}/paramstore_test.go | 10 +- plugins/awsparamstr/main.go | 34 + plugins/awssecrets/main.go | 197 ------ plugins/grpc.go | 8 +- plugins/interface.go | 22 +- plugins/scaffolding.go | 6 - 35 files changed, 463 insertions(+), 3744 deletions(-) delete mode 100644 internal/plugin/plugin.go delete mode 100644 internal/plugin/provider.go delete mode 100644 internal/plugin/tester/main.go delete mode 100644 internal/plugin/wasip1.go delete mode 100644 internal/plugin/wasip1_test.go delete mode 100644 internal/store/azappconf.go delete mode 100644 internal/store/azappconf_test.go delete mode 100644 internal/store/azhelpers.go delete mode 100644 internal/store/azkeyvault.go delete mode 100644 internal/store/azkeyvault_test.go delete mode 100644 internal/store/aztablestorage.go delete mode 100644 internal/store/aztablestorage_test.go delete mode 100644 internal/store/gcpsecrets.go delete mode 100644 internal/store/gcpsecrets_test.go delete mode 100644 internal/store/hashivault.go delete mode 100644 internal/store/hashivault_test.go delete mode 100644 internal/store/paramstore_test.go create mode 100644 internal/store/plugin.go create mode 100644 internal/store/plugin_test.go delete mode 100644 internal/store/secretsmanager.go delete mode 100644 internal/store/secretsmanager_test.go delete mode 100644 plugins/awsparams/paramstore.go rename plugins/{awsparams/main.go => awsparamstr/README.md} (91%) rename {internal/store => plugins/awsparamstr/impl}/paramstore.go (69%) rename plugins/{awsparams => awsparamstr/impl}/paramstore_test.go (95%) create mode 100644 plugins/awsparamstr/main.go delete mode 100644 plugins/awssecrets/main.go diff --git a/internal/config/config.go b/internal/config/config.go index a1898fc..f7b3713 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,8 +5,6 @@ import ( "errors" "fmt" "strings" - - "github.com/DevLabFoundry/configmanager/v3/plugins" ) const ( @@ -175,7 +173,7 @@ func (ptc *ParsedTokenConfig) WithSanitizedToken(v string) { func (t *ParsedTokenConfig) ParseMetadata(metadataTyp any) error { // 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, t.parseMetadata()), 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 @@ -184,21 +182,6 @@ func (t *ParsedTokenConfig) ParseMetadata(metadataTyp any) error { return nil } -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, ",")) -} - // StoreToken returns the sanitized token without: // - metadata // - keySeparator @@ -249,24 +232,17 @@ func (t *ParsedTokenConfig) TokenSeparator() string { return t.tokenSeparator } -func (t *ParsedTokenConfig) JSONMessagExchange() (*plugins.MessagExchange, error) { - md := map[string]any{} - if err := json.Unmarshal([]byte(t.parseMetadata()), &md); err != nil { - return nil, err - } - - jme := &plugins.MessagExchange{ - Token: t.StoreToken(), - Metadata: md, - } - - return jme, nil -} - -func (t *ParsedTokenConfig) JSONMessagExchangeBytes() ([]byte, error) { - j, err := t.JSONMessagExchange() - if err != nil { - return nil, err +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 json.Marshal(j) + return fmt.Sprintf(`{%s}`, strings.Join(metaMap, ",")) } diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 2a97c04..9b93cf5 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -9,7 +9,6 @@ import ( "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"} @@ -188,100 +187,100 @@ 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) { +// func Test_Parse_Path_Keys_WithParsedMetadat(t *testing.T) { - ttests := map[string]struct { - input string - typ *store.SecretsMgrConfig - wantSanitizedPath string - wantKeyPath string - }{ - "without keysPath": { - `AWSSECRETS:///foo[version=1.2.3]`, - &store.SecretsMgrConfig{}, - "/foo", "", - }, - "with keysPath": { - `AWSSECRETS:///foo|path.one[version=1.2.3]`, - &store.SecretsMgrConfig{}, - "/foo", "path.one", - }, - "nestled in text": { - `someQ=AWSPARAMSTR:///path/queryparam|p1[version=1.2.3]&anotherQ`, - &store.SecretsMgrConfig{}, - "/path/queryparam", "p1", - }, - } - 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 +// wantSanitizedPath string +// wantKeyPath string +// }{ +// "without keysPath": { +// `AWSSECRETS:///foo[version=1.2.3]`, +// &store.SecretsMgrConfig{}, +// "/foo", "", +// }, +// "with keysPath": { +// `AWSSECRETS:///foo|path.one[version=1.2.3]`, +// &store.SecretsMgrConfig{}, +// "/foo", "path.one", +// }, +// "nestled in text": { +// `someQ=AWSPARAMSTR:///path/queryparam|p1[version=1.2.3]&anotherQ`, +// &store.SecretsMgrConfig{}, +// "/path/queryparam", "p1", +// }, +// } +// 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 p.ParsedToken.StoreToken() != tt.wantSanitizedPath { - t.Errorf("got %s want %s", p.ParsedToken.StoreToken(), tt.wantSanitizedPath) - } - if p.ParsedToken.LookupKeys() != tt.wantKeyPath { - t.Errorf("got %s want %s", p.ParsedToken.LookupKeys(), tt.wantKeyPath) - } - 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 p.ParsedToken.StoreToken() != tt.wantSanitizedPath { +// t.Errorf("got %s want %s", p.ParsedToken.StoreToken(), tt.wantSanitizedPath) +// } +// if p.ParsedToken.LookupKeys() != tt.wantKeyPath { +// t.Errorf("got %s want %s", p.ParsedToken.LookupKeys(), tt.wantKeyPath) +// } +// 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 testHelperGenDocBlock(t *testing.T, stmtBlock parser.ConfigManagerTokenBlock, tokenType config.ImplementationPrefix, tokenValue, keysLookupPath string) bool { t.Helper() diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go deleted file mode 100644 index 24c2b48..0000000 --- a/internal/plugin/plugin.go +++ /dev/null @@ -1,76 +0,0 @@ -package plugin - -import ( - "net/rpc" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/hashicorp/go-plugin" -) - -// Plugin is responsible for managing plugins within configmanager -// -// It includes the following methods -// - fetch plugins from known sources -// - maintains a list of tokens answerable by a specified pluginEngine -type Plugin struct { - Implementations config.ImplementationPrefix - SourcePath string - Version string - fallbackPaths []string - engineInstance *Engine -} - -// ValueProvider is the interface that we're exposing as a plugin. -type ValueProvider interface { - Value(token string, metadata string) (string, error) -} - -// Here is an implementation that talks over RPC -type StorePluginRPC struct{ client *rpc.Client } - -func (g *StorePluginRPC) Greet() string { - var resp string - err := g.client.Call("Plugin.Greet", new(interface{}), &resp) - if err != nil { - // You usually want your interfaces to return errors. If they don't, - // there isn't much other choice here. - panic(err) - } - - return resp -} - -// Here is the RPC server that GreeterRPC talks to, conforming to -// the requirements of net/rpc -type GreeterRPCServer struct { - // This is the real implementation - Impl ValueProvider -} - -func (s *GreeterRPCServer) Greet(args interface{}, resp *string) error { - *resp = s.Impl.Value() - return nil -} - -// This is the implementation of plugin.Plugin so we can serve/consume this -// -// This has two methods: Server must return an RPC server for this plugin -// type. We construct a GreeterRPCServer for this. -// -// Client must return an implementation of our interface that communicates -// over an RPC client. We return GreeterRPC for this. -// -// Ignore MuxBroker. That is used to create more multiplexed streams on our -// plugin connection and is a more advanced use case. -type GreeterPlugin struct { - // Impl Injection - Impl ValueProvider -} - -func (p *GreeterPlugin) Server(*plugin.MuxBroker) (interface{}, error) { - return &GreeterRPCServer{Impl: p.Impl}, nil -} - -func (GreeterPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) { - return &StorePluginRPC{client: c}, nil -} diff --git a/internal/plugin/provider.go b/internal/plugin/provider.go deleted file mode 100644 index 9a9e236..0000000 --- a/internal/plugin/provider.go +++ /dev/null @@ -1,62 +0,0 @@ -package plugin - -import ( - "fmt" - "log" - "os" - "os/exec" - - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-plugin" - "github.com/hashicorp/go-plugin/examples/basic/shared" -) - -// handshakeConfigs are used to just do a basic handshake between -// a plugin and host. If the handshake fails, a user friendly error is shown. -// This prevents users from executing bad plugins or executing a plugin -// directory. It is a UX feature, not a security feature. -var handshakeConfig = plugin.HandshakeConfig{ - ProtocolVersion: 1, - MagicCookieKey: "BASIC_PLUGIN", - MagicCookieValue: "hello", -} - -// pluginMap is the map of plugins we can dispense. -var pluginMap = map[string]plugin.Plugin{ - "greeter": &shared.GreeterPlugin{}, -} - -func Init() { - // Create an hclog.Logger - logger := hclog.New(&hclog.LoggerOptions{ - Name: "plugin", - Output: os.Stdout, - Level: hclog.Debug, - }) - - // We're a host! Start by launching the plugin process. - client := plugin.NewClient(&plugin.ClientConfig{ - HandshakeConfig: handshakeConfig, - Plugins: pluginMap, - Cmd: exec.Command("./plugin/greeter"), - Logger: logger, - }) - defer client.Kill() - - // Connect via RPC - rpcClient, err := client.Client() - if err != nil { - log.Fatal(err) - } - - // Request the plugin - raw, err := rpcClient.Dispense("greeter") - if err != nil { - log.Fatal(err) - } - - // We should have a Greeter now! This feels like a normal interface - // implementation but is in fact over an RPC connection. - greeter := raw.(shared.Greeter) - fmt.Println(greeter.Greet()) -} diff --git a/internal/plugin/tester/main.go b/internal/plugin/tester/main.go deleted file mode 100644 index b17e481..0000000 --- a/internal/plugin/tester/main.go +++ /dev/null @@ -1,46 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "os" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/plugin" -) - -func main() { - inputReader, err := os.Open("/Users/dusannitschneider/git/dnitsch/configmanager/plugins/awsparams/awsparams.wasm") - if err != nil { - log.Fatal(fmt.Errorf("open plugin.wasm: %w", err)) - } - - ctx := context.Background() - - // Load the compiled WASI plugin. - engine, err := plugin.NewEngine(ctx, inputReader) - if err != nil { - log.Fatal(err) - } - defer engine.Close(ctx) - - inst, err := engine.NewApiInstance(ctx) - if err != nil { - log.Fatal(err) - } - defer inst.Close(ctx) - - // os.Setenv("AWS_PROFILE", "anabode_terraform_dev") - // os.Setenv("AWS_REGION", "eu-west-1") - t1, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) - t1.WithSanitizedToken("/int-test/pocketbase/admin-pwd") - val, err := inst.TokenValue(ctx, t1) - if err != nil { - log.Fatal(err) - } - - fmt.Println(string(val)) - - os.Exit(0) -} diff --git a/internal/plugin/wasip1.go b/internal/plugin/wasip1.go deleted file mode 100644 index 268371f..0000000 --- a/internal/plugin/wasip1.go +++ /dev/null @@ -1,263 +0,0 @@ -// Package plugin -// provides reactor style module -// we can explore the plugin provided host module -package plugin - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "io" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/plugins" - "github.com/tetratelabs/wazero" - "github.com/tetratelabs/wazero/api" - "github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1" -) - -var ( - ErrMissingMethod = errors.New("missing method on the wasiLib instance") - ErrAllocMemForParam = errors.New("failed to allocate memory for property") - ErrAllocateOutPtrZeroLen = errors.New("allocate returned 0 for output pointer") - ErrMemoryReadFailed = errors.New("mem.Read(out) failed") - ErrEmptyToken = errors.New("token must not be empty") -) - -// ==================== -// Engine & ApiInstance -// ==================== - -type Engine struct { - r wazero.Runtime - compiledModule wazero.CompiledModule -} - -// NewEngine compiles the WASI module once for the lifetime of the program. -func NewEngine(ctx context.Context, ps io.ReadCloser) (*Engine, error) { - r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig()) - if _, err := wasi_snapshot_preview1.Instantiate(ctx, r); err != nil { - _ = r.Close(ctx) - return nil, fmt.Errorf("instantiate WASI: %w", err) - } - - defer ps.Close() - wasiLib, err := io.ReadAll(ps) - if err != nil { - _ = r.Close(ctx) - return nil, fmt.Errorf("read plugin: %w", err) - } - - cm, err := r.CompileModule(ctx, wasiLib) - if err != nil { - _ = r.Close(ctx) - return nil, fmt.Errorf("compile module: %w", err) - } - - return &Engine{ - r: r, - compiledModule: cm, - }, nil -} - -// Close shuts down the runtime. -func (e *Engine) Close(ctx context.Context) error { - return e.r.Close(ctx) -} - -type ApiInstance struct { - mod api.Module - mem api.Memory - // exported alloc helpers - allocate api.Function - deallocate api.Function - // exported business function - tokenValue api.Function - // scratch output buffers - outPtr uint32 - outCap uint32 - outLenPtr uint32 // 4-byte cell for required length -} - -// NewApiInstance instantiates a fresh module instance. -func (e *Engine) NewApiInstance(ctx context.Context) (*ApiInstance, error) { - mod, err := e.r.InstantiateModule(ctx, e.compiledModule, wazero.NewModuleConfig().WithStartFunctions("_initialize")) - if err != nil { - return nil, fmt.Errorf("instantiate module: %w", err) - } - - inst := &ApiInstance{ - mod: mod, - mem: mod.Memory(), - allocate: mod.ExportedFunction("allocate"), - deallocate: mod.ExportedFunction("deallocate"), - tokenValue: mod.ExportedFunction("strategy_token_value"), - } - - for name, exported := range map[string]api.Function{ - "allocate": inst.allocate, - "deallocate": inst.deallocate, - "strategy_token_value": inst.tokenValue, - } { - if exported == nil { - return nil, fmt.Errorf("%w, method %q not found on exports", ErrMissingMethod, name) - } - } - - return inst, nil -} - -// Close instance (optional). -func (i *ApiInstance) Close(ctx context.Context) { - i.freeScratch(ctx) - _ = i.mod.Close(ctx) -} - -// put allocates module memory and writes bytes into it. -// returns (ptr, size). caller must deallocate(ptr, size). -func (i *ApiInstance) put(ctx context.Context, b []byte) (uint32, uint32, error) { - if len(b) == 0 { - return 0, 0, ErrEmptyToken - } - - res, err := i.allocate.Call(ctx, uint64(len(b))) - if err != nil { - return 0, 0, fmt.Errorf("allocate: %w", err) - } - ptr := uint32(res[0]) - if ptr == 0 { - return 0, 0, fmt.Errorf("allocate returned 0: %w", ErrAllocMemForParam) - } - - if ok := i.mem.Write(ptr, b); !ok { - _, _ = i.deallocate.Call(ctx, uint64(ptr), uint64(len(b))) - return 0, 0, fmt.Errorf("mem.Write failed: %w", ErrAllocMemForParam) - } - - return ptr, uint32(len(b)), nil -} - -// ensureOut ensures the scratch output buffer has at least `need` bytes. -// allocates outLenPtr (4 bytes) once. -func (i *ApiInstance) ensureOut(ctx context.Context, need uint32) error { - // outLenPtr is a 4-byte cell for required length - if i.outLenPtr == 0 { - res, err := i.allocate.Call(ctx, 4) - if err != nil { - return fmt.Errorf("allocate outLenPtr: %w", err) - } - i.outLenPtr = uint32(res[0]) - if i.outLenPtr == 0 { - return ErrAllocateOutPtrZeroLen - } - } - - if need <= i.outCap { - return nil - } - - // grow if needed - free old and alloc new - if i.outPtr != 0 { - _, _ = i.deallocate.Call(ctx, uint64(i.outPtr), uint64(i.outCap)) - i.outPtr, i.outCap = 0, 0 - } - - res, err := i.allocate.Call(ctx, uint64(need)) - if err != nil { - return fmt.Errorf("allocate outPtr: %w", err) - } - i.outPtr, i.outCap = uint32(res[0]), need - if i.outPtr == 0 { - return ErrAllocateOutPtrZeroLen - } - return nil -} - -// freeScratch frees the reusable output buffers (call once per instance). -func (i *ApiInstance) freeScratch(ctx context.Context) { - if i.outPtr != 0 { - _, _ = i.deallocate.Call(ctx, uint64(i.outPtr), uint64(i.outCap)) - i.outPtr, i.outCap = 0, 0 - } - if i.outLenPtr != 0 { - _, _ = i.deallocate.Call(ctx, uint64(i.outLenPtr), 4) - i.outLenPtr = 0 - } -} - -// TokenValue is the nice host-side API: string in, []byte out. -func (i *ApiInstance) TokenValue(ctx context.Context, token *config.ParsedTokenConfig) ([]byte, error) { - if token.StoreToken() == "" { - return nil, ErrEmptyToken - } - tokenBytes, err := token.JSONMessagExchangeBytes() - tokenPtr, tokenLen, err := i.put(ctx, tokenBytes) - if err != nil { - return nil, fmt.Errorf("put input: %w", err) - } - defer i.deallocate.Call(ctx, uint64(tokenPtr), uint64(tokenLen)) - - // start with a smallish buffer; plugin will ask for more if needed - if err := i.ensureOut(ctx, 64); err != nil { - return nil, fmt.Errorf("ensureOut: %w", err) - } - - call := func() (int32, uint32, error) { - res, err := i.tokenValue.Call( - ctx, - uint64(tokenPtr), uint64(tokenLen), // sanitizedToken - uint64(i.outPtr), uint64(i.outCap), // outPtr, outCap - uint64(i.outLenPtr), // outLenPtr - ) - if err != nil { - return 0, 0, fmt.Errorf("call strategy_token_value: %w", err) - } - - lenBytes, ok := i.mem.Read(i.outLenPtr, 4) - if !ok { - return int32(res[0]), 0, ErrMemoryReadFailed - } - - need := binary.LittleEndian.Uint32(lenBytes) - return int32(res[0]), need, nil - } - - rc, need, err := call() - if err != nil { - return nil, err - } - - if rc == plugins.ERR_BUF_TOO_SMALL { - if err := i.ensureOut(ctx, need); err != nil { - return nil, fmt.Errorf("ensureOut resize: %w", err) - } - rc, need, err = call() - if err != nil { - return nil, err - } - } - - if rc != plugins.OK { - switch rc { - case plugins.ERR_INVALID_UTF8: - return nil, errors.New("token value: invalid UTF-8 in input") - case plugins.ERR_EMPTY_INPUT: - return nil, ErrEmptyToken - case plugins.ERR_BUF_TOO_SMALL: - return nil, fmt.Errorf("token value: buffer too small even after resize (need=%d)", need) - default: - return nil, fmt.Errorf("token value: unknown error code %d", rc) - } - } - - out, ok := i.mem.Read(i.outPtr, need) - if !ok { - return nil, ErrMemoryReadFailed - } - - // Detach from wasm memory. - result := make([]byte, need) - copy(result, out) - return result, nil -} diff --git a/internal/plugin/wasip1_test.go b/internal/plugin/wasip1_test.go deleted file mode 100644 index 5442eb1..0000000 --- a/internal/plugin/wasip1_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package plugin_test - -import ( - "context" - "fmt" - "os" - "testing" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/plugin" -) - -func Test_FullFlow(t *testing.T) { - inputReader, err := os.Open("/Users/dusannitschneider/git/dnitsch/configmanager/plugins/awsparams/awsparams.wasm") - if err != nil { - t.Fatal(fmt.Errorf("open plugin.wasm: %w", err)) - } - ctx := context.Background() - - // Load the compiled WASI plugin. - engine, err := plugin.NewEngine(ctx, inputReader) - if err != nil { - t.Fatal(err) - } - defer engine.Close(ctx) - - inst, err := engine.NewApiInstance(ctx) - if err != nil { - t.Fatal(err) - } - defer inst.Close(ctx) - - os.Setenv("AWS_PROFILE", "anabode_terraform_dev") - os.Setenv("AWS_REGION", "eu-west-1") - t1, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) - t1.WithSanitizedToken("/int-test/pocketbase/admin-pwd") - val, err := inst.TokenValue(ctx, t1) - if err != nil { - t.Fatal(err) - } - fmt.Printf("TokenValue(\"foo\") => %q\n", string(val)) - - // Zero-length test (should error) - t2, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) - _, err = inst.TokenValue(ctx, t2) - fmt.Printf("TokenValue(\"\") error: %v\n", err) -} 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/hashivault.go b/internal/store/hashivault.go deleted file mode 100644 index 558c9b2..0000000 --- a/internal/store/hashivault.go +++ /dev/null @@ -1,172 +0,0 @@ -package store - -import ( - "context" - "encoding/json" - "fmt" - "os" - "strconv" - "strings" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/internal/log" - - vault "github.com/hashicorp/vault/api" - auth "github.com/hashicorp/vault/api/auth/aws" -) - -// HashiVaultHelper provides a broken up string -type HashiVaultHelper struct { - Path string - Token string -} - -type hashiVaultApi interface { - Get(ctx context.Context, secretPath string) (*vault.KVSecret, error) - GetVersion(ctx context.Context, secretPath string, version int) (*vault.KVSecret, error) -} - -type VaultStore struct { - svc hashiVaultApi - ctx context.Context - logger log.ILogger - config *VaultConfig - token *config.ParsedTokenConfig - strippedToken string -} - -// VaultConfig holds the parseable metadata struct -type VaultConfig struct { - Version string `json:"version"` - Role string `json:"iam_role"` -} - -func NewVaultStore(ctx context.Context, token *config.ParsedTokenConfig, logger log.ILogger) (*VaultStore, error) { - storeConf := &VaultConfig{} - _ = token.ParseMetadata(storeConf) - imp := &VaultStore{ - ctx: ctx, - logger: logger, - config: storeConf, - token: token, - } - - config := vault.DefaultConfig() - vt := SplitHashiVaultToken(token.StoreToken()) - imp.strippedToken = vt.Token - client, err := vault.NewClient(config) - if err != nil { - return nil, fmt.Errorf("%v\n%w", err, ErrClientInitialization) - } - - if strings.HasPrefix(os.Getenv("VAULT_TOKEN"), "aws_iam") { - awsclient, err := newVaultStoreWithAWSAuthIAM(client, storeConf.Role) - if err != nil { - return nil, err - } - client = awsclient - } - imp.svc = client.KVv2(vt.Path) - return imp, nil -} - -func (s *VaultStore) WithSvc(svc hashiVaultApi) { - s.svc = svc -} - -// newVaultStoreWithAWSAuthIAM returns an initialised client with AWSIAMAuth -// EC2 auth type is not supported currently -func newVaultStoreWithAWSAuthIAM(client *vault.Client, role string) (*vault.Client, error) { - if len(role) < 1 { - return nil, fmt.Errorf("role provided is empty, EC2 auth not supported") - } - awsAuth, err := auth.NewAWSAuth( - auth.WithRole(role), - ) - if err != nil { - return nil, fmt.Errorf("unable to initialize AWS auth method: %s. %w", err, 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) - } - if authInfo == nil { - return nil, fmt.Errorf("no auth info was returned after login") - } - - return client, nil -} - -// setTokenVal -// imp.token is already set in the Vault constructor -// -// This happens inside the New func call -// due to the way the client needs to be -// initialised with a mountpath -// and mountpath is part of the token so it is set then -func (imp *VaultStore) SetToken(token *config.ParsedTokenConfig) {} - -// getTokenValue implements the underlying techonology -// token retrieval and returns a stringified version -// of the secret -func (imp *VaultStore) Value() (string, error) { - imp.logger.Info("%s", "Concrete implementation HashiVault") - imp.logger.Info("Getting Secret: %s", imp.token) - - ctx, cancel := context.WithCancel(imp.ctx) - defer cancel() - - secret, err := imp.getSecret(ctx, imp.strippedToken, imp.config.Version) - if err != nil { - imp.logger.Error(implementationNetworkErr, imp.token.Prefix(), err, imp.token.String()) - return "", err - } - - if secret.Data != nil { - resp, err := marshall(secret.Data) - if err != nil { - imp.logger.Error("marshalling error: %s", err.Error()) - return "", err - } - imp.logger.Debug("marhalled kvv2: %s", resp) - return resp, nil - } - - imp.logger.Error("value retrieved but empty for token: %v", imp.token) - return "", nil -} - -func (imp *VaultStore) getSecret(ctx context.Context, token string, version string) (*vault.KVSecret, error) { - if version != "" { - v, err := strconv.Atoi(version) - if err != nil { - return nil, fmt.Errorf("unable to parse version into an integer: %s", err.Error()) - } - return imp.svc.GetVersion(ctx, token, v) - } - return imp.svc.Get(ctx, token) -} - -func SplitHashiVaultToken(token string) HashiVaultHelper { - vh := HashiVaultHelper{} - // split token to extract the mount path - s := strings.Split(strings.TrimPrefix(token, "/"), "___") - // grab token and trim prefix if slash - vh.Token = strings.TrimPrefix(strings.Join(s[1:], ""), "/") - // assign mount path as extracted from input token - vh.Path = s[0] - return vh -} - -// marshall converts map[string]any into a JSON -// object. Secrets should only be a single level -// deep. -func marshall(secret map[string]any) (string, error) { - b, err := json.Marshal(secret) - if err != nil { - return "", err - } - return string(b), nil -} diff --git a/internal/store/hashivault_test.go b/internal/store/hashivault_test.go deleted file mode 100644 index 8c9aed8..0000000 --- a/internal/store/hashivault_test.go +++ /dev/null @@ -1,624 +0,0 @@ -package store_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" - vault "github.com/hashicorp/vault/api" -) - -func TestMountPathExtract(t *testing.T) { - ttests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - }{ - "without leading slash": { - func() *config.ParsedTokenConfig { - // "VAULT://secret___/demo/configmanager" - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/demo/configmanager") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, "secret"}, - "with leading slash": { - func() *config.ParsedTokenConfig { - // "VAULT:///secret___/demo/configmanager", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("/secret___/demo/configmanager") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, "secret"}, - "with underscore in path name": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("_secret___/demo/configmanager") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, "_secret"}, - "with double underscore in path name": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("__secret___/demo/configmanager") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, "__secret"}, - "with multiple paths in mountpath": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret/bar/path___/demo/configmanager") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, "secret/bar/path"}, - } - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - got := store.SplitHashiVaultToken(tt.token().StoreToken()) - if got.Path != tt.expect { - t.Errorf("got %q, expected %q", got, tt.expect) - } - }) - } -} - -type mockVaultApi struct { - g func(ctx context.Context, secretPath string) (*vault.KVSecret, error) - gv func(ctx context.Context, secretPath string, version int) (*vault.KVSecret, error) -} - -func (m mockVaultApi) Get(ctx context.Context, secretPath string) (*vault.KVSecret, error) { - return m.g(ctx, secretPath) -} - -func (m mockVaultApi) GetVersion(ctx context.Context, secretPath string, version int) (*vault.KVSecret, error) { - return m.gv(ctx, secretPath, version) -} - -func TestVaultScenarios(t *testing.T) { - - ttests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - mockClient func(t *testing.T) mockVaultApi - setupEnv func() func() - }{ - "happy return": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/foo") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - `{"foo":"test2130-9sd-0ds"}`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { - t.Helper() - if secretPath != "foo" { - t.Errorf("got %v; want %s", secretPath, `foo`) - } - m := make(map[string]interface{}) - m["foo"] = "test2130-9sd-0ds" - return &vault.KVSecret{Data: m}, nil - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "129378y1231283") - return func() { - os.Clearenv() - } - }, - }, - "incorrect json": { - func() *config.ParsedTokenConfig { - // "VAULT://secret___/foo", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/foo") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - `json: unsupported type: func() error`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { - t.Helper() - if secretPath != "foo" { - t.Errorf("got %v; want %s", secretPath, `foo`) - } - m := make(map[string]interface{}) - m["error"] = func() error { return fmt.Errorf("ddodod") } - return &vault.KVSecret{Data: m}, nil - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "129378y1231283") - return func() { - os.Clearenv() - } - }, - }, - "another return": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret/engine1___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - `{"foo1":"test2130-9sd-0ds","foo2":"dsfsdf3454456"}`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { - t.Helper() - if secretPath != "some/other/foo2" { - t.Errorf("got %v; want %s", secretPath, `some/other/foo2`) - } - m := make(map[string]interface{}) - m["foo1"] = "test2130-9sd-0ds" - m["foo2"] = "dsfsdf3454456" - return &vault.KVSecret{Data: m}, nil - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "129378y1231283") - return func() { - os.Clearenv() - } - }, - }, - "not found": { - func() *config.ParsedTokenConfig { - // "VAULT://secret___/foo", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/foo") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - `secret not found`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { - t.Helper() - if secretPath != "foo" { - t.Errorf("got %v; want %s", secretPath, `foo`) - } - return nil, fmt.Errorf("secret not found") - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "129378y1231283") - return func() { - os.Clearenv() - } - }, - }, - "403": { - func() *config.ParsedTokenConfig { - // "VAULT://secret___/some/other/foo2", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - `client 403`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { - t.Helper() - if secretPath != "some/other/foo2" { - t.Errorf("got %v; want %s", secretPath, `some/other/foo2`) - } - return nil, fmt.Errorf("client 403") - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "129378y1231283") - return func() { - os.Clearenv() - } - }, - }, - "found but empty": { - func() *config.ParsedTokenConfig { - // "VAULT://secret___/some/other/foo2", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - // config.NewConfig(), - `{}`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { - t.Helper() - if secretPath != "some/other/foo2" { - t.Errorf("got %v; want %s", secretPath, `some/other/foo2`) - } - m := make(map[string]interface{}) - return &vault.KVSecret{Data: m}, nil - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "129378y1231283") - return func() { - os.Clearenv() - } - }, - }, - "found but nil returned": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - "", - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - 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`) - } - return &vault.KVSecret{Data: nil}, nil - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "129378y1231283") - return func() { - os.Clearenv() - } - }, - }, - "version provided correctly": { - func() *config.ParsedTokenConfig { - // "VAULT://secret___/some/other/foo2[version=1]", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("version=1") - return tkn - }, - `{"foo2":"dsfsdf3454456"}`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - 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`) - } - m := make(map[string]interface{}) - m["foo2"] = "dsfsdf3454456" - return &vault.KVSecret{Data: m}, nil - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "129378y1231283") - return func() { - os.Clearenv() - } - }, - }, - "version provided but unable to parse": { - func() *config.ParsedTokenConfig { - // "VAULT://secret___/some/other/foo2[version=1a]", - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("version=1a") - return tkn - }, - "unable to parse version into an integer: strconv.Atoi: parsing \"1a\": invalid syntax", - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - 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`) - } - return nil, nil - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "129378y1231283") - return func() { - os.Clearenv() - } - }, - }, - "vault rate limit incorrect": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - `error encountered setting up default configuration: VAULT_RATE_LIMIT was provided but incorrectly formatted -failed to initialize the client`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - 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`) - } - return &vault.KVSecret{Data: nil}, nil - } - return mv - }, - func() func() { - os.Setenv("VAULT_TOKEN", "") - os.Setenv("VAULT_RATE_LIMIT", "wrong") - return func() { - os.Clearenv() - } - }, - }, - } - - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - tearDown := tt.setupEnv() - defer tearDown() - - impl, err := store.NewVaultStore(context.TODO(), tt.token(), log.New(io.Discard)) - if err != nil { - if err.Error() != tt.expect { - t.Fatalf("failed to init hashivault, %v", err.Error()) - } - return - } - - 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 TestAwsIamAuth(t *testing.T) { - ttests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - mockClient func(t *testing.T) mockVaultApi - mockHanlder func(t *testing.T) http.Handler - setupEnv func(addr string) func() - }{ - "aws_iam auth no role specified": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("version=1") - return tkn - }, - "role provided is empty, EC2 auth not supported", - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - 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`) - } - return &vault.KVSecret{Data: nil}, nil - } - return mv - }, - func(t *testing.T) http.Handler { - return nil - }, - func(_ string) func() { - os.Setenv("VAULT_TOKEN", "aws_iam") - os.Setenv("AWS_ACCESS_KEY_ID", "1280qwed9u9nsc9fdsbv9gsfrd") - os.Setenv("AWS_SECRET_ACCESS_KEY", "SED)SDVfdv0jfds08sdfgu09sd943tj4fELH/") - os.Setenv("AWS_SESSION_TOKEN", "IQoJb3JpZ2luX2VjELH//////////wEaCWV1LXdlc3QtMiJIMEYCIQDPU6UGJ0...df.fdgdfg.dfg.gdf.dgf") - os.Setenv("AWS_REGION", "eu-west-1") - return func() { - os.Clearenv() - } - }, - }, - "aws_iam auth incorrectly formatted request": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("version=1,iam_role=not_a_role") - return tkn - }, - `unable to login to AWS auth method: unable to log in to auth method: unable to log in with AWS auth: Error making API request. - -URL: PUT %s/v1/auth/aws/login -Code: 400. Raw Message: - -incorrect values supplied. failed to initialize the client`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - 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`) - } - return &vault.KVSecret{Data: nil}, nil - } - return mv - }, - func(t *testing.T) http.Handler { - mux := http.NewServeMux() - mux.HandleFunc("/v1/auth/aws/login", func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.WriteHeader(400) - w.Write([]byte(`incorrect values supplied`)) - }) - return mux - }, - func(addr string) func() { - os.Setenv("VAULT_TOKEN", "aws_iam") - os.Setenv("VAULT_ADDR", addr) - os.Setenv("AWS_ACCESS_KEY_ID", "1280qwed9u9nsc9fdsbv9gsfrd") - os.Setenv("AWS_SECRET_ACCESS_KEY", "SED)SDVfdv0jfds08sdfgu09sd943tj4fELH/") - os.Setenv("AWS_SESSION_TOKEN", "IQoJb3JpZ2luX2VjELH//////////wEaCWV1LXdlc3QtMiJIMEYCIQDPU6UGJ0...df.fdgdfg.dfg.gdf.dgf") - os.Setenv("AWS_REGION", "eu-west-1") - return func() { - os.Clearenv() - } - }, - }, - "aws_iam auth success": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("iam_role=arn:aws:iam::1111111:role/i-orchestration") - return tkn - }, - // - `{"foo2":"dsfsdf3454456"}`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - 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`) - } - m := make(map[string]any) - m["foo2"] = "dsfsdf3454456" - return &vault.KVSecret{Data: m}, nil - } - return mv - }, - func(t *testing.T) http.Handler { - mux := http.NewServeMux() - mux.HandleFunc("/v1/auth/aws/login", func(w http.ResponseWriter, r *http.Request) { - - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.Write([]byte(`{"auth":{"client_token": "fooresddfasdsasad"}}`)) - }) - return mux - }, - func(addr string) func() { - os.Setenv("VAULT_TOKEN", "aws_iam") - os.Setenv("VAULT_ADDR", addr) - os.Setenv("AWS_ACCESS_KEY_ID", "1280qwed9u9nsc9fdsbv9gsfrd") - os.Setenv("AWS_SECRET_ACCESS_KEY", "SED)SDVfdv0jfds08sdfgu09sd943tj4fELH/") - os.Setenv("AWS_SESSION_TOKEN", "IQoJb3JpZ2luX2VjELH//////////wEaCWV1LXdlc3QtMiJIMEYCIQDPU6UGJ0...df.fdgdfg.dfg.gdf.dgf") - os.Setenv("AWS_REGION", "eu-west-1") - return func() { - os.Clearenv() - } - }, - }, - "aws_iam auth no token returned": { - func() *config.ParsedTokenConfig { - tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) - tkn.WithSanitizedToken("secret___/some/other/foo2") - tkn.WithKeyPath("") - tkn.WithMetadata("iam_role=arn:aws:iam::1111111:role/i-orchestration") - return tkn - }, - `unable to login to AWS auth method: response did not return ClientToken, client token not set. failed to initialize the client`, - func(t *testing.T) mockVaultApi { - mv := mockVaultApi{} - 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`) - } - m := make(map[string]interface{}) - m["foo2"] = "dsfsdf3454456" - return &vault.KVSecret{Data: m}, nil - } - return mv - }, - func(t *testing.T) http.Handler { - mux := http.NewServeMux() - mux.HandleFunc("/v1/auth/aws/login", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - w.Write([]byte(`{"auth":{}}`)) - }) - return mux - }, - func(addr string) func() { - os.Setenv("VAULT_TOKEN", "aws_iam") - os.Setenv("VAULT_ADDR", addr) - os.Setenv("AWS_ACCESS_KEY_ID", "1280qwed9u9nsc9fdsbv9gsfrd") - os.Setenv("AWS_SECRET_ACCESS_KEY", "SED)SDVfdv0jfds08sdfgu09sd943tj4fELH/") - os.Setenv("AWS_SESSION_TOKEN", "IQoJb3JpZ2luX2VjELH//////////wEaCWV1LXdlc3QtMiJIMEYCIQDPU6UGJ0...df.fdgdfg.dfg.gdf.dgf") - os.Setenv("AWS_REGION", "eu-west-1") - return func() { - os.Clearenv() - } - }, - }, - } - - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - // - 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)) - 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.Fatalf("failed to init hashivault, %v", err.Error()) - } - return - } - - 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/paramstore_test.go b/internal/store/paramstore_test.go deleted file mode 100644 index 8fc11d4..0000000 --- a/internal/store/paramstore_test.go +++ /dev/null @@ -1,152 +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/ssm" - "github.com/aws/aws-sdk-go-v2/service/ssm/types" -) - -type mockParamApi func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) - -func (m mockParamApi) GetParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { - return m(ctx, params, optFns...) -} - -func awsParamtStoreCommonGetChecker(t *testing.T, params *ssm.GetParameterInput) { - if params.Name == nil { - t.Fatal("expect name to not be nil") - } - - if strings.Contains(*params.Name, "#") { - t.Errorf("incorrectly stripped token separator") - } - - if strings.Contains(*params.Name, string(config.ParamStorePrefix)) { - t.Errorf("incorrectly stripped prefix") - } - - if !*params.WithDecryption { - t.Fatal("expect WithDecryption to not be false") - } -} - -func Test_GetParamStore(t *testing.T) { - var ( - tsuccessParam = "someVal" - // tsuccessObj map[string]string = map[string]string{"AWSPARAMSTR#/token/1": "someVal"} - ) - tests := map[string]struct { - token func() *config.ParsedTokenConfig - expect string - mockClient func(t *testing.T) mockParamApi - }{ - "successVal": { - func() *config.ParsedTokenConfig { - // "VAULT://secret___/demo/configmanager" - tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - tsuccessParam, func(t *testing.T) mockParamApi { - return mockParamApi(func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { - t.Helper() - awsParamtStoreCommonGetChecker(t, params) - return &ssm.GetParameterOutput{ - Parameter: &types.Parameter{Value: &tsuccessParam}, - }, nil - }) - }, - }, - "successVal with keyseparator": { - func() *config.ParsedTokenConfig { - // "AWSPARAMSTR#/token/1|somekey", - tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("somekey") - tkn.WithMetadata("") - return tkn - }, - tsuccessParam, func(t *testing.T) mockParamApi { - return mockParamApi(func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { - t.Helper() - awsParamtStoreCommonGetChecker(t, params) - - if strings.Contains(*params.Name, "|somekey") { - t.Errorf("incorrectly stripped key separator") - } - - return &ssm.GetParameterOutput{ - Parameter: &types.Parameter{Value: &tsuccessParam}, - }, nil - }) - }, - }, - "errored": { - func() *config.ParsedTokenConfig { - // "AWSPARAMSTR#/token/1", - tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - "unable to retrieve", func(t *testing.T) mockParamApi { - return mockParamApi(func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { - t.Helper() - awsParamtStoreCommonGetChecker(t, params) - return nil, fmt.Errorf("unable to retrieve") - }) - }, - }, - "nil to empty": { - func() *config.ParsedTokenConfig { - // "AWSPARAMSTR#/token/1", - tkn, _ := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) - tkn.WithSanitizedToken("/token/1") - tkn.WithKeyPath("") - tkn.WithMetadata("") - return tkn - }, - "", func(t *testing.T) mockParamApi { - return mockParamApi(func(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) { - t.Helper() - awsParamtStoreCommonGetChecker(t, params) - return &ssm.GetParameterOutput{ - Parameter: &types.Parameter{Value: nil}, - }, nil - }) - }, - }, - } - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - impl, err := store.NewParamStore(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..ddfd739 --- /dev/null +++ b/internal/store/plugin.go @@ -0,0 +1,64 @@ +package store + +import ( + "context" + "os/exec" + + "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/plugins" + "github.com/hashicorp/go-plugin" +) + +// Plugin is responsible for managing plugins within configmanager +// +// It includes the following methods +// - fetch plugins from known sources +// - maintains a list of tokens answerable by a specified pluginEngine +type Plugin struct { + Implementations config.ImplementationPrefix + SourcePath string + Version string + ClientCleanUp func() + tokenStore plugins.TokenStore +} + +// New Plugin gets called once per implementation +func New(ctx context.Context, path string, prefix config.ImplementationPrefix) (*Plugin, error) { + // We're a host. Start by launching the plugin process. + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: plugins.Handshake, + Plugins: plugin.PluginSet{"configmanager_token_store": &plugins.TokenStoreGRPCPlugin{}}, + Cmd: exec.Command(path), + AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + }) + + // 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.(plugins.TokenStore) + + p := &Plugin{ + ClientCleanUp: client.Kill, + tokenStore: ts, + } + return p, nil +} + +func (p *Plugin) GetValue(token *config.ParsedTokenConfig) (string, error) { + result, err := p.tokenStore.Value(token.StoreToken(), []byte(token.Metadata())) + if err != nil { + return "", err + } + return result, nil +} diff --git a/internal/store/plugin_test.go b/internal/store/plugin_test.go new file mode 100644 index 0000000..f646841 --- /dev/null +++ b/internal/store/plugin_test.go @@ -0,0 +1,34 @@ +package store_test + +import ( + "context" + "os" + "testing" + + "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/internal/store" +) + +func TestPlugin_GetValue_integration(t *testing.T) { + // as the plugin is technically a subprocess + // setting env vars at this level will affect the loaded plugin + os.Setenv("AWS_REGION", "eu-west-1") + os.Setenv("AWS_PROFILE", "PROFILE_TO_USE") + np, err := store.New(context.TODO(), "../../plugins/awsparamstr/bin/awsparamstr", config.ParamStorePrefix) + if err != nil { + t.Fatal(err) + } + defer np.ClientCleanUp() + token, err := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + if err != nil { + t.Fatal(err) + } + token.WithSanitizedToken("/int-test/pocketbase/admin-pwd") + got, err := np.GetValue(token) + if err != nil { + t.Fatal(err) + } + if got == "" { + t.Error("empty...") + } +} 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..a4fdbc0 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -22,6 +22,6 @@ var ( type Strategy interface { // Value retrieves the underlying value for the token Value() (s string, e error) - // SetToken + // SetToken SetToken(s *config.ParsedTokenConfig) } diff --git a/internal/strategy/strategy.go b/internal/strategy/strategy.go index ac19a30..7aa8345 100644 --- a/internal/strategy/strategy.go +++ b/internal/strategy/strategy.go @@ -94,27 +94,27 @@ func (tr *TokenResponse) Value() string { 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) - }, + // 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) + // }, } } diff --git a/internal/strategy/strategy_test.go b/internal/strategy/strategy_test.go index acae5e1..c5fe3f7 100644 --- a/internal/strategy/strategy_test.go +++ b/internal/strategy/strategy_test.go @@ -2,9 +2,7 @@ package strategy_test import ( "context" - "fmt" "io" - "os" "testing" "github.com/DevLabFoundry/configmanager/v3/internal/config" @@ -12,7 +10,6 @@ import ( "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 { @@ -116,175 +113,175 @@ func Test_CustomStrategyFuncMap_add_own(t *testing.T) { } } -func Test_SelectImpl_With(t *testing.T) { +// 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") +// 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) +// 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 - } +// 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 := 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/plugins/awsparams/paramstore.go b/plugins/awsparams/paramstore.go deleted file mode 100644 index 9001e53..0000000 --- a/plugins/awsparams/paramstore.go +++ /dev/null @@ -1,67 +0,0 @@ -package main - -import ( - "context" - - "github.com/DevLabFoundry/configmanager/v3/internal/config" - "github.com/DevLabFoundry/configmanager/v3/plugins" - "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" -) - -type paramStoreApi interface { - GetParameter(ctx context.Context, params *ssm.GetParameterInput, optFns ...func(*ssm.Options)) (*ssm.GetParameterOutput, error) -} - -type ParamStore struct { - svc paramStoreApi - ctx context.Context - config *ParamStrConfig - token *config.ParsedTokenConfig -} - -type ParamStrConfig struct { - // reserved for potential future use -} - -func NewParamStore(ctx context.Context) (*ParamStore, error) { - cfg, err := awsConf.LoadDefaultConfig(ctx) - if err != nil { - return nil, err - } - c := ssm.NewFromConfig(cfg) - - return &ParamStore{ - svc: c, - ctx: ctx, - }, nil -} - -func (s *ParamStore) WithSvc(svc paramStoreApi) { - s.svc = svc -} - -func (imp *ParamStore) Value(token *plugins.MessagExchange) (string, error) { - // imp.logger.Info("%s", "Concrete implementation ParameterStore") - // imp.logger.Info("ParamStore Token: %s", token.Token) - - input := &ssm.GetParameterInput{ - Name: aws.String(token.Token), - WithDecryption: aws.Bool(true), - } - ctx, cancel := context.WithCancel(imp.ctx) - defer cancel() - - result, err := imp.svc.GetParameter(ctx, input) - if err != nil { - // imp.logger.Error(plugins.ImplementationNetworkErr, config.ParamStorePrefix, err, token) - return "", err - } - - if result.Parameter.Value != nil { - return *result.Parameter.Value, nil - } - // imp.logger.Error("value retrieved but empty for token: %v", imp.token) - return "", nil -} diff --git a/plugins/awsparams/main.go b/plugins/awsparamstr/README.md similarity index 91% rename from plugins/awsparams/main.go rename to plugins/awsparamstr/README.md index 4bd4ec6..cc07402 100644 --- a/plugins/awsparams/main.go +++ b/plugins/awsparamstr/README.md @@ -1,5 +1,13 @@ -package main +# AWS PARAM STORE Plugin +This is the `awsparamstr` implementation plugin built using the gp-plugin architecture from hashicorp... + + +## Alternate architecture + +Explored an alternate architecture using WASIP1 + +```go import ( "context" "encoding/binary" @@ -199,5 +207,10 @@ func StrategyTokenValue(tokenPtr, tokenLen, outPtr, outCap, outLenPtr uint32) in // 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 +`GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o awsparams.wasm` diff --git a/internal/store/paramstore.go b/plugins/awsparamstr/impl/paramstore.go similarity index 69% rename from internal/store/paramstore.go rename to plugins/awsparamstr/impl/paramstore.go index aa45ace..6255252 100644 --- a/internal/store/paramstore.go +++ b/plugins/awsparamstr/impl/paramstore.go @@ -1,10 +1,11 @@ -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/plugins" "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" @@ -17,9 +18,9 @@ type paramStoreApi interface { type ParamStore struct { svc paramStoreApi ctx context.Context - logger log.ILogger config *ParamStrConfig token *config.ParsedTokenConfig + logger log.ILogger } type ParamStrConfig struct { @@ -29,7 +30,6 @@ type ParamStrConfig struct { func NewParamStore(ctx context.Context, logger log.ILogger) (*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(plugins.ImplementationNetworkErr, config.ParamStorePrefix, err, token) return "", err } diff --git a/plugins/awsparams/paramstore_test.go b/plugins/awsparamstr/impl/paramstore_test.go similarity index 95% rename from plugins/awsparams/paramstore_test.go rename to plugins/awsparamstr/impl/paramstore_test.go index 84a0d23..cc64dfc 100644 --- a/plugins/awsparams/paramstore_test.go +++ b/plugins/awsparamstr/impl/paramstore_test.go @@ -1,4 +1,4 @@ -package main_test +package impl_test import ( "context" @@ -9,8 +9,8 @@ import ( "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/v3/plugins/awsparamstr/impl" "github.com/aws/aws-sdk-go-v2/service/ssm" "github.com/aws/aws-sdk-go-v2/service/ssm/types" ) @@ -131,13 +131,13 @@ 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(), 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() + + got, err := impl.Value(tt.token().StoreToken(), []byte{}) if err != nil { if err.Error() != tt.expect { t.Errorf(testutils.TestPhrase, err.Error(), tt.expect) diff --git a/plugins/awsparamstr/main.go b/plugins/awsparamstr/main.go new file mode 100644 index 0000000..c4b340f --- /dev/null +++ b/plugins/awsparamstr/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + "os" + + "github.com/DevLabFoundry/configmanager/v3/internal/log" + "github.com/DevLabFoundry/configmanager/v3/plugins" + "github.com/DevLabFoundry/configmanager/v3/plugins/awsparamstr/impl" + "github.com/hashicorp/go-plugin" +) + +// Here is a real implementation of KV that writes to a local file with +// the key name and the contents are the value of the key. +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) +} + +func main() { + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: plugins.Handshake, + Plugins: map[string]plugin.Plugin{ + "configmanager_token_store": &plugins.TokenStoreGRPCPlugin{Impl: &TokenStorePlugin{}}, + }, + // A non-nil value here enables gRPC serving for this plugin... + GRPCServer: plugin.DefaultGRPCServer, + }) +} diff --git a/plugins/awssecrets/main.go b/plugins/awssecrets/main.go deleted file mode 100644 index 8a03b12..0000000 --- a/plugins/awssecrets/main.go +++ /dev/null @@ -1,197 +0,0 @@ -package main - -import ( - "encoding/binary" - "strings" - "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, metadataStrPtr, metadataStrLen, 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 - } - - if metadataStrLen > 0 { - metadataStrBytes := bytesFromPtrLen(metadataStrPtr, metadataStrLen) - if !utf8.Valid(metadataStrBytes) { - if outLenPtr != 0 { - if lenCell := bytesFromPtrLen(metadataStrPtr, 4); len(lenCell) == 4 { - binary.LittleEndian.PutUint32(lenCell, uint32(len(tokenBytes))) - } - } - return plugins.ERR_INVALID_UTF8 - } - } - - // --- Business logic (replace with your real token strategy) --- - - inStr := string(tokenBytes) - outStr := "TOKEN_VALUE:" + strings.ToUpper(inStr) - outBytes := []byte(outStr) - // -------------------------------------------------------------- - - 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 `_instantiate` method -func main() {} - -// GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o awssecrets.wasm diff --git a/plugins/grpc.go b/plugins/grpc.go index 9d7ffc5..5f9ebf0 100644 --- a/plugins/grpc.go +++ b/plugins/grpc.go @@ -9,7 +9,7 @@ import ( // GRPCClient is an implementation of KV that talks over RPC. type GRPCClient struct{ client proto.TokenStoreClient } -func (m *GRPCClient) Get(key string, metadata []byte) (string, error) { +func (m *GRPCClient) Value(key string, metadata []byte) (string, error) { resp, err := m.client.Value(context.Background(), &proto.TokenValueRequest{ Token: key, Metadata: metadata, @@ -27,9 +27,9 @@ type GRPCServer struct { Impl TokenStore } -func (m *GRPCServer) Get( +func (m *GRPCServer) Value( ctx context.Context, req *proto.TokenValueRequest) (*proto.TokenValueResponse, error) { - v, err := m.Impl.Get(req.) - return &proto.GetResponse{Value: v}, err + v, err := m.Impl.Value(req.Token, req.Metadata) + return &proto.TokenValueResponse{Value: v}, err } diff --git a/plugins/interface.go b/plugins/interface.go index ee33294..5a3cd1b 100644 --- a/plugins/interface.go +++ b/plugins/interface.go @@ -5,34 +5,26 @@ import ( "google.golang.org/grpc" + "github.com/DevLabFoundry/configmanager/v3/plugins/proto" "github.com/hashicorp/go-plugin" - "github.com/hashicorp/go-plugin/examples/grpc/proto" ) // 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: "BASIC_PLUGIN", + MagicCookieKey: "CONFIGMANAGER_PLUGIN", MagicCookieValue: "hello", } -// PluginMap is the map of plugins we can dispense. +// // PluginMap is the map of plugins we can dispense. var PluginMap = map[string]plugin.Plugin{ - "kv_grpc": &TokenStoreGRPCPlugin{}, - // "kv": &KVPlugin{}, + "configmanager_token_store": &TokenStoreGRPCPlugin{}, } // TokenStore is the interface that we're exposing as a plugin. type TokenStore interface { - Get(token string, metadata []byte) (string, error) -} - -// This is the implementation of plugin.Plugin so we can serve/consume this. -type TokenStorePlugin struct { - // Concrete implementation, written in Go. This is only used for plugins - // that are written in Go. - Impl TokenStore + Value(token string, metadata []byte) (string, error) } // This is the implementation of plugin.GRPCPlugin so we can serve/consume this. @@ -45,10 +37,10 @@ type TokenStoreGRPCPlugin struct { } func (p *TokenStoreGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { - proto.RegisterKVServer(s, &GRPCServer{Impl: p.Impl}) + proto.RegisterTokenStoreServer(s, &GRPCServer{Impl: p.Impl}) return nil } func (p *TokenStoreGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { - return &GRPCClient{client: proto(c)}, nil + return &GRPCClient{client: proto.NewTokenStoreClient(c)}, nil } diff --git a/plugins/scaffolding.go b/plugins/scaffolding.go index f4bdce0..f317496 100644 --- a/plugins/scaffolding.go +++ b/plugins/scaffolding.go @@ -22,9 +22,3 @@ var ( ErrEmptyResponse = errors.New("value retrieved but empty for token") ErrServiceCallFailed = errors.New("failed to complete the service call") ) - -type MessagExchange struct { - Token string `json:"token"` - Metadata map[string]any `json:"metadata,omitempty"` - Version string `json:"version"` -} From 6f0a3ba8df9170c594fb03694b2ba8923d53b3ce Mon Sep 17 00:00:00 2001 From: dnitsch Date: Mon, 8 Dec 2025 15:10:51 +0000 Subject: [PATCH 06/20] fix: add init as wip element --- .gitignore | 7 +- .trivyignore.yaml | 7 + Dockerfile | 6 + eirctl.yaml | 54 ++- generator/generator.go | 88 ++-- generator/generatorvars.go | 22 + go.mod | 87 ++-- go.sum | 252 +++-------- internal/store/plugin.go | 12 +- internal/store/plugin_test.go | 14 +- internal/store/store.go | 97 +++- internal/strategy/strategy.go | 250 ++++++----- internal/strategy/strategy_test.go | 200 ++++----- plugins/README.md | 240 ++++++++++ plugins/awsparamstr/README.md | 214 +-------- plugins/awssecrets/.gitkeep | 0 plugins/grpc.go | 3 +- plugins/vault/README.md | 3 + plugins/vault/impl/hashivault.go | 173 +++++++ plugins/vault/impl/hashivault_test.go | 624 ++++++++++++++++++++++++++ plugins/vault/main.go | 34 ++ 21 files changed, 1649 insertions(+), 738 deletions(-) create mode 100644 .trivyignore.yaml create mode 100644 plugins/README.md create mode 100644 plugins/awssecrets/.gitkeep create mode 100644 plugins/vault/README.md create mode 100644 plugins/vault/impl/hashivault.go create mode 100644 plugins/vault/impl/hashivault_test.go create mode 100644 plugins/vault/main.go diff --git a/.gitignore b/.gitignore index 81bfbd1..0c0758a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,13 +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/eirctl.yaml b/eirctl.yaml index 7c64937..22db6e4 100644 --- a/eirctl.yaml +++ b/eirctl.yaml @@ -4,7 +4,8 @@ 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.7/shared/build/go/eirctl.yaml + - https://raw.githubusercontent.com/Ensono/eirctl/refs/tags/0.9.7/shared/security/eirctl.yaml contexts: bash: @@ -51,6 +52,19 @@ pipelines: - 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 + + scan:plugins: + - task: trivy:file:system:sbom + tasks: show:coverage: description: Opens the current coverage viewer for the the configmanager utility. @@ -66,6 +80,44 @@ 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" + GOPATH=/eirctl/.deps GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} CGO_ENABLED=0 go build -mod=readonly -buildvcs=false -ldflags="$ldflags" \ + -o ./plugins/$PLUGIN/bin/$PLUGIN-${BUILD_GOOS}-${BUILD_GOARCH}${BUILD_SUFFIX} ./plugins/$PLUGIN/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: | diff --git a/generator/generator.go b/generator/generator.go index 8dcef19..ca3243f 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -14,7 +14,7 @@ import ( "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" ) // Generator is the main struct holding the @@ -24,10 +24,11 @@ import ( // which wil be passed in a loop into a goroutine to perform the // relevant strategy network calls to the config store implementations type Generator struct { - Logger log.ILogger - strategy strategy.StrategyFuncMap - ctx context.Context - config config.GenVarsConfig + Logger log.ILogger + // strategy strategy.StrategyFuncMap + store *store.Store + ctx context.Context + config config.GenVarsConfig } type Opts func(*Generator) @@ -48,7 +49,7 @@ func new(ctx context.Context, opts ...Opts) *Generator { // return using default config config: *conf, } - g.strategy = nil + // g.strategy = nil // now apply additional opts for _, o := range opts { @@ -58,13 +59,13 @@ func new(ctx context.Context, opts ...Opts) *Generator { return g } -// WithStrategyMap -// -// Adds addtional funcs for storageRetrieval used for testing only -func (c *Generator) WithStrategyMap(sm strategy.StrategyFuncMap) *Generator { - c.strategy = sm - return c -} +// // WithStrategyMap +// // +// // Adds addtional funcs for storageRetrieval used for testing only +// func (c *Generator) WithStrategyMap(sm strategy.StrategyFuncMap) *Generator { +// c.strategy = sm +// return c +// } // WithConfig uses custom config func (c *Generator) WithConfig(cfg *config.GenVarsConfig) *Generator { @@ -97,6 +98,14 @@ func (c *Generator) Generate(tokens []string) (ReplacedToken, error) { return nil, err } + // initialise pugins here based on discovered tokens + // + s, err := store.Init(c.ctx, ntm.TokenSet()) + if err != nil { + return nil, err + } + + c.store = s // pass in default initialised retrieveStrategy // input should be rt, err := c.generate(ntm) @@ -145,14 +154,15 @@ func IsParsed(v any, trm ReplacedToken) bool { // 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 *Generator) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { - if len(ntm.normalizedTokenMap) < 1 { + 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)) + // initialise the stores here + // s := strategy.New(c.config, c.Logger, strategy.WithStrategyFuncMap(c.strategy)) // safe read of normalized token map // this will ensure that we are minimizing @@ -164,13 +174,20 @@ func (c *Generator) 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) + storeStrategy, err := c.store.GetImplementation(token.Prefix()) + if err != nil { + prsdTkn.resp.Err = err + return + } + // storeStrategy.GetValue(token) + v, err := storeStrategy.GetValue(token) if err != nil { prsdTkn.resp.Err = err return } - prsdTkn.resp = strategy.ExchangeToken(storeStrategy, token) + prsdTkn.resp.WithValue(v) }) } @@ -200,7 +217,9 @@ func (c *Generator) 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 +228,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 +241,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 *Generator) NormalizeRawToken(rtm *RawTokenConfig) NormalizedTokenSafe { - ntm := NormalizedTokenSafe{mu: &sync.Mutex{}, normalizedTokenMap: make(map[string]*NormalizedToken)} + 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/generatorvars.go b/generator/generatorvars.go index d2ac986..0e1bf8d 100644 --- a/generator/generatorvars.go +++ b/generator/generatorvars.go @@ -43,6 +43,28 @@ func (rtm *RawTokenConfig) RawTokenMap() map[string]*config.ParsedTokenConfig { return rtm.tokenMap } +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 // If it does contain one then it tries to parse diff --git a/go.mod b/go.mod index 4049a5c..2c30ca2 100644 --- a/go.mod +++ b/go.mod @@ -1,70 +1,54 @@ module github.com/DevLabFoundry/configmanager/v3 -go 1.25.4 +go 1.25.5 require ( - cloud.google.com/go/secretmanager v1.11.4 - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 - 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.1 - github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 - github.com/aws/aws-sdk-go-v2 v1.40.0 - github.com/aws/aws-sdk-go-v2/config v1.32.0 - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1 - github.com/aws/aws-sdk-go-v2/service/ssm v1.67.3 + github.com/aws/aws-sdk-go-v2 v1.40.1 + github.com/aws/aws-sdk-go-v2/config v1.32.3 + github.com/aws/aws-sdk-go-v2/service/ssm v1.67.5 github.com/go-test/deep v1.1.1 - github.com/googleapis/gax-go/v2 v2.12.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.77.0 + google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/hashicorp/yamux v0.1.2 // indirect - github.com/oklog/run v1.1.0 // indirect - go.opencensus.io v0.24.0 // 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 ( - cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/iam v1.1.5 // 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/a8m/envsubst v1.4.3 github.com/aws/aws-sdk-go v1.55.8 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.0 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.3 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 // 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.14 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 // indirect - github.com/aws/smithy-go v1.23.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 // indirect + github.com/aws/smithy-go v1.24.0 // 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.7 // 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 + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.6.2 + github.com/hashicorp/go-plugin v1.7.0 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 @@ -75,33 +59,16 @@ require ( github.com/hashicorp/hcl v1.0.1-vault-7 // indirect 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 - github.com/tetratelabs/wazero v1.10.1 - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // 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.45.0 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/oauth2 v0.33.0 // indirect - golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.38.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/time v0.14.0 // indirect - google.golang.org/api v0.155.0 // indirect - google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect - google.golang.org/grpc v1.61.2 - google.golang.org/protobuf v1.31.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect ) diff --git a/go.sum b/go.sum index 3008268..fa846d7 100644 --- a/go.sum +++ b/go.sum @@ -1,98 +1,52 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y= -cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= -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.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/secretmanager v1.11.4 h1:krnX9qpG2kR2fJ+u+uNyNo+ACVhplIAS4Pu7u+4gd+k= -cloud.google.com/go/secretmanager v1.11.4/go.mod h1:wreJlbS9Zdq21lMzWmJ0XhWW2ZxgPeahsqeV/vZoJ3w= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0 h1:JXg2dwJUmPB9JmtVmdEB16APJ7jurfbY5jnfXpJoRMc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.20.0/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= -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.1 h1:j0hhYS006eJ54vusoap0f2NVZ1YY3QnaAEnLM68f0SQ= -github.com/Azure/azure-sdk-for-go/sdk/data/aztables v1.4.1/go.mod h1:AdtInaXmK8eYmbjezRWgLz+Qs46nc9Up9GWGwteWNfw= -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/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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.40.0 h1:/WMUA0kjhZExjOQN2z3oLALDREea1A7TobfuiBrKlwc= -github.com/aws/aws-sdk-go-v2 v1.40.0/go.mod h1:c9pm7VwuW0UPxAEYGyTmyurVcNrbF6Rt/wixFqDhcjE= -github.com/aws/aws-sdk-go-v2/config v1.32.0 h1:T5WWJYnam9SzBLbsVYDu2HscLDe+GU1AUJtfcDAc/vA= -github.com/aws/aws-sdk-go-v2/config v1.32.0/go.mod h1:pSRm/+D3TxBixGMXlgtX4+MPO9VNtEEtiFmNpxksoxw= -github.com/aws/aws-sdk-go-v2/credentials v1.19.0 h1:7zm+ez+qEqLaNsCSRaistkvJRJv8sByDOVuCnyHbP7M= -github.com/aws/aws-sdk-go-v2/credentials v1.19.0/go.mod h1:pHKPblrT7hqFGkNLxqoS3FlGoPrQg4hMIa+4asZzBfs= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14 h1:PZHqQACxYb8mYgms4RZbhZG0a7dPW06xOjmaH0EJC/I= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.14/go.mod h1:VymhrMJUWs69D8u0/lZ7jSB6WgaG/NqHi3gX0aYf6U0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14 h1:bOS19y6zlJwagBfHxs0ESzr1XCOU2KXJCWcq3E2vfjY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.14/go.mod h1:1ipeGBMAxZ0xcTm6y6paC2C/J6f6OO7LBODV9afuAyM= +github.com/aws/aws-sdk-go-v2 v1.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= +github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/config v1.32.3 h1:cpz7H2uMNTDa0h/5CYL5dLUEzPSLo2g0NkbxTRJtSSU= +github.com/aws/aws-sdk-go-v2/config v1.32.3/go.mod h1:srtPKaJJe3McW6T/+GMBZyIPc+SeqJsNPJsd4mOYZ6s= +github.com/aws/aws-sdk-go-v2/credentials v1.19.3 h1:01Ym72hK43hjwDeJUfi1l2oYLXBAOR8gNSZNmXmvuas= +github.com/aws/aws-sdk-go-v2/credentials v1.19.3/go.mod h1:55nWF/Sr9Zvls0bGnWkRxUdhzKqj9uRNlPvgV1vgxKc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 h1:utxLraaifrSBkeyII9mIbVwXXWrZdlPO7FIKmyLCEcY= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15/go.mod h1:hW6zjYUDQwfz3icf4g2O41PHi77u10oAzJ84iSzR/lo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= 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.14 h1:FIouAnCE46kyYqyhs0XEBDFFSREtdnr8HQuLPQPLCrY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.14/go.mod h1:UTwDc5COa5+guonQU8qBikJo1ZJ4ln2r1MkF7Dqag1E= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1 h1:w6a0H79HrHf3lr+zrw+pSzR5B+caiQFAKiNHlrUcnoc= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.40.1/go.mod h1:c6Vg0BRiU7v0MVhHupw90RyL120QBwAMLbDCzptGeMk= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.1 h1:BDgIUYGEo5TkayOWv/oBLPphWwNm/A91AebUjAu5L5g= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.1/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk= -github.com/aws/aws-sdk-go-v2/service/ssm v1.67.3 h1:ofiQvKwka2E3T8FXBsU1iWj7Yvk2wd1p4ZCdS6qGiKQ= -github.com/aws/aws-sdk-go-v2/service/ssm v1.67.3/go.mod h1:+nlWvcgDPQ56mChEBzTC0puAMck+4onOFaHg5cE+Lgg= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.4 h1:U//SlnkE1wOQiIImxzdY5PXat4Wq+8rlfVEw4Y7J8as= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.4/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8 h1:MvlNs/f+9eM0mOjD9JzBUbf5jghyTk3p+O9yHMXX94Y= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.8/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.1 h1:GdGmKtG+/Krag7VfyOXV17xjTCz0i9NT+JnqLTOI5nA= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.1/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso= -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/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= -github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 h1:3/u/4yZOffg5jdNk1sDpOQ4Y+R6Xbh+GzpDrSZjuy3U= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15/go.mod h1:4Zkjq0FKjE78NKjabuM4tRXKFzUJWXgP0ItEZK8l7JU= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 h1:d/6xOGIllc/XW1lzG9a4AUBMmpLA9PXcQnVPTuHHcik= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.3/go.mod h1:fQ7E7Qj9GiW8y0ClD7cUJk3Bz5Iw8wZkWDHsTe8vDKs= +github.com/aws/aws-sdk-go-v2/service/ssm v1.67.5 h1:YKGgwB1rye0JpV10Bfma3cZdQzX61j2HPWQw+YxWvrQ= +github.com/aws/aws-sdk-go-v2/service/ssm v1.67.5/go.mod h1:eBDSa0vuYB0lalpNxavIw80Q4Ksy08bhHHbT0aWa4tE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 h1:8sTTiw+9yuNXcfWeqKF2x01GqCF49CpP4Z9nKrrk/ts= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.6/go.mod h1:8WYg+Y40Sn3X2hioaaWAAIngndR8n1XFdRPPX+7QBaM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 h1:E+KqWoVsSrj1tJ6I/fjDIu5xoS2Zacuu1zT+H7KtiIk= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11/go.mod h1:qyWHz+4lvkXcr3+PoGlGHEI+3DLLiU6/GdrFfMaAhB0= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 h1:tzMkjh0yTChUqJDgGkcDdxvZDSrJ/WB6R6ymI5ehqJI= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.3/go.mod h1:T270C0R5sZNLbWUe8ueiAF42XSZxxPocTaGSgs5c/60= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= 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.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= 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= @@ -100,43 +54,12 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre 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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas= -github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= 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= @@ -147,8 +70,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 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.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= -github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= +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= @@ -173,16 +96,15 @@ 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/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= -github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +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/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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -190,8 +112,6 @@ 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= @@ -204,23 +124,22 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG 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.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= -github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -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/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/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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 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= @@ -233,20 +152,11 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 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= -github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8= -github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= 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.46.1 h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= -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= @@ -257,89 +167,33 @@ go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6 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= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= -golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= -golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= -golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 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-20200930185726-fdedc70b468f/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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.155.0 h1:vBmGhCYs0djJttDNynWo44zosHlPvHmA0XiN2zP2DtA= -google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= -google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= -google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3 h1:EWIeHfGuUf00zrVZGEgYFxok7plSAXBGcH7NNdMAWvA= -google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 h1:/jFB8jK5R3Sq3i/lmeZO0cATSzFfZaJq1J2Euan3XKU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.61.2 h1:TzJay21lXCf7BiNFKl7mSskt5DlkKAumAYTs52SpJeo= -google.golang.org/grpc v1.61.2/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/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= @@ -351,5 +205,3 @@ 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= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/store/plugin.go b/internal/store/plugin.go index ddfd739..ee6fe83 100644 --- a/internal/store/plugin.go +++ b/internal/store/plugin.go @@ -9,11 +9,8 @@ import ( "github.com/hashicorp/go-plugin" ) -// Plugin is responsible for managing plugins within configmanager -// -// It includes the following methods -// - fetch plugins from known sources -// - maintains a list of tokens answerable by a specified pluginEngine +// 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 @@ -22,8 +19,8 @@ type Plugin struct { tokenStore plugins.TokenStore } -// New Plugin gets called once per implementation -func New(ctx context.Context, path string, prefix config.ImplementationPrefix) (*Plugin, error) { +// 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: plugins.Handshake, @@ -31,7 +28,6 @@ func New(ctx context.Context, path string, prefix config.ImplementationPrefix) ( Cmd: exec.Command(path), AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, }) - // Connect via RPC rpcClient, err := client.Client() if err != nil { diff --git a/internal/store/plugin_test.go b/internal/store/plugin_test.go index f646841..47a840a 100644 --- a/internal/store/plugin_test.go +++ b/internal/store/plugin_test.go @@ -2,33 +2,41 @@ package store_test import ( "context" + "fmt" "os" + "runtime" "testing" "github.com/DevLabFoundry/configmanager/v3/internal/config" "github.com/DevLabFoundry/configmanager/v3/internal/store" ) +// TODO: make the implementation of the plugin system more testable func TestPlugin_GetValue_integration(t *testing.T) { + t.Skip() // as the plugin is technically a subprocess // setting env vars at this level will affect the loaded plugin os.Setenv("AWS_REGION", "eu-west-1") - os.Setenv("AWS_PROFILE", "PROFILE_TO_USE") - np, err := store.New(context.TODO(), "../../plugins/awsparamstr/bin/awsparamstr", config.ParamStorePrefix) + os.Setenv("AWS_PROFILE", "FOO") + tp := fmt.Sprintf("../../.configmanager/plugins/awsparamstr/awsparamstr-%s-%s", runtime.GOOS, runtime.GOARCH) + np, err := store.NewPlugin(context.TODO(), tp) if err != nil { t.Fatal(err) } + defer np.ClientCleanUp() token, err := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) if err != nil { t.Fatal(err) } + token.WithSanitizedToken("/int-test/pocketbase/admin-pwd") got, err := np.GetValue(token) if err != nil { t.Fatal(err) } - if got == "" { + + if len(got) < 1 { t.Error("empty...") } } diff --git a/internal/store/store.go b/internal/store/store.go index a4fdbc0..79ae798 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -1,18 +1,24 @@ package store import ( + "context" "errors" + "fmt" + "os" + "path" + "runtime" + "strings" + "sync" "github.com/DevLabFoundry/configmanager/v3/internal/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 @@ -25,3 +31,90 @@ type Strategy interface { // SetToken SetToken(s *config.ParsedTokenConfig) } + +// +// 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 Store struct { + pluginLocation []string + plugin pluginMap + // PluginCleanUp func() +} + +func Init(ctx context.Context, implt []string) (*Store, error) { + pm := pluginMap{mu: &sync.Mutex{}, m: make(map[string]*Plugin)} + + // l := []string{""} + // + for _, plugin := range implt { + plpath, err := findPlugin(plugin) + if err != nil { + return nil, err + } + p, err := NewPlugin(ctx, plpath) + pm.Add(plugin, p) + } + return &Store{plugin: pm}, nil +} + +func (s *Store) GetImplementation(implemenation config.ImplementationPrefix) (plugin *Plugin, err error) { + var exists bool + if plugin, exists = s.plugin.m[strings.ToLower(string(implemenation))]; exists { + return plugin, nil + } + return nil, ErrPluginNotFound +} + +// PluginCleanUp ensures the plugins are properly shut down +func (s *Store) PluginCleanUp() { + s.plugin.mu.Lock() + defer s.plugin.mu.Unlock() + for _, plugin := range s.plugin.m { + plugin.ClientCleanUp() + } +} + +// findPlugin ensures the path exists and search the following locations +// +// current dir +// home dir +func 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 + } + for _, p := range []string{cwd, hd} { + 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 "", ErrPluginNotFound +} diff --git a/internal/strategy/strategy.go b/internal/strategy/strategy.go index 7aa8345..9548f8b 100644 --- a/internal/strategy/strategy.go +++ b/internal/strategy/strategy.go @@ -1,124 +1,132 @@ // 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 -} +// 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.val, cr.Err = s.Value() +// return cr +// } + +// 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 +// } + +// 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 index c5fe3f7..b567e52 100644 --- a/internal/strategy/strategy_test.go +++ b/internal/strategy/strategy_test.go @@ -1,117 +1,117 @@ package strategy_test -import ( - "context" - "io" - "testing" +// import ( +// "context" +// "io" +// "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/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" +// ) -type mockGenerate struct { - inToken, value string - err error -} +// type mockGenerate struct { +// inToken, value string +// err error +// } -func (m mockGenerate) SetToken(s *config.ParsedTokenConfig) { -} +// func (m mockGenerate) SetToken(s *config.ParsedTokenConfig) { +// } -func (m mockGenerate) Value() (s string, e error) { - return m.value, m.err -} +// 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" - }`) +// 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_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) { +// 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") +// 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 - } +// 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})) +// 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) +// store, _ := s.GetImplementation(context.TODO(), token) +// _ = strategy.ExchangeToken(store, token) - if called != 1 { - t.Errorf(testutils.TestPhraseWithContext, "custom func not called", called, 1) - } - }) - } -} +// if called != 1 { +// t.Errorf(testutils.TestPhraseWithContext, "custom func not called", called, 1) +// } +// }) +// } +// } // func Test_SelectImpl_With(t *testing.T) { diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..5ed18b5 --- /dev/null +++ b/plugins/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/plugins/awsparamstr/README.md b/plugins/awsparamstr/README.md index cc07402..8f4fda3 100644 --- a/plugins/awsparamstr/README.md +++ b/plugins/awsparamstr/README.md @@ -1,216 +1,4 @@ # AWS PARAM STORE Plugin -This is the `awsparamstr` implementation plugin built using the gp-plugin architecture from hashicorp... +This is the `awsparamstr` implementation plugin built using the go-plugin architecture from hashicorp... - -## Alternate architecture - -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/plugins/awssecrets/.gitkeep b/plugins/awssecrets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/plugins/grpc.go b/plugins/grpc.go index 5f9ebf0..c59d35f 100644 --- a/plugins/grpc.go +++ b/plugins/grpc.go @@ -6,7 +6,8 @@ import ( "github.com/DevLabFoundry/configmanager/v3/plugins/proto" ) -// GRPCClient is an implementation of KV that talks over RPC. +// 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) { diff --git a/plugins/vault/README.md b/plugins/vault/README.md new file mode 100644 index 0000000..27b590e --- /dev/null +++ b/plugins/vault/README.md @@ -0,0 +1,3 @@ +# Hashicorp Vault + + diff --git a/plugins/vault/impl/hashivault.go b/plugins/vault/impl/hashivault.go new file mode 100644 index 0000000..75ba351 --- /dev/null +++ b/plugins/vault/impl/hashivault.go @@ -0,0 +1,173 @@ +package impl + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/internal/log" + "github.com/DevLabFoundry/configmanager/v3/plugins" + + vault "github.com/hashicorp/vault/api" + auth "github.com/hashicorp/vault/api/auth/aws" +) + +// HashiVaultHelper provides a broken up string +type HashiVaultHelper struct { + Path string + Token string +} + +type hashiVaultApi interface { + Get(ctx context.Context, secretPath string) (*vault.KVSecret, error) + GetVersion(ctx context.Context, secretPath string, version int) (*vault.KVSecret, error) +} + +type VaultStore struct { + svc hashiVaultApi + ctx context.Context + logger log.ILogger + config *VaultConfig + token *config.ParsedTokenConfig + strippedToken string +} + +// VaultConfig holds the parseable metadata struct +type VaultConfig struct { + Version string `json:"version"` + Role string `json:"iam_role"` +} + +func NewVaultStore(ctx context.Context, token *config.ParsedTokenConfig, logger log.ILogger) (*VaultStore, error) { + storeConf := &VaultConfig{} + _ = token.ParseMetadata(storeConf) + imp := &VaultStore{ + ctx: ctx, + logger: logger, + config: storeConf, + token: token, + } + + config := vault.DefaultConfig() + vt := SplitHashiVaultToken(token.StoreToken()) + imp.strippedToken = vt.Token + client, err := vault.NewClient(config) + if err != nil { + return nil, fmt.Errorf("%v\n%w", err, plugins.ErrClientInitialization) + } + + if strings.HasPrefix(os.Getenv("VAULT_TOKEN"), "aws_iam") { + awsclient, err := newVaultStoreWithAWSAuthIAM(client, storeConf.Role) + if err != nil { + return nil, err + } + client = awsclient + } + imp.svc = client.KVv2(vt.Path) + return imp, nil +} + +func (s *VaultStore) WithSvc(svc hashiVaultApi) { + s.svc = svc +} + +// newVaultStoreWithAWSAuthIAM returns an initialised client with AWSIAMAuth +// EC2 auth type is not supported currently +func newVaultStoreWithAWSAuthIAM(client *vault.Client, role string) (*vault.Client, error) { + if len(role) < 1 { + return nil, fmt.Errorf("role provided is empty, EC2 auth not supported") + } + awsAuth, err := auth.NewAWSAuth( + auth.WithRole(role), + ) + if err != nil { + return nil, fmt.Errorf("unable to initialize AWS auth method: %s. %w", err, plugins.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, plugins.ErrClientInitialization) + } + if authInfo == nil { + return nil, fmt.Errorf("no auth info was returned after login") + } + + return client, nil +} + +// setTokenVal +// imp.token is already set in the Vault constructor +// +// This happens inside the New func call +// due to the way the client needs to be +// initialised with a mountpath +// and mountpath is part of the token so it is set then +func (imp *VaultStore) SetToken(token *config.ParsedTokenConfig) {} + +// getTokenValue implements the underlying techonology +// token retrieval and returns a stringified version +// of the secret +func (imp *VaultStore) Value() (string, error) { + imp.logger.Info("%s", "Concrete implementation HashiVault") + imp.logger.Info("Getting Secret: %s", imp.token) + + ctx, cancel := context.WithCancel(imp.ctx) + defer cancel() + + secret, err := imp.getSecret(ctx, imp.strippedToken, imp.config.Version) + if err != nil { + imp.logger.Error(plugins.ImplementationNetworkErr, imp.token.Prefix(), err, imp.token.String()) + return "", err + } + + if secret.Data != nil { + resp, err := marshall(secret.Data) + if err != nil { + imp.logger.Error("marshalling error: %s", err.Error()) + return "", err + } + imp.logger.Debug("marhalled kvv2: %s", resp) + return resp, nil + } + + imp.logger.Error("value retrieved but empty for token: %v", imp.token) + return "", nil +} + +func (imp *VaultStore) getSecret(ctx context.Context, token string, version string) (*vault.KVSecret, error) { + if version != "" { + v, err := strconv.Atoi(version) + if err != nil { + return nil, fmt.Errorf("unable to parse version into an integer: %s", err.Error()) + } + return imp.svc.GetVersion(ctx, token, v) + } + return imp.svc.Get(ctx, token) +} + +func SplitHashiVaultToken(token string) HashiVaultHelper { + vh := HashiVaultHelper{} + // split token to extract the mount path + s := strings.Split(strings.TrimPrefix(token, "/"), "___") + // grab token and trim prefix if slash + vh.Token = strings.TrimPrefix(strings.Join(s[1:], ""), "/") + // assign mount path as extracted from input token + vh.Path = s[0] + return vh +} + +// marshall converts map[string]any into a JSON +// object. Secrets should only be a single level +// deep. +func marshall(secret map[string]any) (string, error) { + b, err := json.Marshal(secret) + if err != nil { + return "", err + } + return string(b), nil +} diff --git a/plugins/vault/impl/hashivault_test.go b/plugins/vault/impl/hashivault_test.go new file mode 100644 index 0000000..8e6f94a --- /dev/null +++ b/plugins/vault/impl/hashivault_test.go @@ -0,0 +1,624 @@ +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/testutils" + "github.com/DevLabFoundry/configmanager/v3/plugins/vault/impl" + vault "github.com/hashicorp/vault/api" +) + +func TestMountPathExtract(t *testing.T) { + ttests := map[string]struct { + token func() *config.ParsedTokenConfig + expect string + }{ + "without leading slash": { + func() *config.ParsedTokenConfig { + // "VAULT://secret___/demo/configmanager" + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/demo/configmanager") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, "secret"}, + "with leading slash": { + func() *config.ParsedTokenConfig { + // "VAULT:///secret___/demo/configmanager", + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("/secret___/demo/configmanager") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, "secret"}, + "with underscore in path name": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("_secret___/demo/configmanager") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, "_secret"}, + "with double underscore in path name": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("__secret___/demo/configmanager") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, "__secret"}, + "with multiple paths in mountpath": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret/bar/path___/demo/configmanager") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, "secret/bar/path"}, + } + for name, tt := range ttests { + t.Run(name, func(t *testing.T) { + got := impl.SplitHashiVaultToken(tt.token().StoreToken()) + if got.Path != tt.expect { + t.Errorf("got %q, expected %q", got, tt.expect) + } + }) + } +} + +type mockVaultApi struct { + g func(ctx context.Context, secretPath string) (*vault.KVSecret, error) + gv func(ctx context.Context, secretPath string, version int) (*vault.KVSecret, error) +} + +func (m mockVaultApi) Get(ctx context.Context, secretPath string) (*vault.KVSecret, error) { + return m.g(ctx, secretPath) +} + +func (m mockVaultApi) GetVersion(ctx context.Context, secretPath string, version int) (*vault.KVSecret, error) { + return m.gv(ctx, secretPath, version) +} + +func TestVaultScenarios(t *testing.T) { + + ttests := map[string]struct { + token func() *config.ParsedTokenConfig + expect string + mockClient func(t *testing.T) mockVaultApi + setupEnv func() func() + }{ + "happy return": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/foo") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + `{"foo":"test2130-9sd-0ds"}`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { + t.Helper() + if secretPath != "foo" { + t.Errorf("got %v; want %s", secretPath, `foo`) + } + m := make(map[string]interface{}) + m["foo"] = "test2130-9sd-0ds" + return &vault.KVSecret{Data: m}, nil + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "129378y1231283") + return func() { + os.Clearenv() + } + }, + }, + "incorrect json": { + func() *config.ParsedTokenConfig { + // "VAULT://secret___/foo", + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/foo") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + `json: unsupported type: func() error`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { + t.Helper() + if secretPath != "foo" { + t.Errorf("got %v; want %s", secretPath, `foo`) + } + m := make(map[string]interface{}) + m["error"] = func() error { return fmt.Errorf("ddodod") } + return &vault.KVSecret{Data: m}, nil + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "129378y1231283") + return func() { + os.Clearenv() + } + }, + }, + "another return": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret/engine1___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + `{"foo1":"test2130-9sd-0ds","foo2":"dsfsdf3454456"}`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { + t.Helper() + if secretPath != "some/other/foo2" { + t.Errorf("got %v; want %s", secretPath, `some/other/foo2`) + } + m := make(map[string]interface{}) + m["foo1"] = "test2130-9sd-0ds" + m["foo2"] = "dsfsdf3454456" + return &vault.KVSecret{Data: m}, nil + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "129378y1231283") + return func() { + os.Clearenv() + } + }, + }, + "not found": { + func() *config.ParsedTokenConfig { + // "VAULT://secret___/foo", + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/foo") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + `secret not found`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { + t.Helper() + if secretPath != "foo" { + t.Errorf("got %v; want %s", secretPath, `foo`) + } + return nil, fmt.Errorf("secret not found") + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "129378y1231283") + return func() { + os.Clearenv() + } + }, + }, + "403": { + func() *config.ParsedTokenConfig { + // "VAULT://secret___/some/other/foo2", + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + `client 403`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { + t.Helper() + if secretPath != "some/other/foo2" { + t.Errorf("got %v; want %s", secretPath, `some/other/foo2`) + } + return nil, fmt.Errorf("client 403") + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "129378y1231283") + return func() { + os.Clearenv() + } + }, + }, + "found but empty": { + func() *config.ParsedTokenConfig { + // "VAULT://secret___/some/other/foo2", + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + // config.NewConfig(), + `{}`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + mv.g = func(ctx context.Context, secretPath string) (*vault.KVSecret, error) { + t.Helper() + if secretPath != "some/other/foo2" { + t.Errorf("got %v; want %s", secretPath, `some/other/foo2`) + } + m := make(map[string]interface{}) + return &vault.KVSecret{Data: m}, nil + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "129378y1231283") + return func() { + os.Clearenv() + } + }, + }, + "found but nil returned": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + "", + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + 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`) + } + return &vault.KVSecret{Data: nil}, nil + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "129378y1231283") + return func() { + os.Clearenv() + } + }, + }, + "version provided correctly": { + func() *config.ParsedTokenConfig { + // "VAULT://secret___/some/other/foo2[version=1]", + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("version=1") + return tkn + }, + `{"foo2":"dsfsdf3454456"}`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + 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`) + } + m := make(map[string]interface{}) + m["foo2"] = "dsfsdf3454456" + return &vault.KVSecret{Data: m}, nil + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "129378y1231283") + return func() { + os.Clearenv() + } + }, + }, + "version provided but unable to parse": { + func() *config.ParsedTokenConfig { + // "VAULT://secret___/some/other/foo2[version=1a]", + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("version=1a") + return tkn + }, + "unable to parse version into an integer: strconv.Atoi: parsing \"1a\": invalid syntax", + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + 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`) + } + return nil, nil + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "129378y1231283") + return func() { + os.Clearenv() + } + }, + }, + "vault rate limit incorrect": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("") + return tkn + }, + `error encountered setting up default configuration: VAULT_RATE_LIMIT was provided but incorrectly formatted +failed to initialize the client`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + 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`) + } + return &vault.KVSecret{Data: nil}, nil + } + return mv + }, + func() func() { + os.Setenv("VAULT_TOKEN", "") + os.Setenv("VAULT_RATE_LIMIT", "wrong") + return func() { + os.Clearenv() + } + }, + }, + } + + for name, tt := range ttests { + t.Run(name, func(t *testing.T) { + tearDown := tt.setupEnv() + defer tearDown() + + i, err := impl.NewVaultStore(context.TODO(), tt.token(), log.New(io.Discard)) + if err != nil { + if err.Error() != tt.expect { + t.Fatalf("failed to init hashivault, %v", err.Error()) + } + return + } + + 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) + } + return + } + if got != tt.expect { + t.Errorf(testutils.TestPhrase, got, tt.expect) + } + }) + } +} + +func TestAwsIamAuth(t *testing.T) { + ttests := map[string]struct { + token func() *config.ParsedTokenConfig + expect string + mockClient func(t *testing.T) mockVaultApi + mockHanlder func(t *testing.T) http.Handler + setupEnv func(addr string) func() + }{ + "aws_iam auth no role specified": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("version=1") + return tkn + }, + "role provided is empty, EC2 auth not supported", + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + 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`) + } + return &vault.KVSecret{Data: nil}, nil + } + return mv + }, + func(t *testing.T) http.Handler { + return nil + }, + func(_ string) func() { + os.Setenv("VAULT_TOKEN", "aws_iam") + os.Setenv("AWS_ACCESS_KEY_ID", "1280qwed9u9nsc9fdsbv9gsfrd") + os.Setenv("AWS_SECRET_ACCESS_KEY", "SED)SDVfdv0jfds08sdfgu09sd943tj4fELH/") + os.Setenv("AWS_SESSION_TOKEN", "IQoJb3JpZ2luX2VjELH//////////wEaCWV1LXdlc3QtMiJIMEYCIQDPU6UGJ0...df.fdgdfg.dfg.gdf.dgf") + os.Setenv("AWS_REGION", "eu-west-1") + return func() { + os.Clearenv() + } + }, + }, + "aws_iam auth incorrectly formatted request": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("version=1,iam_role=not_a_role") + return tkn + }, + `unable to login to AWS auth method: unable to log in to auth method: unable to log in with AWS auth: Error making API request. + +URL: PUT %s/v1/auth/aws/login +Code: 400. Raw Message: + +incorrect values supplied. failed to initialize the client`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + 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`) + } + return &vault.KVSecret{Data: nil}, nil + } + return mv + }, + func(t *testing.T) http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/v1/auth/aws/login", func(w http.ResponseWriter, r *http.Request) { + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(400) + w.Write([]byte(`incorrect values supplied`)) + }) + return mux + }, + func(addr string) func() { + os.Setenv("VAULT_TOKEN", "aws_iam") + os.Setenv("VAULT_ADDR", addr) + os.Setenv("AWS_ACCESS_KEY_ID", "1280qwed9u9nsc9fdsbv9gsfrd") + os.Setenv("AWS_SECRET_ACCESS_KEY", "SED)SDVfdv0jfds08sdfgu09sd943tj4fELH/") + os.Setenv("AWS_SESSION_TOKEN", "IQoJb3JpZ2luX2VjELH//////////wEaCWV1LXdlc3QtMiJIMEYCIQDPU6UGJ0...df.fdgdfg.dfg.gdf.dgf") + os.Setenv("AWS_REGION", "eu-west-1") + return func() { + os.Clearenv() + } + }, + }, + "aws_iam auth success": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("iam_role=arn:aws:iam::1111111:role/i-orchestration") + return tkn + }, + // + `{"foo2":"dsfsdf3454456"}`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + 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`) + } + m := make(map[string]any) + m["foo2"] = "dsfsdf3454456" + return &vault.KVSecret{Data: m}, nil + } + return mv + }, + func(t *testing.T) http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/v1/auth/aws/login", func(w http.ResponseWriter, r *http.Request) { + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Write([]byte(`{"auth":{"client_token": "fooresddfasdsasad"}}`)) + }) + return mux + }, + func(addr string) func() { + os.Setenv("VAULT_TOKEN", "aws_iam") + os.Setenv("VAULT_ADDR", addr) + os.Setenv("AWS_ACCESS_KEY_ID", "1280qwed9u9nsc9fdsbv9gsfrd") + os.Setenv("AWS_SECRET_ACCESS_KEY", "SED)SDVfdv0jfds08sdfgu09sd943tj4fELH/") + os.Setenv("AWS_SESSION_TOKEN", "IQoJb3JpZ2luX2VjELH//////////wEaCWV1LXdlc3QtMiJIMEYCIQDPU6UGJ0...df.fdgdfg.dfg.gdf.dgf") + os.Setenv("AWS_REGION", "eu-west-1") + return func() { + os.Clearenv() + } + }, + }, + "aws_iam auth no token returned": { + func() *config.ParsedTokenConfig { + tkn, _ := config.NewToken(config.HashicorpVaultPrefix, *config.NewConfig()) + tkn.WithSanitizedToken("secret___/some/other/foo2") + tkn.WithKeyPath("") + tkn.WithMetadata("iam_role=arn:aws:iam::1111111:role/i-orchestration") + return tkn + }, + `unable to login to AWS auth method: response did not return ClientToken, client token not set. failed to initialize the client`, + func(t *testing.T) mockVaultApi { + mv := mockVaultApi{} + 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`) + } + m := make(map[string]interface{}) + m["foo2"] = "dsfsdf3454456" + return &vault.KVSecret{Data: m}, nil + } + return mv + }, + func(t *testing.T) http.Handler { + mux := http.NewServeMux() + mux.HandleFunc("/v1/auth/aws/login", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Write([]byte(`{"auth":{}}`)) + }) + return mux + }, + func(addr string) func() { + os.Setenv("VAULT_TOKEN", "aws_iam") + os.Setenv("VAULT_ADDR", addr) + os.Setenv("AWS_ACCESS_KEY_ID", "1280qwed9u9nsc9fdsbv9gsfrd") + os.Setenv("AWS_SECRET_ACCESS_KEY", "SED)SDVfdv0jfds08sdfgu09sd943tj4fELH/") + os.Setenv("AWS_SESSION_TOKEN", "IQoJb3JpZ2luX2VjELH//////////wEaCWV1LXdlc3QtMiJIMEYCIQDPU6UGJ0...df.fdgdfg.dfg.gdf.dgf") + os.Setenv("AWS_REGION", "eu-west-1") + return func() { + os.Clearenv() + } + }, + }, + } + + for name, tt := range ttests { + t.Run(name, func(t *testing.T) { + // + ts := httptest.NewServer(tt.mockHanlder(t)) + tearDown := tt.setupEnv(ts.URL) + defer tearDown() + i, err := impl.NewVaultStore(context.TODO(), tt.token(), log.New(io.Discard)) + 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.Fatalf("failed to init hashivault, %v", err.Error()) + } + return + } + + 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) + } + return + } + if got != tt.expect { + t.Errorf(testutils.TestPhrase, got, tt.expect) + } + }) + } +} diff --git a/plugins/vault/main.go b/plugins/vault/main.go new file mode 100644 index 0000000..c4b340f --- /dev/null +++ b/plugins/vault/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "context" + "os" + + "github.com/DevLabFoundry/configmanager/v3/internal/log" + "github.com/DevLabFoundry/configmanager/v3/plugins" + "github.com/DevLabFoundry/configmanager/v3/plugins/awsparamstr/impl" + "github.com/hashicorp/go-plugin" +) + +// Here is a real implementation of KV that writes to a local file with +// the key name and the contents are the value of the key. +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) +} + +func main() { + plugin.Serve(&plugin.ServeConfig{ + HandshakeConfig: plugins.Handshake, + Plugins: map[string]plugin.Plugin{ + "configmanager_token_store": &plugins.TokenStoreGRPCPlugin{Impl: &TokenStorePlugin{}}, + }, + // A non-nil value here enables gRPC serving for this plugin... + GRPCServer: plugin.DefaultGRPCServer, + }) +} From bef899b45560727c49186163de061ab7586702ac Mon Sep 17 00:00:00 2001 From: dnitsch Date: Thu, 19 Mar 2026 20:45:03 +0000 Subject: [PATCH 07/20] fix: rejig packages --- cmd/configmanager/configmanager.go | 2 +- {internal/config => config}/config.go | 12 +- {internal/config => config}/config_test.go | 34 +- configmanager.go | 2 +- configmanager_test.go | 2 +- generator/generator.go | 35 +- generator/generator_test.go | 435 ++++++++++---------- generator/generatorvars.go | 2 +- go.mod | 48 +-- go.sum | 49 +++ internal/cmdutils/cmdutils.go | 2 +- internal/cmdutils/cmdutils_test.go | 2 +- internal/cmdutils/postprocessor.go | 2 +- internal/cmdutils/postprocessor_test.go | 2 +- internal/lexer/lexer.go | 75 ++-- internal/lexer/lexer_test.go | 111 ++--- internal/parser/parser.go | 60 +-- internal/parser/parser_test.go | 2 +- internal/store/plugin.go | 2 +- internal/store/plugin_test.go | 14 +- internal/store/store.go | 44 +- internal/{config => token}/token.go | 6 +- internal/token/token_test.go | 25 ++ plugins/awsparamstr/go.mod | 46 +++ plugins/awsparamstr/go.sum | 109 +++++ plugins/awsparamstr/impl/paramstore.go | 10 +- plugins/awsparamstr/impl/paramstore_test.go | 29 +- plugins/awsparamstr/main.go | 21 +- plugins/empty/main.go | 35 ++ plugins/vault/impl/hashivault.go | 8 +- plugins/vault/impl/hashivault_test.go | 47 ++- plugins/vault/main.go | 18 +- 32 files changed, 767 insertions(+), 524 deletions(-) rename {internal/config => config}/config.go (92%) rename {internal/config => config}/config_test.go (81%) rename internal/{config => token}/token.go (95%) create mode 100644 internal/token/token_test.go create mode 100644 plugins/awsparamstr/go.mod create mode 100644 plugins/awsparamstr/go.sum create mode 100644 plugins/empty/main.go diff --git a/cmd/configmanager/configmanager.go b/cmd/configmanager/configmanager.go index 383d229..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" ) diff --git a/internal/config/config.go b/config/config.go similarity index 92% rename from internal/config/config.go rename to config/config.go index f7b3713..ac8f9b7 100644 --- a/internal/config/config.go +++ b/config/config.go @@ -41,14 +41,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") ) @@ -144,8 +136,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 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 4da0d1b..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" ) 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/generator/generator.go b/generator/generator.go index ca3243f..0d4010f 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -10,7 +10,7 @@ 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" @@ -47,9 +47,9 @@ func new(ctx context.Context, opts ...Opts) *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 { @@ -59,13 +59,13 @@ func new(ctx context.Context, opts ...Opts) *Generator { return g } -// // WithStrategyMap -// // -// // Adds addtional funcs for storageRetrieval used for testing only -// func (c *Generator) WithStrategyMap(sm strategy.StrategyFuncMap) *Generator { -// c.strategy = sm -// return c -// } +// WithStrategyMap +// +// Adds addtional funcs for storageRetrieval used for testing only +func (c *Generator) WithStores(sm *store.Store) *Generator { + c.store = sm + return c +} // WithConfig uses custom config func (c *Generator) WithConfig(cfg *config.GenVarsConfig) *Generator { @@ -76,11 +76,11 @@ func (c *Generator) WithConfig(cfg *config.GenVarsConfig) *Generator { return c } -// WithContext uses caller passed context -func (c *Generator) WithContext(ctx context.Context) *Generator { - 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 *Generator) Config() *config.GenVarsConfig { @@ -100,12 +100,11 @@ func (c *Generator) Generate(tokens []string) (ReplacedToken, error) { // initialise pugins here based on discovered tokens // - s, err := store.Init(c.ctx, ntm.TokenSet()) - if err != nil { + // this can only be done once the tokens are known + if err := c.store.Init(c.ctx, ntm.TokenSet()); err != nil { return nil, err } - c.store = s // pass in default initialised retrieveStrategy // input should be rt, err := c.generate(ntm) @@ -256,7 +255,7 @@ func (n NormalizedTokenSafe) TokenSet() []string { n.mu.Lock() defer n.mu.Unlock() ss := []string{} - for key, _ := range n.set { + for key := range n.set { ss = append(ss, strings.ToLower(key)) } return ss diff --git a/generator/generator_test.go b/generator/generator_test.go index d391d86..6e5418b 100644 --- a/generator/generator_test.go +++ b/generator/generator_test.go @@ -1,18 +1,11 @@ package generator_test import ( - "bytes" "context" - "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 { @@ -26,187 +19,187 @@ func (m *mockGenerate) Value() (s string, e error) { return m.value, m.err } -func TestGenerate(t *testing.T) { +// func TestGenerate(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 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 +// } - g := generator.New(context.TODO(), func(gv *generator.Generator) { - gv.Logger = log.New(&bytes.Buffer{}) - }) - g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) - got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) +// g := generator.New(context.TODO(), func(gv *generator.Generator) { +// gv.Logger = log.New(&bytes.Buffer{}) +// }) +// g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) +// got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) - if err != nil { - t.Fatal("errored on generate") - } - if len(got) != 1 { - t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 1) - } - }) +// if err != nil { +// t.Fatal("errored on generate") +// } +// if len(got) != 1 { +// t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 1) +// } +// }) - 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("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 +// } - g := generator.New(context.TODO()) - g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) - got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) +// g := generator.New(context.TODO()) +// g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) +// got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) - if err != nil { - t.Fatal("errored on generate") - } - if len(got) != 0 { - t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) - } - }) +// if err != nil { +// t.Fatal("errored on generate") +// } +// if len(got) != 0 { +// t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) +// } +// }) - 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 - } +// 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 +// } - g := generator.New(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.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc, store: nil}) +// got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token|key1.key2"}) - if err != nil { - t.Fatal("errored on generate") - } - if len(got) != 1 { - 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") - } - }) -} +// if err != nil { +// t.Fatal("errored on generate") +// } +// if len(got) != 1 { +// 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") +// } +// }) +// } -func TestGenerate_withKeys_lookup(t *testing.T) { - ttests := map[string]struct { - custFunc strategy.StrategyFunc - 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 - }, - 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 - }, - 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 - }, - 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 - }, - token: "AWSPARAMSTR://mountPath/token|noprop", - expectVal: `foo":"bar","key1":{"key2":123}}`, - }, - } - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - g := generator.New(context.TODO()) - g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: tt.custFunc}) - got, err := g.Generate([]string{tt.token}) +// func TestGenerate_withKeys_lookup(t *testing.T) { +// ttests := map[string]struct { +// custFunc strategy.StrategyFunc +// 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 +// }, +// 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 +// }, +// 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 +// }, +// 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 +// }, +// token: "AWSPARAMSTR://mountPath/token|noprop", +// expectVal: `foo":"bar","key1":{"key2":123}}`, +// }, +// } +// for name, tt := range ttests { +// t.Run(name, func(t *testing.T) { +// g := generator.New(context.TODO()) +// g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: tt.custFunc}) +// got, err := g.Generate([]string{tt.token}) - if err != nil { - t.Fatal("errored on generate") - } - if len(got) != 1 { - t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) - } - if got[tt.token] != tt.expectVal { - t.Errorf(testutils.TestPhraseWithContext, "incorrect value returned in parsedMap", got[tt.token], tt.expectVal) - } - }) - } -} +// if err != nil { +// t.Fatal("errored on generate") +// } +// if len(got) != 1 { +// t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) +// } +// if got[tt.token] != tt.expectVal { +// t.Errorf(testutils.TestPhraseWithContext, "incorrect value returned in parsedMap", got[tt.token], tt.expectVal) +// } +// }) +// } +// } -func Test_IsParsed(t *testing.T) { - ttests := map[string]struct { - val any - isParsed bool - }{ - "not parseable": { - `notparseable`, false, - }, - "one level parseable": { - `{"parseable":"foo"}`, true, - }, - "incorrect JSON": { - `parseable":"foo"}`, false, - }, - } - for name, tt := range ttests { - t.Run(name, func(t *testing.T) { - typ := generator.ReplacedToken{} - got := generator.IsParsed(tt.val, typ) - if got != tt.isParsed { - t.Errorf(testutils.TestPhraseWithContext, "unexpected IsParsed", got, tt.isParsed) - } - }) - } -} +// func Test_IsParsed(t *testing.T) { +// ttests := map[string]struct { +// val any +// isParsed bool +// }{ +// "not parseable": { +// `notparseable`, false, +// }, +// "one level parseable": { +// `{"parseable":"foo"}`, true, +// }, +// "incorrect JSON": { +// `parseable":"foo"}`, false, +// }, +// } +// for name, tt := range ttests { +// t.Run(name, func(t *testing.T) { +// typ := generator.ReplacedToken{} +// got := generator.IsParsed(tt.val, typ) +// if got != tt.isParsed { +// t.Errorf(testutils.TestPhraseWithContext, "unexpected IsParsed", got, tt.isParsed) +// } +// }) +// } +// } -func TestGenVars_NormalizeRawToken(t *testing.T) { +// func TestGenVars_NormalizeRawToken(t *testing.T) { - t.Run("multiple tokens", func(t *testing.T) { - g := generator.New(context.TODO()) +// t.Run("multiple tokens", func(t *testing.T) { +// g := generator.New(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) - } - } - }) -} +// 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 { @@ -297,7 +290,7 @@ 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} + // config.VarPrefix = map[config.ImplementationPrefix]bool{"AWSPARAMSTR": true} g := generator.New(context.TODO()) g.Config().WithTokenSeparator(tt.separator) gdt, err := g.DiscoverTokens(tt.input) @@ -318,53 +311,53 @@ func Test_ConfigManager_DiscoverTokens(t *testing.T) { } } -func Test_Generate_EnsureRaceFree(t *testing.T) { - g := generator.New(context.TODO()) +// func Test_Generate_EnsureRaceFree(t *testing.T) { +// g := generator.New(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` +// 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 - }, - }) +// 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) - } +// 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 0e1bf8d..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" ) diff --git a/go.mod b/go.mod index 2c30ca2..9555ab0 100644 --- a/go.mod +++ b/go.mod @@ -1,23 +1,25 @@ module github.com/DevLabFoundry/configmanager/v3 -go 1.25.5 +go 1.26 + +toolchain go1.26.1 require ( - github.com/aws/aws-sdk-go-v2 v1.40.1 - github.com/aws/aws-sdk-go-v2/config v1.32.3 - github.com/aws/aws-sdk-go-v2/service/ssm v1.67.5 + 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/service/ssm v1.68.3 github.com/go-test/deep v1.1.1 github.com/hashicorp/vault/api v1.22.0 github.com/hashicorp/vault/api/auth/aws v0.11.0 github.com/spf13/cobra v1.10.2 github.com/spyzhov/ajson v0.9.6 - google.golang.org/grpc v1.77.0 - google.golang.org/protobuf v1.36.10 + google.golang.org/grpc v1.79.3 + google.golang.org/protobuf v1.36.11 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/aws/aws-sdk-go-v2/service/signin v1.0.3 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // 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 @@ -30,17 +32,17 @@ require ( 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.19.3 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15 // 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.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.41.3 // indirect - github.com/aws/smithy-go v1.24.0 // indirect + 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/configsources v1.4.20 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.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/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/aws/smithy-go v1.24.2 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/fatih/color v1.18.0 // indirect github.com/go-jose/go-jose/v4 v4.1.3 // indirect @@ -66,9 +68,9 @@ require ( 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 - golang.org/x/net v0.47.0 // indirect - golang.org/x/sys v0.38.0 // indirect - golang.org/x/text v0.31.0 // indirect - golang.org/x/time v0.14.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // 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 + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 // indirect ) diff --git a/go.sum b/go.sum index fa846d7..53815db 100644 --- a/go.sum +++ b/go.sum @@ -5,34 +5,64 @@ 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.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +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.3 h1:cpz7H2uMNTDa0h/5CYL5dLUEzPSLo2g0NkbxTRJtSSU= github.com/aws/aws-sdk-go-v2/config v1.32.3/go.mod h1:srtPKaJJe3McW6T/+GMBZyIPc+SeqJsNPJsd4mOYZ6s= +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.3 h1:01Ym72hK43hjwDeJUfi1l2oYLXBAOR8gNSZNmXmvuas= github.com/aws/aws-sdk-go-v2/credentials v1.19.3/go.mod h1:55nWF/Sr9Zvls0bGnWkRxUdhzKqj9uRNlPvgV1vgxKc= +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.15 h1:utxLraaifrSBkeyII9mIbVwXXWrZdlPO7FIKmyLCEcY= github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15/go.mod h1:hW6zjYUDQwfz3icf4g2O41PHi77u10oAzJ84iSzR/lo= +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.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= +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.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= +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.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/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.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +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.15 h1:3/u/4yZOffg5jdNk1sDpOQ4Y+R6Xbh+GzpDrSZjuy3U= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15/go.mod h1:4Zkjq0FKjE78NKjabuM4tRXKFzUJWXgP0ItEZK8l7JU= +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.3 h1:d/6xOGIllc/XW1lzG9a4AUBMmpLA9PXcQnVPTuHHcik= github.com/aws/aws-sdk-go-v2/service/signin v1.0.3/go.mod h1:fQ7E7Qj9GiW8y0ClD7cUJk3Bz5Iw8wZkWDHsTe8vDKs= +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.67.5 h1:YKGgwB1rye0JpV10Bfma3cZdQzX61j2HPWQw+YxWvrQ= github.com/aws/aws-sdk-go-v2/service/ssm v1.67.5/go.mod h1:eBDSa0vuYB0lalpNxavIw80Q4Ksy08bhHHbT0aWa4tE= +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.6 h1:8sTTiw+9yuNXcfWeqKF2x01GqCF49CpP4Z9nKrrk/ts= github.com/aws/aws-sdk-go-v2/service/sso v1.30.6/go.mod h1:8WYg+Y40Sn3X2hioaaWAAIngndR8n1XFdRPPX+7QBaM= +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.11 h1:E+KqWoVsSrj1tJ6I/fjDIu5xoS2Zacuu1zT+H7KtiIk= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11/go.mod h1:qyWHz+4lvkXcr3+PoGlGHEI+3DLLiU6/GdrFfMaAhB0= +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.3 h1:tzMkjh0yTChUqJDgGkcDdxvZDSrJ/WB6R6ymI5ehqJI= github.com/aws/aws-sdk-go-v2/service/sts v1.41.3/go.mod h1:T270C0R5sZNLbWUe8ueiAF42XSZxxPocTaGSgs5c/60= +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.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +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/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -159,19 +189,26 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= 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 v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= 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/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= 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 v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= 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/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8= 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= +go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +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= @@ -181,19 +218,31 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +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.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +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-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +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.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +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.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +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= 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 9b93cf5..7fe25e7 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -5,7 +5,7 @@ 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" diff --git a/internal/store/plugin.go b/internal/store/plugin.go index ee6fe83..eeb0994 100644 --- a/internal/store/plugin.go +++ b/internal/store/plugin.go @@ -4,7 +4,7 @@ import ( "context" "os/exec" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/plugins" "github.com/hashicorp/go-plugin" ) diff --git a/internal/store/plugin_test.go b/internal/store/plugin_test.go index 47a840a..0185b5e 100644 --- a/internal/store/plugin_test.go +++ b/internal/store/plugin_test.go @@ -7,25 +7,26 @@ import ( "runtime" "testing" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/internal/store" ) // TODO: make the implementation of the plugin system more testable func TestPlugin_GetValue_integration(t *testing.T) { - t.Skip() // as the plugin is technically a subprocess // setting env vars at this level will affect the loaded plugin os.Setenv("AWS_REGION", "eu-west-1") os.Setenv("AWS_PROFILE", "FOO") - tp := fmt.Sprintf("../../.configmanager/plugins/awsparamstr/awsparamstr-%s-%s", runtime.GOOS, runtime.GOARCH) + defer os.Unsetenv("AWS_PROFILE") + defer os.Unsetenv("AWS_REGION") + tp := fmt.Sprintf("../../.configmanager/plugins/empty/empty-%s-%s", runtime.GOOS, runtime.GOARCH) np, err := store.NewPlugin(context.TODO(), tp) if err != nil { t.Fatal(err) } defer np.ClientCleanUp() - token, err := config.NewToken(config.ParamStorePrefix, *config.NewConfig()) + token, err := config.NewParsedToken(config.ParamStorePrefix, *config.NewConfig()) if err != nil { t.Fatal(err) } @@ -37,6 +38,9 @@ func TestPlugin_GetValue_integration(t *testing.T) { } if len(got) < 1 { - t.Error("empty...") + t.Fatal("empty...") + } + if got != "/int-test/pocketbase/admin-pwd->" { + t.Errorf("") } } diff --git a/internal/store/store.go b/internal/store/store.go index 79ae798..798c6f7 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -10,7 +10,7 @@ import ( "strings" "sync" - "github.com/DevLabFoundry/configmanager/v3/internal/config" + "github.com/DevLabFoundry/configmanager/v3/config" ) var ( @@ -21,22 +21,20 @@ var ( 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 -// -// 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) -} +// // Strategy iface that all store implementations +// // must conform to, in order to be be used by the retrieval implementation +// // +// // Defined on the package for easier re-use across the program +// type Strategy interface { +// // Value retrieves the underlying value for the token +// Value() (s string, e error) +// // SetToken +// SetToken(s *config.ParsedTokenConfig) +// } -// // It 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 @@ -56,25 +54,27 @@ const ( ) type Store struct { - pluginLocation []string - plugin pluginMap - // PluginCleanUp func() + plugin pluginMap } -func Init(ctx context.Context, implt []string) (*Store, error) { +func New(ctx context.Context) *Store { pm := pluginMap{mu: &sync.Mutex{}, m: make(map[string]*Plugin)} + s := &Store{plugin: pm} + return s +} + +// Init ensures all the discovered tokens have their implementations initialised +func (s *Store) Init(ctx context.Context, implt []string) error { - // l := []string{""} - // for _, plugin := range implt { plpath, err := findPlugin(plugin) if err != nil { - return nil, err + return err } p, err := NewPlugin(ctx, plpath) - pm.Add(plugin, p) + s.plugin.Add(plugin, p) } - return &Store{plugin: pm}, nil + return nil } func (s *Store) GetImplementation(implemenation config.ImplementationPrefix) (plugin *Plugin, err error) { 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/plugins/awsparamstr/go.mod b/plugins/awsparamstr/go.mod new file mode 100644 index 0000000..7289550 --- /dev/null +++ b/plugins/awsparamstr/go.mod @@ -0,0 +1,46 @@ +module github.com/DevLabFoundry/configmanager/plugins/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 => ../../../configmanager diff --git a/plugins/awsparamstr/go.sum b/plugins/awsparamstr/go.sum new file mode 100644 index 0000000..20a3784 --- /dev/null +++ b/plugins/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/plugins/awsparamstr/impl/paramstore.go b/plugins/awsparamstr/impl/paramstore.go index 6255252..23f98a0 100644 --- a/plugins/awsparamstr/impl/paramstore.go +++ b/plugins/awsparamstr/impl/paramstore.go @@ -3,12 +3,12 @@ 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/plugins" "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 { @@ -20,14 +20,14 @@ type ParamStore struct { ctx context.Context config *ParamStrConfig token *config.ParsedTokenConfig - logger log.ILogger + 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 { return nil, err @@ -58,7 +58,7 @@ func (imp *ParamStore) Value(token string, metadata []byte) (string, error) { result, err := imp.svc.GetParameter(ctx, input) if err != nil { - imp.logger.Error(plugins.ImplementationNetworkErr, config.ParamStorePrefix, err, token) + imp.logger.Error(plugins.ImplementationNetworkErr, "config.ParamStorePrefix", err, token) return "", err } diff --git a/plugins/awsparamstr/impl/paramstore_test.go b/plugins/awsparamstr/impl/paramstore_test.go index cc64dfc..948fd52 100644 --- a/plugins/awsparamstr/impl/paramstore_test.go +++ b/plugins/awsparamstr/impl/paramstore_test.go @@ -3,16 +3,19 @@ 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/testutils" - "github.com/DevLabFoundry/configmanager/v3/plugins/awsparamstr/impl" + "github.com/DevLabFoundry/configmanager/plugins/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 := impl.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)) 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/plugins/awsparamstr/main.go b/plugins/awsparamstr/main.go index c4b340f..e9db859 100644 --- a/plugins/awsparamstr/main.go +++ b/plugins/awsparamstr/main.go @@ -2,20 +2,19 @@ package main import ( "context" - "os" - "github.com/DevLabFoundry/configmanager/v3/internal/log" + "github.com/DevLabFoundry/configmanager/plugins/awsparamstr/impl" "github.com/DevLabFoundry/configmanager/v3/plugins" - "github.com/DevLabFoundry/configmanager/v3/plugins/awsparamstr/impl" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" ) -// Here is a real implementation of KV that writes to a local file with -// the key name and the contents are the value of the key. -type TokenStorePlugin struct{} +type TokenStorePlugin struct { + log hclog.Logger +} func (ts TokenStorePlugin) Value(key string, metadata []byte) (string, error) { - srv, err := impl.NewParamStore(context.Background(), log.New(os.Stderr)) + srv, err := impl.NewParamStore(context.Background(), ts.log) if err != nil { return "", err } @@ -23,10 +22,16 @@ func (ts TokenStorePlugin) Value(key string, metadata []byte) (string, error) { } 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: plugins.Handshake, Plugins: map[string]plugin.Plugin{ - "configmanager_token_store": &plugins.TokenStoreGRPCPlugin{Impl: &TokenStorePlugin{}}, + "configmanager_token_store": &plugins.TokenStoreGRPCPlugin{Impl: ts}, }, // A non-nil value here enables gRPC serving for this plugin... GRPCServer: plugin.DefaultGRPCServer, diff --git a/plugins/empty/main.go b/plugins/empty/main.go new file mode 100644 index 0000000..db5a189 --- /dev/null +++ b/plugins/empty/main.go @@ -0,0 +1,35 @@ +// 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/plugins" + "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{ + HandshakeConfig: plugins.Handshake, + Plugins: map[string]plugin.Plugin{ + "configmanager_token_store": &plugins.TokenStoreGRPCPlugin{Impl: &TokenStorePlugin{}}, + }, + // A non-nil value here enables gRPC serving for this plugin... + GRPCServer: plugin.DefaultGRPCServer, + }) +} diff --git a/plugins/vault/impl/hashivault.go b/plugins/vault/impl/hashivault.go index 75ba351..ab0061d 100644 --- a/plugins/vault/impl/hashivault.go +++ b/plugins/vault/impl/hashivault.go @@ -8,10 +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/plugins" + "github.com/hashicorp/go-hclog" vault "github.com/hashicorp/vault/api" auth "github.com/hashicorp/vault/api/auth/aws" ) @@ -30,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 @@ -42,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{ diff --git a/plugins/vault/impl/hashivault_test.go b/plugins/vault/impl/hashivault_test.go index 8e6f94a..7911bc9 100644 --- a/plugins/vault/impl/hashivault_test.go +++ b/plugins/vault/impl/hashivault_test.go @@ -3,17 +3,16 @@ 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/config" "github.com/DevLabFoundry/configmanager/v3/internal/testutils" "github.com/DevLabFoundry/configmanager/v3/plugins/vault/impl" + "github.com/hashicorp/go-hclog" vault "github.com/hashicorp/vault/api" ) @@ -25,7 +24,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 +33,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 +41,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 +49,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 +57,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("") @@ -98,7 +97,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 +127,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 +156,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 +187,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 +215,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 +243,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 +272,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("") @@ -301,7 +300,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") @@ -331,7 +330,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") @@ -358,7 +357,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("") @@ -392,7 +391,7 @@ failed to initialize the client`, tearDown := tt.setupEnv() defer tearDown() - i, err := impl.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()) @@ -425,7 +424,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") @@ -459,7 +458,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") @@ -506,7 +505,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") @@ -550,7 +549,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") @@ -598,7 +597,7 @@ incorrect values supplied. failed to initialize the client`, ts := httptest.NewServer(tt.mockHanlder(t)) tearDown := tt.setupEnv(ts.URL) defer tearDown() - i, err := impl.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] { diff --git a/plugins/vault/main.go b/plugins/vault/main.go index c4b340f..b2e3e8c 100644 --- a/plugins/vault/main.go +++ b/plugins/vault/main.go @@ -1,25 +1,19 @@ package main import ( - "context" - "os" - - "github.com/DevLabFoundry/configmanager/v3/internal/log" "github.com/DevLabFoundry/configmanager/v3/plugins" - "github.com/DevLabFoundry/configmanager/v3/plugins/awsparamstr/impl" "github.com/hashicorp/go-plugin" ) -// Here is a real implementation of KV that writes to a local file with -// the key name and the contents are the value of the key. 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) + // srv, err := impl.NewParamStore(context.Background(), log.New(os.Stderr)) + // if err != nil { + // return "", err + // } + // return srv.Value(key, metadata) + return "", nil } func main() { From b6417105ca65d553dd921060204a766f3a26f725 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Thu, 19 Mar 2026 21:45:21 +0000 Subject: [PATCH 08/20] fix: rejig layout of sub modules --- eirctl.yaml | 5 +- go.mod | 37 ---- go.sum | 161 +----------------- {plugins => tokenstore}/README.md | 0 {plugins => tokenstore}/grpc.go | 8 +- {plugins => tokenstore}/interface.go | 12 +- .../proto/token_store.pb.go | 0 .../proto/token_store.proto | 0 .../proto/token_store_grpc.pb.go | 0 .../provider}/awsparamstr/README.md | 0 .../provider}/awsparamstr/go.mod | 4 +- .../provider}/awsparamstr/go.sum | 0 .../provider}/awsparamstr/impl/paramstore.go | 4 +- .../awsparamstr/impl/paramstore_test.go | 2 +- .../provider}/awsparamstr/main.go | 8 +- .../provider}/awssecrets/.gitkeep | 0 tokenstore/provider/empty/go.mod | 28 +++ tokenstore/provider/empty/go.sum | 79 +++++++++ .../provider}/empty/main.go | 6 +- .../provider}/vault/README.md | 1 - tokenstore/provider/vault/go.mod | 53 ++++++ tokenstore/provider/vault/go.sum | 155 +++++++++++++++++ .../provider}/vault/impl/hashivault.go | 10 +- .../provider}/vault/impl/hashivault_test.go | 34 ++-- .../provider}/vault/main.go | 6 +- {plugins => tokenstore}/scaffolding.go | 2 +- 26 files changed, 374 insertions(+), 241 deletions(-) rename {plugins => tokenstore}/README.md (100%) rename {plugins => tokenstore}/grpc.go (77%) rename {plugins => tokenstore}/interface.go (72%) rename {plugins => tokenstore}/proto/token_store.pb.go (100%) rename {plugins => tokenstore}/proto/token_store.proto (100%) rename {plugins => tokenstore}/proto/token_store_grpc.pb.go (100%) rename {plugins => tokenstore/provider}/awsparamstr/README.md (100%) rename {plugins => tokenstore/provider}/awsparamstr/go.mod (95%) rename {plugins => tokenstore/provider}/awsparamstr/go.sum (100%) rename {plugins => tokenstore/provider}/awsparamstr/impl/paramstore.go (91%) rename {plugins => tokenstore/provider}/awsparamstr/impl/paramstore_test.go (98%) rename {plugins => tokenstore/provider}/awsparamstr/main.go (76%) rename {plugins => tokenstore/provider}/awssecrets/.gitkeep (100%) create mode 100644 tokenstore/provider/empty/go.mod create mode 100644 tokenstore/provider/empty/go.sum rename {plugins => tokenstore/provider}/empty/main.go (82%) rename {plugins => tokenstore/provider}/vault/README.md (95%) create mode 100644 tokenstore/provider/vault/go.mod create mode 100644 tokenstore/provider/vault/go.sum rename {plugins => tokenstore/provider}/vault/impl/hashivault.go (93%) rename {plugins => tokenstore/provider}/vault/impl/hashivault_test.go (94%) rename {plugins => tokenstore/provider}/vault/main.go (75%) rename {plugins => tokenstore}/scaffolding.go (97%) diff --git a/eirctl.yaml b/eirctl.yaml index 22db6e4..8577638 100644 --- a/eirctl.yaml +++ b/eirctl.yaml @@ -87,8 +87,9 @@ tasks: mkdir -p .deps unset GOTOOLCHAIN ldflags="-s -w -extldflags -static" - GOPATH=/eirctl/.deps GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} CGO_ENABLED=0 go build -mod=readonly -buildvcs=false -ldflags="$ldflags" \ - -o ./plugins/$PLUGIN/bin/$PLUGIN-${BUILD_GOOS}-${BUILD_GOARCH}${BUILD_SUFFIX} ./plugins/$PLUGIN/main.go + export GOPATH=/eirctl/.deps GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} CGO_ENABLED=0 + go build -C ./plugins/$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 diff --git a/go.mod b/go.mod index 9555ab0..dff52b3 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,7 @@ go 1.26 toolchain go1.26.1 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/service/ssm v1.68.3 github.com/go-test/deep v1.1.1 - github.com/hashicorp/vault/api v1.22.0 - github.com/hashicorp/vault/api/auth/aws v0.11.0 github.com/spf13/cobra v1.10.2 github.com/spyzhov/ajson v0.9.6 google.golang.org/grpc v1.79.3 @@ -19,7 +14,6 @@ require ( ) require ( - github.com/aws/aws-sdk-go-v2/service/signin v1.0.8 // 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 @@ -31,46 +25,15 @@ require ( 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.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/configsources v1.4.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.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/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/aws/smithy-go v1.24.2 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/fatih/color v1.18.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-hclog v1.6.3 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.7.0 - 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/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jmespath/go-jmespath v0.4.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/errors v0.9.1 // indirect - github.com/ryanuber/go-glob v1.0.0 // indirect github.com/spf13/pflag v1.0.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 - golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260319171110-e3a33c96fb44 // indirect ) diff --git a/go.sum b/go.sum index 53815db..eeb042f 100644 --- a/go.sum +++ b/go.sum @@ -1,72 +1,9 @@ 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.40.1 h1:difXb4maDZkRH0x//Qkwcfpdg1XQVXEAEs2DdXldFFc= -github.com/aws/aws-sdk-go-v2 v1.40.1/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= -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.3 h1:cpz7H2uMNTDa0h/5CYL5dLUEzPSLo2g0NkbxTRJtSSU= -github.com/aws/aws-sdk-go-v2/config v1.32.3/go.mod h1:srtPKaJJe3McW6T/+GMBZyIPc+SeqJsNPJsd4mOYZ6s= -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.3 h1:01Ym72hK43hjwDeJUfi1l2oYLXBAOR8gNSZNmXmvuas= -github.com/aws/aws-sdk-go-v2/credentials v1.19.3/go.mod h1:55nWF/Sr9Zvls0bGnWkRxUdhzKqj9uRNlPvgV1vgxKc= -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.15 h1:utxLraaifrSBkeyII9mIbVwXXWrZdlPO7FIKmyLCEcY= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.15/go.mod h1:hW6zjYUDQwfz3icf4g2O41PHi77u10oAzJ84iSzR/lo= -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.15 h1:Y5YXgygXwDI5P4RkteB5yF7v35neH7LfJKBG+hzIons= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.15/go.mod h1:K+/1EpG42dFSY7CBj+Fruzm8PsCGWTXJ3jdeJ659oGQ= -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.15 h1:AvltKnW9ewxX2hFmQS0FyJH93aSvJVUEFvXfU+HWtSE= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.15/go.mod h1:3I4oCdZdmgrREhU74qS1dK9yZ62yumob+58AbFR4cQA= -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.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/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.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= -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.15 h1:3/u/4yZOffg5jdNk1sDpOQ4Y+R6Xbh+GzpDrSZjuy3U= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.15/go.mod h1:4Zkjq0FKjE78NKjabuM4tRXKFzUJWXgP0ItEZK8l7JU= -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.3 h1:d/6xOGIllc/XW1lzG9a4AUBMmpLA9PXcQnVPTuHHcik= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.3/go.mod h1:fQ7E7Qj9GiW8y0ClD7cUJk3Bz5Iw8wZkWDHsTe8vDKs= -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.67.5 h1:YKGgwB1rye0JpV10Bfma3cZdQzX61j2HPWQw+YxWvrQ= -github.com/aws/aws-sdk-go-v2/service/ssm v1.67.5/go.mod h1:eBDSa0vuYB0lalpNxavIw80Q4Ksy08bhHHbT0aWa4tE= -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.6 h1:8sTTiw+9yuNXcfWeqKF2x01GqCF49CpP4Z9nKrrk/ts= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.6/go.mod h1:8WYg+Y40Sn3X2hioaaWAAIngndR8n1XFdRPPX+7QBaM= -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.11 h1:E+KqWoVsSrj1tJ6I/fjDIu5xoS2Zacuu1zT+H7KtiIk= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.11/go.mod h1:qyWHz+4lvkXcr3+PoGlGHEI+3DLLiU6/GdrFfMaAhB0= -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.3 h1:tzMkjh0yTChUqJDgGkcDdxvZDSrJ/WB6R6ymI5ehqJI= -github.com/aws/aws-sdk-go-v2/service/sts v1.41.3/go.mod h1:T270C0R5sZNLbWUe8ueiAF42XSZxxPocTaGSgs5c/60= -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.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= -github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= -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/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/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= @@ -75,13 +12,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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= @@ -90,52 +24,17 @@ 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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 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= @@ -150,24 +49,15 @@ 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/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/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/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.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= @@ -176,81 +66,44 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A 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/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 v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -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 v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -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/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/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= -go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +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/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= -go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +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/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.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= -golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= 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.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= -golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 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.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= -golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= 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.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= -golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= -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-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= 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.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= 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.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= 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/plugins/README.md b/tokenstore/README.md similarity index 100% rename from plugins/README.md rename to tokenstore/README.md diff --git a/plugins/grpc.go b/tokenstore/grpc.go similarity index 77% rename from plugins/grpc.go rename to tokenstore/grpc.go index c59d35f..ed618ca 100644 --- a/plugins/grpc.go +++ b/tokenstore/grpc.go @@ -1,9 +1,9 @@ -package plugins +package tokenstore import ( "context" - "github.com/DevLabFoundry/configmanager/v3/plugins/proto" + "github.com/DevLabFoundry/configmanager/v3/tokenstore/proto" ) // GRPCClient is the host process talking to the plugins @@ -28,9 +28,7 @@ type GRPCServer struct { Impl TokenStore } -func (m *GRPCServer) Value( - ctx context.Context, - req *proto.TokenValueRequest) (*proto.TokenValueResponse, error) { +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/plugins/interface.go b/tokenstore/interface.go similarity index 72% rename from plugins/interface.go rename to tokenstore/interface.go index 5a3cd1b..faaa4c5 100644 --- a/plugins/interface.go +++ b/tokenstore/interface.go @@ -1,11 +1,11 @@ -package plugins +package tokenstore import ( "context" "google.golang.org/grpc" - "github.com/DevLabFoundry/configmanager/v3/plugins/proto" + "github.com/DevLabFoundry/configmanager/v3/tokenstore/proto" "github.com/hashicorp/go-plugin" ) @@ -19,7 +19,7 @@ var Handshake = plugin.HandshakeConfig{ // // PluginMap is the map of plugins we can dispense. var PluginMap = map[string]plugin.Plugin{ - "configmanager_token_store": &TokenStoreGRPCPlugin{}, + "configmanager_token_store": &GRPCPlugin{}, } // TokenStore is the interface that we're exposing as a plugin. @@ -28,7 +28,7 @@ type TokenStore interface { } // This is the implementation of plugin.GRPCPlugin so we can serve/consume this. -type TokenStoreGRPCPlugin struct { +type GRPCPlugin struct { // GRPCPlugin must still implement the Plugin interface plugin.Plugin // Concrete implementation, written in Go. This is only used for plugins @@ -36,11 +36,11 @@ type TokenStoreGRPCPlugin struct { Impl TokenStore } -func (p *TokenStoreGRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { +func (p *GRPCPlugin) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error { proto.RegisterTokenStoreServer(s, &GRPCServer{Impl: p.Impl}) return nil } -func (p *TokenStoreGRPCPlugin) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) { +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/plugins/proto/token_store.pb.go b/tokenstore/proto/token_store.pb.go similarity index 100% rename from plugins/proto/token_store.pb.go rename to tokenstore/proto/token_store.pb.go diff --git a/plugins/proto/token_store.proto b/tokenstore/proto/token_store.proto similarity index 100% rename from plugins/proto/token_store.proto rename to tokenstore/proto/token_store.proto diff --git a/plugins/proto/token_store_grpc.pb.go b/tokenstore/proto/token_store_grpc.pb.go similarity index 100% rename from plugins/proto/token_store_grpc.pb.go rename to tokenstore/proto/token_store_grpc.pb.go diff --git a/plugins/awsparamstr/README.md b/tokenstore/provider/awsparamstr/README.md similarity index 100% rename from plugins/awsparamstr/README.md rename to tokenstore/provider/awsparamstr/README.md diff --git a/plugins/awsparamstr/go.mod b/tokenstore/provider/awsparamstr/go.mod similarity index 95% rename from plugins/awsparamstr/go.mod rename to tokenstore/provider/awsparamstr/go.mod index 7289550..c3d1a37 100644 --- a/plugins/awsparamstr/go.mod +++ b/tokenstore/provider/awsparamstr/go.mod @@ -1,4 +1,4 @@ -module github.com/DevLabFoundry/configmanager/plugins/awsparamstr +module github.com/DevLabFoundry/configmanager-plugin/awsparamstr go 1.26 @@ -43,4 +43,4 @@ require ( github.com/hashicorp/go-plugin v1.7.0 ) -replace github.com/DevLabFoundry/configmanager/v3 v3.0.0 => ../../../configmanager +replace github.com/DevLabFoundry/configmanager/v3 v3.0.0 => ../../../ diff --git a/plugins/awsparamstr/go.sum b/tokenstore/provider/awsparamstr/go.sum similarity index 100% rename from plugins/awsparamstr/go.sum rename to tokenstore/provider/awsparamstr/go.sum diff --git a/plugins/awsparamstr/impl/paramstore.go b/tokenstore/provider/awsparamstr/impl/paramstore.go similarity index 91% rename from plugins/awsparamstr/impl/paramstore.go rename to tokenstore/provider/awsparamstr/impl/paramstore.go index 23f98a0..58ffbb5 100644 --- a/plugins/awsparamstr/impl/paramstore.go +++ b/tokenstore/provider/awsparamstr/impl/paramstore.go @@ -4,7 +4,7 @@ import ( "context" "github.com/DevLabFoundry/configmanager/v3/config" - "github.com/DevLabFoundry/configmanager/v3/plugins" + "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" @@ -58,7 +58,7 @@ func (imp *ParamStore) Value(token string, metadata []byte) (string, error) { result, err := imp.svc.GetParameter(ctx, input) if err != nil { - imp.logger.Error(plugins.ImplementationNetworkErr, "config.ParamStorePrefix", err, token) + imp.logger.Error(tokenstore.ImplementationNetworkErr, "config.ParamStorePrefix", err, token) return "", err } diff --git a/plugins/awsparamstr/impl/paramstore_test.go b/tokenstore/provider/awsparamstr/impl/paramstore_test.go similarity index 98% rename from plugins/awsparamstr/impl/paramstore_test.go rename to tokenstore/provider/awsparamstr/impl/paramstore_test.go index 948fd52..0979e6a 100644 --- a/plugins/awsparamstr/impl/paramstore_test.go +++ b/tokenstore/provider/awsparamstr/impl/paramstore_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/DevLabFoundry/configmanager/plugins/awsparamstr/impl" + "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" diff --git a/plugins/awsparamstr/main.go b/tokenstore/provider/awsparamstr/main.go similarity index 76% rename from plugins/awsparamstr/main.go rename to tokenstore/provider/awsparamstr/main.go index e9db859..a01ecda 100644 --- a/plugins/awsparamstr/main.go +++ b/tokenstore/provider/awsparamstr/main.go @@ -3,8 +3,8 @@ package main import ( "context" - "github.com/DevLabFoundry/configmanager/plugins/awsparamstr/impl" - "github.com/DevLabFoundry/configmanager/v3/plugins" + "github.com/DevLabFoundry/configmanager-plugin/awsparamstr/impl" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" ) @@ -29,9 +29,9 @@ func main() { ts := TokenStorePlugin{log: log} plugin.Serve(&plugin.ServeConfig{ // Logger: , - HandshakeConfig: plugins.Handshake, + HandshakeConfig: tokenstore.Handshake, Plugins: map[string]plugin.Plugin{ - "configmanager_token_store": &plugins.TokenStoreGRPCPlugin{Impl: ts}, + "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: ts}, }, // A non-nil value here enables gRPC serving for this plugin... GRPCServer: plugin.DefaultGRPCServer, diff --git a/plugins/awssecrets/.gitkeep b/tokenstore/provider/awssecrets/.gitkeep similarity index 100% rename from plugins/awssecrets/.gitkeep rename to tokenstore/provider/awssecrets/.gitkeep 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/plugins/empty/main.go b/tokenstore/provider/empty/main.go similarity index 82% rename from plugins/empty/main.go rename to tokenstore/provider/empty/main.go index db5a189..9b723a4 100644 --- a/plugins/empty/main.go +++ b/tokenstore/provider/empty/main.go @@ -8,7 +8,7 @@ import ( "errors" "fmt" - "github.com/DevLabFoundry/configmanager/v3/plugins" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" "github.com/hashicorp/go-plugin" ) @@ -25,9 +25,9 @@ func (ts TokenStorePlugin) Value(key string, metadata []byte) (string, error) { func main() { plugin.Serve(&plugin.ServeConfig{ - HandshakeConfig: plugins.Handshake, + HandshakeConfig: tokenstore.Handshake, Plugins: map[string]plugin.Plugin{ - "configmanager_token_store": &plugins.TokenStoreGRPCPlugin{Impl: &TokenStorePlugin{}}, + "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: &TokenStorePlugin{}}, }, // A non-nil value here enables gRPC serving for this plugin... GRPCServer: plugin.DefaultGRPCServer, diff --git a/plugins/vault/README.md b/tokenstore/provider/vault/README.md similarity index 95% rename from plugins/vault/README.md rename to tokenstore/provider/vault/README.md index 27b590e..89622a7 100644 --- a/plugins/vault/README.md +++ b/tokenstore/provider/vault/README.md @@ -1,3 +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/plugins/vault/impl/hashivault.go b/tokenstore/provider/vault/impl/hashivault.go similarity index 93% rename from plugins/vault/impl/hashivault.go rename to tokenstore/provider/vault/impl/hashivault.go index ab0061d..dd4ef31 100644 --- a/plugins/vault/impl/hashivault.go +++ b/tokenstore/provider/vault/impl/hashivault.go @@ -9,7 +9,7 @@ import ( "strings" "github.com/DevLabFoundry/configmanager/v3/config" - "github.com/DevLabFoundry/configmanager/v3/plugins" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" "github.com/hashicorp/go-hclog" vault "github.com/hashicorp/vault/api" @@ -57,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, plugins.ErrClientInitialization) + return nil, fmt.Errorf("%v\n%w", err, tokenstore.ErrClientInitialization) } if strings.HasPrefix(os.Getenv("VAULT_TOKEN"), "aws_iam") { @@ -85,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, plugins.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, plugins.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") @@ -121,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(plugins.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/plugins/vault/impl/hashivault_test.go b/tokenstore/provider/vault/impl/hashivault_test.go similarity index 94% rename from plugins/vault/impl/hashivault_test.go rename to tokenstore/provider/vault/impl/hashivault_test.go index 7911bc9..f4c437b 100644 --- a/plugins/vault/impl/hashivault_test.go +++ b/tokenstore/provider/vault/impl/hashivault_test.go @@ -9,13 +9,17 @@ import ( "strings" "testing" + "github.com/DevLabFoundry/configmanager-plugin/vault/impl" "github.com/DevLabFoundry/configmanager/v3/config" - "github.com/DevLabFoundry/configmanager/v3/internal/testutils" - "github.com/DevLabFoundry/configmanager/v3/plugins/vault/impl" "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 @@ -284,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 } @@ -312,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" @@ -342,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 } @@ -370,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 } @@ -403,12 +407,12 @@ failed to initialize the client`, 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) } }) } @@ -436,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 } @@ -475,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 } @@ -518,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" @@ -561,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" @@ -601,7 +605,7 @@ incorrect values supplied. failed to initialize the client`, 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 @@ -611,12 +615,12 @@ incorrect values supplied. failed to initialize the client`, 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/plugins/vault/main.go b/tokenstore/provider/vault/main.go similarity index 75% rename from plugins/vault/main.go rename to tokenstore/provider/vault/main.go index b2e3e8c..cdf20e4 100644 --- a/plugins/vault/main.go +++ b/tokenstore/provider/vault/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/DevLabFoundry/configmanager/v3/plugins" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" "github.com/hashicorp/go-plugin" ) @@ -18,9 +18,9 @@ func (ts TokenStorePlugin) Value(key string, metadata []byte) (string, error) { func main() { plugin.Serve(&plugin.ServeConfig{ - HandshakeConfig: plugins.Handshake, + HandshakeConfig: tokenstore.Handshake, Plugins: map[string]plugin.Plugin{ - "configmanager_token_store": &plugins.TokenStoreGRPCPlugin{Impl: &TokenStorePlugin{}}, + "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: &TokenStorePlugin{}}, }, // A non-nil value here enables gRPC serving for this plugin... GRPCServer: plugin.DefaultGRPCServer, diff --git a/plugins/scaffolding.go b/tokenstore/scaffolding.go similarity index 97% rename from plugins/scaffolding.go rename to tokenstore/scaffolding.go index f317496..72ee236 100644 --- a/plugins/scaffolding.go +++ b/tokenstore/scaffolding.go @@ -1,4 +1,4 @@ -package plugins +package tokenstore import "errors" From 746b1a500ea00e29999f360b9a7dd3ca517a3945 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Thu, 19 Mar 2026 22:19:36 +0000 Subject: [PATCH 09/20] fix: minor clean up --- .github/workflows/build.yml | 4 +- .github/workflows/release.yml | 8 +-- buf.gen.yaml | 6 +- buf.yaml | 2 +- eirctl.yaml | 8 ++- internal/parser/parser_test.go | 110 +++++++++++++++-------------- internal/store/plugin.go | 10 +-- tokenstore/proto/token_store.pb.go | 2 +- tokenstore/provider/empty/main.go | 2 + 9 files changed, 78 insertions(+), 74 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e0aae4f..95e6f70 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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 405c112..bfb8538 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,7 +35,7 @@ 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 @@ -52,9 +53,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/buf.gen.yaml b/buf.gen.yaml index eb64d58..7aea791 100644 --- a/buf.gen.yaml +++ b/buf.gen.yaml @@ -3,13 +3,13 @@ version: v2 clean: false plugins: - remote: buf.build/protocolbuffers/go - out: plugins/proto + out: tokenstore/proto opt: - paths=source_relative - remote: buf.build/grpc/go:v1.3.0 - out: plugins/proto + out: tokenstore/proto opt: - paths=source_relative - require_unimplemented_servers=false inputs: - - directory: plugins/proto + - directory: tokenstore/proto diff --git a/buf.yaml b/buf.yaml index d055ccd..fa50bb2 100644 --- a/buf.yaml +++ b/buf.yaml @@ -1,6 +1,6 @@ version: v2 modules: - - path: ./plugins/proto + - path: ./tokenstore/proto lint: use: - STANDARD diff --git a/eirctl.yaml b/eirctl.yaml index 8577638..cc4b208 100644 --- a/eirctl.yaml +++ b/eirctl.yaml @@ -61,6 +61,10 @@ pipelines: name: vault env: PLUGIN: vault + - task: go:build:plugin + name: empty + env: + PLUGIN: empty scan:plugins: - task: trivy:file:system:sbom @@ -88,7 +92,7 @@ tasks: unset GOTOOLCHAIN ldflags="-s -w -extldflags -static" export GOPATH=/eirctl/.deps GOOS=${BUILD_GOOS} GOARCH=${BUILD_GOARCH} CGO_ENABLED=0 - go build -C ./plugins/$PLUGIN -mod=readonly -buildvcs=false -ldflags="$ldflags" \ + 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}" @@ -166,7 +170,7 @@ tasks: 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:" diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 7fe25e7..b22b026 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -90,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 } } @@ -229,60 +229,62 @@ func Test_Parse_should_pass_with_metadata_end_tag(t *testing.T) { // } // } -// func Test_Parse_Path_Keys_WithParsedMetadat(t *testing.T) { - -// ttests := map[string]struct { -// input string -// typ *store.SecretsMgrConfig -// wantSanitizedPath string -// wantKeyPath string -// }{ -// "without keysPath": { -// `AWSSECRETS:///foo[version=1.2.3]`, -// &store.SecretsMgrConfig{}, -// "/foo", "", -// }, -// "with keysPath": { -// `AWSSECRETS:///foo|path.one[version=1.2.3]`, -// &store.SecretsMgrConfig{}, -// "/foo", "path.one", -// }, -// "nestled in text": { -// `someQ=AWSPARAMSTR:///path/queryparam|p1[version=1.2.3]&anotherQ`, -// &store.SecretsMgrConfig{}, -// "/path/queryparam", "p1", -// }, -// } -// 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) -// } +func Test_Parse_Path_Keys_WithParsedMetadat(t *testing.T) { + type version struct { + Version string + } + ttests := map[string]struct { + input string + typ *version + wantSanitizedPath string + wantKeyPath string + }{ + "without keysPath": { + `AWSSECRETS:///foo[version=1.2.3]`, + &version{}, + "/foo", "", + }, + "with keysPath": { + `AWSSECRETS:///foo|path.one[version=1.2.3]`, + &version{}, + "/foo", "path.one", + }, + "nestled in text": { + `someQ=AWSPARAMSTR:///path/queryparam|p1[version=1.2.3]&anotherQ`, + &version{}, + "/path/queryparam", "p1", + }, + } + 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 p.ParsedToken.StoreToken() != tt.wantSanitizedPath { -// t.Errorf("got %s want %s", p.ParsedToken.StoreToken(), tt.wantSanitizedPath) -// } -// if p.ParsedToken.LookupKeys() != tt.wantKeyPath { -// t.Errorf("got %s want %s", p.ParsedToken.LookupKeys(), tt.wantKeyPath) -// } -// 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 p.ParsedToken.StoreToken() != tt.wantSanitizedPath { + t.Errorf("got %s want %s", p.ParsedToken.StoreToken(), tt.wantSanitizedPath) + } + if p.ParsedToken.LookupKeys() != tt.wantKeyPath { + t.Errorf("got %s want %s", p.ParsedToken.LookupKeys(), tt.wantKeyPath) + } + 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 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/plugin.go b/internal/store/plugin.go index eeb0994..912b40b 100644 --- a/internal/store/plugin.go +++ b/internal/store/plugin.go @@ -5,7 +5,7 @@ import ( "os/exec" "github.com/DevLabFoundry/configmanager/v3/config" - "github.com/DevLabFoundry/configmanager/v3/plugins" + "github.com/DevLabFoundry/configmanager/v3/tokenstore" "github.com/hashicorp/go-plugin" ) @@ -16,15 +16,15 @@ type Plugin struct { SourcePath string Version string ClientCleanUp func() - tokenStore plugins.TokenStore + 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: plugins.Handshake, - Plugins: plugin.PluginSet{"configmanager_token_store": &plugins.TokenStoreGRPCPlugin{}}, + HandshakeConfig: tokenstore.Handshake, + Plugins: plugin.PluginSet{"configmanager_token_store": &tokenstore.GRPCPlugin{}}, Cmd: exec.Command(path), AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, }) @@ -42,7 +42,7 @@ func NewPlugin(ctx context.Context, path string) (*Plugin, error) { return nil, err } - ts := raw.(plugins.TokenStore) + ts := raw.(tokenstore.TokenStore) p := &Plugin{ ClientCleanUp: client.Kill, diff --git a/tokenstore/proto/token_store.pb.go b/tokenstore/proto/token_store.pb.go index aa4eff0..db5e957 100644 --- a/tokenstore/proto/token_store.pb.go +++ b/tokenstore/proto/token_store.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.36.10 +// protoc-gen-go v1.36.11 // protoc (unknown) // source: token_store.proto diff --git a/tokenstore/provider/empty/main.go b/tokenstore/provider/empty/main.go index 9b723a4..31abf0d 100644 --- a/tokenstore/provider/empty/main.go +++ b/tokenstore/provider/empty/main.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/DevLabFoundry/configmanager/v3/tokenstore" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" ) @@ -25,6 +26,7 @@ func (ts TokenStorePlugin) Value(key string, metadata []byte) (string, error) { func main() { plugin.Serve(&plugin.ServeConfig{ + Logger: hclog.NewNullLogger(), HandshakeConfig: tokenstore.Handshake, Plugins: map[string]plugin.Plugin{ "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: &TokenStorePlugin{}}, From 5d65761b054564cf3ca013384725ef36c77991b5 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Thu, 2 Apr 2026 15:19:01 +0100 Subject: [PATCH 10/20] fix: interim --- eirctl.yaml | 2 +- generator/generator.go | 2 +- internal/store/store.go | 19 ++++++++++++++++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/eirctl.yaml b/eirctl.yaml index cc4b208..cdc62f6 100644 --- a/eirctl.yaml +++ b/eirctl.yaml @@ -19,7 +19,7 @@ contexts: go1xalpine: container: - name: mirror.gcr.io/golang:1.25-alpine + name: mirror.gcr.io/golang:1.26-alpine envfile: exclude: - GO diff --git a/generator/generator.go b/generator/generator.go index 0d4010f..73e355a 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -59,7 +59,7 @@ func new(ctx context.Context, opts ...Opts) *Generator { return g } -// WithStrategyMap +// WithStores assigns additional stores to the strategy // // Adds addtional funcs for storageRetrieval used for testing only func (c *Generator) WithStores(sm *store.Store) *Generator { diff --git a/internal/store/store.go b/internal/store/store.go index 798c6f7..5ea4af0 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -53,13 +53,22 @@ const ( 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} + s := &Store{ + plugin: pm, + osOps: osOps{UserHomeDir: os.UserHomeDir, Getwd: os.Getwd}, + } return s } @@ -67,11 +76,15 @@ func New(ctx context.Context) *Store { func (s *Store) Init(ctx context.Context, implt []string) error { for _, plugin := range implt { - plpath, err := findPlugin(plugin) + 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 @@ -98,7 +111,7 @@ func (s *Store) PluginCleanUp() { // // current dir // home dir -func findPlugin(plugin string) (string, error) { +func (s *Store) findPlugin(plugin string) (string, error) { // fallback locations // current dir cwd, err := os.Getwd() From a3f89bd3cc10dff334ce0a1163fe670214ec1133 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Fri, 3 Apr 2026 11:25:40 +0100 Subject: [PATCH 11/20] fix: add tests after re-work --- config/config.go | 16 +- eirctl.yaml | 18 +- generator/generator.go | 39 ++-- generator/generator_test.go | 381 ++++++++++++++++++---------------- internal/store/plugin.go | 4 + internal/store/plugin_test.go | 14 +- internal/store/store.go | 10 +- internal/store/store_test.go | 2 + 8 files changed, 260 insertions(+), 224 deletions(-) diff --git a/config/config.go b/config/config.go index ac8f9b7..ed57d80 100644 --- a/config/config.go +++ b/config/config.go @@ -50,6 +50,7 @@ type GenVarsConfig struct { tokenSeparator string keySeparator string enableEnvSubst bool + enableLaxMode bool // parseAdditionalVars func(token string) TokenConfigVars } @@ -83,12 +84,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 @@ -109,6 +116,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 diff --git a/eirctl.yaml b/eirctl.yaml index cdc62f6..a8bd4d9 100644 --- a/eirctl.yaml +++ b/eirctl.yaml @@ -4,8 +4,8 @@ output: prefixed debug: false import: - - https://raw.githubusercontent.com/Ensono/eirctl/refs/tags/0.9.7/shared/build/go/eirctl.yaml - - https://raw.githubusercontent.com/Ensono/eirctl/refs/tags/0.9.7/shared/security/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: @@ -28,7 +28,13 @@ contexts: pipelines: unit:test: + - 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 - pipeline: test:unit + depends_on: empty env: ROOT_PKG_NAME: github.com/DevLabFoundry @@ -37,9 +43,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: @@ -70,10 +76,6 @@ pipelines: - task: trivy:file:system:sbom tasks: - show:coverage: - description: Opens the current coverage viewer for the the configmanager utility. - command: go tool cover -html=.coverage/out - show_docs: description: | Opens a webview with godoc running diff --git a/generator/generator.go b/generator/generator.go index 73e355a..f489682 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -17,6 +17,14 @@ import ( "github.com/DevLabFoundry/configmanager/v3/internal/store" ) +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 @@ -26,7 +34,7 @@ import ( type Generator struct { Logger log.ILogger // strategy strategy.StrategyFuncMap - store *store.Store + store storeIface ctx context.Context config config.GenVarsConfig } @@ -62,7 +70,7 @@ func new(ctx context.Context, opts ...Opts) *Generator { // WithStores assigns additional stores to the strategy // // Adds addtional funcs for storageRetrieval used for testing only -func (c *Generator) WithStores(sm *store.Store) *Generator { +func (c *Generator) WithStores(sm storeIface) *Generator { c.store = sm return c } @@ -102,7 +110,7 @@ func (c *Generator) Generate(tokens []string) (ReplacedToken, error) { // // this can only be done once the tokens are known if err := c.store.Init(c.ctx, ntm.TokenSet()); err != nil { - return nil, err + return nil, fmt.Errorf("%w, %v", ErrProvidersNotFound, err) } // pass in default initialised retrieveStrategy @@ -111,6 +119,7 @@ func (c *Generator) Generate(tokens []string) (ReplacedToken, error) { if err != nil { return nil, err } + return rt, nil } @@ -143,8 +152,7 @@ func (c *Generator) 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 @@ -160,9 +168,6 @@ func (c *Generator) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { wg := &sync.WaitGroup{} - // initialise the stores here - // 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 @@ -175,18 +180,12 @@ func (c *Generator) generate(ntm NormalizedTokenSafe) (ReplacedToken, error) { wg.Go(func() { prsdTkn.resp = &TokenResponse{} prsdTkn.resp.WithKey(token) - storeStrategy, err := c.store.GetImplementation(token.Prefix()) + val, err := c.store.GetValue(token) if err != nil { prsdTkn.resp.Err = err return } - // storeStrategy.GetValue(token) - v, err := storeStrategy.GetValue(token) - if err != nil { - prsdTkn.resp.Err = err - return - } - prsdTkn.resp.WithValue(v) + prsdTkn.resp.WithValue(val) }) } @@ -195,6 +194,7 @@ func (c *Generator) 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 @@ -202,12 +202,19 @@ func (c *Generator) 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 } diff --git a/generator/generator_test.go b/generator/generator_test.go index 6e5418b..86aa3ea 100644 --- a/generator/generator_test.go +++ b/generator/generator_test.go @@ -2,204 +2,217 @@ package generator_test import ( "context" + "errors" + "fmt" + "slices" "testing" "github.com/DevLabFoundry/configmanager/v3/config" "github.com/DevLabFoundry/configmanager/v3/generator" + "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 -// } - -// g := generator.New(context.TODO(), func(gv *generator.Generator) { -// gv.Logger = log.New(&bytes.Buffer{}) -// }) -// g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) -// got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) - -// if err != nil { -// t.Fatal("errored on generate") -// } -// if len(got) != 1 { -// t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 1) -// } -// }) + 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) -// 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 -// } - -// g := generator.New(context.TODO()) -// g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc}) -// got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) - -// if err != nil { -// t.Fatal("errored on generate") -// } -// if len(got) != 0 { -// t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) -// } -// }) + got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) -// 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 -// } - -// g := generator.New(context.TODO()) -// g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: custFunc, store: nil}) -// got, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token|key1.key2"}) - -// if err != nil { -// t.Fatal("errored on generate") -// } -// if len(got) != 1 { -// 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") -// } -// }) -// } + if err != nil { + t.Fatal("errored on generate") + } + if len(got) != 1 { + t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 1) + } + }) -// func TestGenerate_withKeys_lookup(t *testing.T) { -// ttests := map[string]struct { -// custFunc strategy.StrategyFunc -// 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 -// }, -// 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 -// }, -// 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 -// }, -// 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 -// }, -// token: "AWSPARAMSTR://mountPath/token|noprop", -// expectVal: `foo":"bar","key1":{"key2":123}}`, -// }, -// } -// for name, tt := range ttests { -// t.Run(name, func(t *testing.T) { -// g := generator.New(context.TODO()) -// g.WithStrategyMap(strategy.StrategyFuncMap{config.ParamStorePrefix: tt.custFunc}) -// got, err := g.Generate([]string{tt.token}) - -// if err != nil { -// t.Fatal("errored on generate") -// } -// if len(got) != 1 { -// t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) -// } -// if got[tt.token] != tt.expectVal { -// t.Errorf(testutils.TestPhraseWithContext, "incorrect value returned in parsedMap", got[tt.token], tt.expectVal) -// } -// }) -// } -// } + 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") + } -// func Test_IsParsed(t *testing.T) { -// ttests := map[string]struct { -// val any -// isParsed bool -// }{ -// "not parseable": { -// `notparseable`, false, -// }, -// "one level parseable": { -// `{"parseable":"foo"}`, true, -// }, -// "incorrect JSON": { -// `parseable":"foo"}`, false, -// }, -// } -// for name, tt := range ttests { -// t.Run(name, func(t *testing.T) { -// typ := generator.ReplacedToken{} -// got := generator.IsParsed(tt.val, typ) -// if got != tt.isParsed { -// t.Errorf(testutils.TestPhraseWithContext, "unexpected IsParsed", got, tt.isParsed) -// } -// }) -// } -// } + g := generator.New(context.TODO()) + g.WithStores(m) -// func TestGenVars_NormalizeRawToken(t *testing.T) { + _, err := g.Generate([]string{"AWSPARAMSTR://mountPath/token"}) -// t.Run("multiple tokens", func(t *testing.T) { -// g := generator.New(context.TODO()) + if err == nil { + t.Fatal("got nil, wanted err") + } + 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) -// 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) -// } -// } -// }) -// } + _, 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) { + + m := &mockStore{} + m.getVal = func(implemenation *config.ParsedTokenConfig) (string, error) { + return ``, fmt.Errorf("failed to get value") + } + + 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) != 0 { + t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) + } + }) + 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 { + store func(t *testing.T) *mockStore + token string + expectVal string + }{ + "retrieves string value correctly from a keylookup inside": { + 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": { + + 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": { + 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": { + 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}}`, + }, + } + for name, tt := range ttests { + t.Run(name, func(t *testing.T) { + g := generator.New(context.TODO()) + g.WithStores(tt.store(t)) + got, err := g.Generate([]string{tt.token}) + + if err != nil { + t.Fatal("errored on generate") + } + if len(got) != 1 { + t.Errorf(testutils.TestPhraseWithContext, "incorect number in a map", len(got), 0) + } + if got[tt.token] != tt.expectVal { + t.Errorf(testutils.TestPhraseWithContext, "incorrect value returned in parsedMap", got[tt.token], tt.expectVal) + } + }) + } +} + +func Test_IsParsed(t *testing.T) { + ttests := map[string]struct { + val any + isParsed bool + }{ + "not parseable": { + `notparseable`, false, + }, + "one level parseable": { + `{"parseable":"foo"}`, true, + }, + "incorrect JSON": { + `parseable":"foo"}`, false, + }, + } + for name, tt := range ttests { + t.Run(name, func(t *testing.T) { + typ := generator.ReplacedToken{} + got := generator.IsParsed(tt.val, typ) + if got != tt.isParsed { + t.Errorf(testutils.TestPhraseWithContext, "unexpected IsParsed", got, tt.isParsed) + } + }) + } +} func Test_ConfigManager_DiscoverTokens(t *testing.T) { ttests := map[string]struct { @@ -302,11 +315,11 @@ 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) + } + } }) } } diff --git a/internal/store/plugin.go b/internal/store/plugin.go index 912b40b..2d579ae 100644 --- a/internal/store/plugin.go +++ b/internal/store/plugin.go @@ -51,6 +51,10 @@ func NewPlugin(ctx context.Context, path string) (*Plugin, error) { 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 { diff --git a/internal/store/plugin_test.go b/internal/store/plugin_test.go index 0185b5e..56cb55b 100644 --- a/internal/store/plugin_test.go +++ b/internal/store/plugin_test.go @@ -3,7 +3,6 @@ package store_test import ( "context" "fmt" - "os" "runtime" "testing" @@ -11,15 +10,10 @@ import ( "github.com/DevLabFoundry/configmanager/v3/internal/store" ) -// TODO: make the implementation of the plugin system more testable -func TestPlugin_GetValue_integration(t *testing.T) { - // as the plugin is technically a subprocess - // setting env vars at this level will affect the loaded plugin - os.Setenv("AWS_REGION", "eu-west-1") - os.Setenv("AWS_PROFILE", "FOO") - defer os.Unsetenv("AWS_PROFILE") - defer os.Unsetenv("AWS_REGION") - tp := fmt.Sprintf("../../.configmanager/plugins/empty/empty-%s-%s", runtime.GOOS, runtime.GOARCH) +// Note: this step depends on pre-built empty tester plugin provider +// Running the +func Test_Plugin_GetValue(t *testing.T) { + tp := fmt.Sprintf("../../tokenstore/provider/empty/bin/empty-%s-%s", runtime.GOOS, runtime.GOARCH) np, err := store.NewPlugin(context.TODO(), tp) if err != nil { t.Fatal(err) diff --git a/internal/store/store.go b/internal/store/store.go index 5ea4af0..e8fff35 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -90,12 +90,12 @@ func (s *Store) Init(ctx context.Context, implt []string) error { return nil } -func (s *Store) GetImplementation(implemenation config.ImplementationPrefix) (plugin *Plugin, err error) { - var exists bool - if plugin, exists = s.plugin.m[strings.ToLower(string(implemenation))]; exists { - return plugin, 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 nil, ErrPluginNotFound + return plugin.GetValue(implemenation) } // PluginCleanUp ensures the plugins are properly shut down diff --git a/internal/store/store_test.go b/internal/store/store_test.go index bb1c5b3..2394565 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -1 +1,3 @@ package store_test + +// From ccf0c38823b0a250b5c26fe2d643c40453afcf9e Mon Sep 17 00:00:00 2001 From: dnitsch Date: Sat, 4 Apr 2026 08:46:51 +0100 Subject: [PATCH 12/20] fix: add more tests to store package --- config/config.go | 4 +++ eirctl.yaml | 11 +++++- internal/store/plugin.go | 8 ++++- internal/store/plugin_test.go | 40 --------------------- internal/store/store.go | 20 ++++------- internal/store/store_test.go | 68 ++++++++++++++++++++++++++++++++++- 6 files changed, 95 insertions(+), 56 deletions(-) delete mode 100644 internal/store/plugin_test.go diff --git a/config/config.go b/config/config.go index ed57d80..aeec5ff 100644 --- a/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 ( diff --git a/eirctl.yaml b/eirctl.yaml index a8bd4d9..d4e5204 100644 --- a/eirctl.yaml +++ b/eirctl.yaml @@ -33,8 +33,11 @@ pipelines: name: empty env: PLUGIN: empty - - pipeline: test:unit + - 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 @@ -178,6 +181,12 @@ tasks: 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` diff --git a/internal/store/plugin.go b/internal/store/plugin.go index 2d579ae..2c87cc0 100644 --- a/internal/store/plugin.go +++ b/internal/store/plugin.go @@ -2,13 +2,18 @@ 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 { @@ -27,6 +32,7 @@ func NewPlugin(ctx context.Context, path string) (*Plugin, error) { 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() @@ -58,7 +64,7 @@ func (p *Plugin) WithTokenStore(ts tokenstore.TokenStore) { func (p *Plugin) GetValue(token *config.ParsedTokenConfig) (string, error) { result, err := p.tokenStore.Value(token.StoreToken(), []byte(token.Metadata())) if err != nil { - return "", err + return "", fmt.Errorf("%w - (%s), %v", ErrRetrieveFailed, token.String(), err) } return result, nil } diff --git a/internal/store/plugin_test.go b/internal/store/plugin_test.go deleted file mode 100644 index 56cb55b..0000000 --- a/internal/store/plugin_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package store_test - -import ( - "context" - "fmt" - "runtime" - "testing" - - "github.com/DevLabFoundry/configmanager/v3/config" - "github.com/DevLabFoundry/configmanager/v3/internal/store" -) - -// Note: this step depends on pre-built empty tester plugin provider -// Running the -func Test_Plugin_GetValue(t *testing.T) { - tp := fmt.Sprintf("../../tokenstore/provider/empty/bin/empty-%s-%s", runtime.GOOS, runtime.GOARCH) - np, err := store.NewPlugin(context.TODO(), tp) - if err != nil { - t.Fatal(err) - } - - defer np.ClientCleanUp() - token, err := config.NewParsedToken(config.ParamStorePrefix, *config.NewConfig()) - if err != nil { - t.Fatal(err) - } - - token.WithSanitizedToken("/int-test/pocketbase/admin-pwd") - got, err := np.GetValue(token) - if err != nil { - t.Fatal(err) - } - - if len(got) < 1 { - t.Fatal("empty...") - } - if got != "/int-test/pocketbase/admin-pwd->" { - t.Errorf("") - } -} diff --git a/internal/store/store.go b/internal/store/store.go index e8fff35..c35e71d 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -21,17 +21,6 @@ var ( 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 -// // -// // Defined on the package for easier re-use across the program -// type Strategy interface { -// // Value retrieves the underlying value for the token -// Value() (s string, e error) -// // SetToken -// SetToken(s *config.ParsedTokenConfig) -// } - // It includes the following methods // - fetch plugins from known sources // - maintains a list of tokens answerable by a specified pluginEngine @@ -122,12 +111,17 @@ func (s *Store) findPlugin(plugin string) (string, error) { if err != nil { return "", err } - for _, p := range []string{cwd, hd} { + + fallbackPath := []string{cwd, hd} + if val, exists := os.LookupEnv(config.CONFIGMANAGER_DIR); exists { + fallbackPath = append([]string{val}, fallbackPath...) + } + for _, p := range []string{os.Getenv(config.CONFIGMANAGER_DIR), cwd, hd} { 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 "", ErrPluginNotFound + return "", fmt.Errorf("configmanger provider: ( %s ) %w", plugin, ErrPluginNotFound) } diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 2394565..c21fcff 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -1,3 +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) + } + +} From a6e6706fa86c50c582aa086d146e04aec2424403 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Sat, 4 Apr 2026 12:28:55 +0100 Subject: [PATCH 13/20] fix: linter errors --- internal/store/store.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/store/store.go b/internal/store/store.go index c35e71d..e834880 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -116,12 +116,12 @@ func (s *Store) findPlugin(plugin string) (string, error) { if val, exists := os.LookupEnv(config.CONFIGMANAGER_DIR); exists { fallbackPath = append([]string{val}, fallbackPath...) } - for _, p := range []string{os.Getenv(config.CONFIGMANAGER_DIR), cwd, hd} { + 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("configmanger provider: ( %s ) %w", plugin, ErrPluginNotFound) + return "", fmt.Errorf("configmanager provider: ( %s ) %w", plugin, ErrPluginNotFound) } From 9d7cc13c4041abf02033d898afe9307098b1e9dc Mon Sep 17 00:00:00 2001 From: dnitsch Date: Sat, 4 Apr 2026 12:41:39 +0100 Subject: [PATCH 14/20] fix: sonar and remove unused --- .github/workflows/build.yml | 3 - internal/strategy/strategy.go | 132 ------------- internal/strategy/strategy_test.go | 287 ----------------------------- sonar-project.properties | 5 +- 4 files changed, 2 insertions(+), 425 deletions(-) delete mode 100644 internal/strategy/strategy.go delete mode 100644 internal/strategy/strategy_test.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 95e6f70..03409b1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -56,9 +56,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/internal/strategy/strategy.go b/internal/strategy/strategy.go deleted file mode 100644 index 9548f8b..0000000 --- a/internal/strategy/strategy.go +++ /dev/null @@ -1,132 +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.val, cr.Err = s.Value() -// return cr -// } - -// 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 -// } - -// 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 b567e52..0000000 --- a/internal/strategy/strategy_test.go +++ /dev/null @@ -1,287 +0,0 @@ -package strategy_test - -// import ( -// "context" -// "io" -// "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" -// ) - -// 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/sonar-project.properties b/sonar-project.properties index b070476..c823ad6 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 From dcdc163c75b1a39978fe92b483de4fc596409ea4 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Sat, 4 Apr 2026 15:33:10 +0100 Subject: [PATCH 15/20] fix: update set up tasks --- .github/workflows/build.yml | 3 +++ .github/workflows/release.yml | 3 +++ .github/workflows/release_container.yml | 17 ++++++++++------- sonar-project.properties | 3 ++- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03409b1..47c3611 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,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: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bfb8538..89db3d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,9 @@ jobs: - name: Set SemVer Version uses: gittools/actions/gitversion/execute@v4.1.0 id: gitversion + with: + overrideConfig: | + next-version=3.0.0 release: name: Release diff --git a/.github/workflows/release_container.yml b/.github/workflows/release_container.yml index 3ac8556..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: ['CI'] + 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/sonar-project.properties b/sonar-project.properties index c823ad6..818232b 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -12,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 From e1996880ea6ae151b5fbc65a83aecb9bb7c447fd Mon Sep 17 00:00:00 2001 From: dnitsch Date: Tue, 7 Apr 2026 18:45:11 +0100 Subject: [PATCH 16/20] fix: add more interim init changes --- cmd/configmanager/init.go | 44 +++++++++ configmanager.go | 4 - generator/generator.go | 5 +- generator/generator_test.go | 2 + go.mod | 4 + go.sum | 8 ++ internal/store/plugin.go | 26 +++++ internal/store/store.go | 184 +++++++++++++++++++++++++++++++----- tokenstore/README.md | 27 +++++- 9 files changed, 269 insertions(+), 35 deletions(-) create mode 100644 cmd/configmanager/init.go diff --git a/cmd/configmanager/init.go b/cmd/configmanager/init.go new file mode 100644 index 0000000..7942176 --- /dev/null +++ b/cmd/configmanager/init.go @@ -0,0 +1,44 @@ +package cmd + +import ( + "github.com/spf13/cobra" +) + +type initFlags struct { +} + +func newInitCmd(rootCmd *Root) { + + // f := &initFlags{} + + initCmd := &cobra.Command{ + Use: "init", + Aliases: []string{}, + Short: ``, + Long: ``, + RunE: func(cmd *cobra.Command, args []string) error { + // c := generator.New(cmd.Context(), func(gv *generator.Generator) { + // if rootCmd.rootFlags.verbose { + // rootCmd.logger.SetLevel(log.DebugLvl) + // } + // gv.Logger = rootCmd.logger + // }).WithConfig(config.NewConfig().WithTokenSeparator(rootCmd.rootFlags.tokenSeparator).WithKeySeparator(rootCmd.rootFlags.keySeparator)) + + // // ntm, err := c.DiscoverTokens("") + // if err != nil { + // return err + // } + + // initialise pugins here based on discovered tokens + // // + // // this can only be done once the tokens are known + // if err := c.store.Init(cmd.Context(), ntm.TokenSet()); err != nil { + // return nil, fmt.Errorf("%w, %v", ErrProvidersNotFound, err) + // } + // defer c.store.PluginCleanUp() + return nil + }, + } + + rootCmd.Cmd.AddCommand(initCmd) +} diff --git a/configmanager.go b/configmanager.go index ff6c3ea..373b71a 100644 --- a/configmanager.go +++ b/configmanager.go @@ -15,10 +15,6 @@ import ( "github.com/a8m/envsubst" ) -const ( - TERMINATING_CHAR string = `[^\'\"\s\n\\\,]` // :\@\?\/ -) - // generateAPI type generateAPI interface { Generate(tokens []string) (generator.ReplacedToken, error) diff --git a/generator/generator.go b/generator/generator.go index f489682..3c87e9d 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -23,6 +23,7 @@ var ErrProvidersNotFound = errors.New("providers not initialised") type storeIface interface { GetValue(implemenation *config.ParsedTokenConfig) (string, error) Init(ctx context.Context, implt []string) error + PluginCleanUp() } // Generator is the main struct holding the @@ -95,6 +96,8 @@ func (c *Generator) Config() *config.GenVarsConfig { return &c.config } +func (c *Generator) InitPlugins() + // Generate generates a k/v map of the tokens with their corresponding secret/paramstore values // the standard pattern of a token should follow a path like string // @@ -112,7 +115,7 @@ func (c *Generator) Generate(tokens []string) (ReplacedToken, error) { if err := c.store.Init(c.ctx, ntm.TokenSet()); err != nil { return nil, fmt.Errorf("%w, %v", ErrProvidersNotFound, err) } - + defer c.store.PluginCleanUp() // pass in default initialised retrieveStrategy // input should be rt, err := c.generate(ntm) diff --git a/generator/generator_test.go b/generator/generator_test.go index 86aa3ea..f849d71 100644 --- a/generator/generator_test.go +++ b/generator/generator_test.go @@ -28,6 +28,8 @@ func (m mockStore) Init(ctx context.Context, implt []string) error { return nil } +func (m mockStore) PluginCleanUp() {} + func Test_Generate(t *testing.T) { t.Run("succeeds", func(t *testing.T) { diff --git a/go.mod b/go.mod index dff52b3..77b53d8 100644 --- a/go.mod +++ b/go.mod @@ -17,9 +17,12 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/hashicorp/yamux v0.1.2 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/oklog/run v1.2.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/stretchr/testify v1.11.1 // indirect + golang.org/x/term v0.41.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) @@ -31,6 +34,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/schollz/progressbar/v3 v3.19.0 github.com/spf13/pflag v1.0.10 // indirect golang.org/x/net v0.52.0 // indirect golang.org/x/sys v0.42.0 // indirect diff --git a/go.sum b/go.sum index eeb042f..f19afa2 100644 --- a/go.sum +++ b/go.sum @@ -49,15 +49,21 @@ 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/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ= +github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc= +github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -92,6 +98,8 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc 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/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= diff --git a/internal/store/plugin.go b/internal/store/plugin.go index 2c87cc0..4c75d1b 100644 --- a/internal/store/plugin.go +++ b/internal/store/plugin.go @@ -68,3 +68,29 @@ func (p *Plugin) GetValue(token *config.ParsedTokenConfig) (string, error) { } return result, nil } + +type PluginDownloadInfo struct { + BaseUrl string + Name string +} + +const corePluginBaseUrl = "https://github.com/DevLabFoundry/configmanager/releases" + +type PluginDownloadInfoMap map[string]*PluginDownloadInfo + +// corePluginMap are the configmanager maintained plugins +var corePluginMap PluginDownloadInfoMap = map[string]*PluginDownloadInfo{ + "empty": { + BaseUrl: corePluginBaseUrl, + Name: "", + }, + "awsparamstr": { + BaseUrl: corePluginBaseUrl, + Name: "", + }, + "awssecrets": { + BaseUrl: corePluginBaseUrl, + Name: "", + }, + // ... +} diff --git a/internal/store/store.go b/internal/store/store.go index e834880..c2ed1fb 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -4,13 +4,19 @@ import ( "context" "errors" "fmt" + "io" + "maps" + "net/http" + "net/url" "os" "path" + "path/filepath" "runtime" "strings" "sync" "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/schollz/progressbar/v3" ) var ( @@ -18,7 +24,8 @@ var ( 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") + ErrPluginIssue = errors.New("plugin init failed") + ErrMkdirAllFail = errors.New("unable to create the required directory") ) // It includes the following methods @@ -38,37 +45,92 @@ func (p pluginMap) Add(key string, pl *Plugin) { } const ( - loc string = ".configmanager/plugins" - namePattern string = "%s-%s-%s" + pluginsLocation string = "." + config.SELF_NAME + "/plugins" + namePattern string = "%s-%s-%s" ) type osOps struct { UserHomeDir func() (string, error) Getwd func() (dir string, err error) + MkdirAll func(path string, perm os.FileMode) error + Create func(name string) (io.WriteCloser, error) } +type downloadClient interface { + Do(*http.Request) (*http.Response, error) +} type Store struct { - plugin pluginMap - osOps osOps + plugin pluginMap + osOps osOps + downloadInfoMap PluginDownloadInfoMap + downloadClient downloadClient } -func New(ctx context.Context) *Store { - pm := pluginMap{mu: &sync.Mutex{}, m: make(map[string]*Plugin)} +type StoreOpts func(s *Store) + +func New(ctx context.Context, opts ...StoreOpts) *Store { + pm := pluginMap{ + mu: &sync.Mutex{}, + m: make(map[string]*Plugin), + } + os.Getwd() s := &Store{ plugin: pm, - osOps: osOps{UserHomeDir: os.UserHomeDir, Getwd: os.Getwd}, + osOps: osOps{ + UserHomeDir: os.UserHomeDir, + Getwd: os.Getwd, + MkdirAll: os.MkdirAll, + Create: func(name string) (io.WriteCloser, error) { + return os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0700) + }, + }, + downloadInfoMap: corePluginMap, + downloadClient: &http.Client{}, } + + for _, o := range opts { + o(s) + } + return s } +func WithOsOps(v osOps) StoreOpts { + return func(s *Store) { + s.osOps = v + } +} + +func WithDownloadClient(v downloadClient) StoreOpts { + return func(s *Store) { + s.downloadClient = v + } +} + +func WithAdditionalPluginInfo(v PluginDownloadInfoMap) StoreOpts { + return func(s *Store) { + maps.Copy(s.downloadInfoMap, v) + } +} + // Init ensures all the discovered tokens have their implementations initialised +// +// NOTE: it is important to package the providers at a build stage +// if your target deployment environment does not support open network outbound connections func (s *Store) Init(ctx context.Context, implt []string) error { + dir, err := s.configManagerDir() + if err != nil { + return err + } for _, plugin := range implt { - plpath, err := s.findPlugin(plugin) + // we first look for existing plugins + plpath, err := s.findPlugin(dir, plugin) if err != nil { - return err + // try to retrieve from remote source + return fmt.Errorf("configmanager provider: ( %s ) %w\n%v", plugin, ErrPluginIssue, err) } + // Initialising the plugins will ensure the client (configmanager-core) and the server (token-store-plugin-provider) are at expected versions of each other p, err := NewPlugin(ctx, plpath) if err != nil { // wrap in init error @@ -82,7 +144,7 @@ func (s *Store) Init(ctx context.Context, implt []string) error { func (s *Store) GetValue(implemenation *config.ParsedTokenConfig) (string, error) { plugin, exists := s.plugin.m[strings.ToLower(string(implemenation.Prefix()))] if !exists { - return "", ErrPluginNotFound + return "", ErrPluginIssue } return plugin.GetValue(implemenation) } @@ -100,28 +162,100 @@ func (s *Store) PluginCleanUp() { // // current dir // home dir -func (s *Store) findPlugin(plugin string) (string, error) { - // fallback locations - // current dir - cwd, err := os.Getwd() +func (s *Store) findPlugin(pluginDir, plugin string) (string, error) { + fullPluginPath := path.Join(pluginDir, plugin, fmt.Sprintf(namePattern, plugin, runtime.GOOS, runtime.GOARCH)) + if _, err := os.Stat(fullPluginPath); err == nil { + // plugin exists let's use it + return fullPluginPath, nil + } + + // Plugin is not found - will attempt to download and write them to the first specified directory + return s.downloadPlugin(fullPluginPath, plugin) +} + +// downloadPlugin deals with a specific plugin and only gets the current architecture +// +// it will create the required directory +func (s *Store) downloadPlugin(pluginFullPath, plugin string) (string, error) { + + releaseName := filepath.Base(pluginFullPath) + // This is opinionated and all windows plugins must be stored with the `.exe` extension + if runtime.GOOS == "windows" { + releaseName = releaseName + ".exe" + pluginFullPath = pluginFullPath + ".exe" + } + + // as an example + // https://github.com/DevLabFoundry/configmanager/releases/download/v3.0.0/awsparamstr-linux-amd64 + if err := s.osOps.MkdirAll(filepath.Dir(pluginFullPath), 0777); err != nil { + return "", fmt.Errorf("%w, %v", ErrMkdirAllFail, err) + } + + w, err := s.osOps.Create(pluginFullPath) if err != nil { return "", err } - hd, err := os.UserHomeDir() + defer w.Close() + + specific := "download/%s" + // latest := "latest/download" + // TODO: need to think about providing these in a more configurable way + // + releasePath := path.Join(fmt.Sprintf(specific, "v3.0.0"), releaseName) + + pluginInfo, found := s.downloadInfoMap[plugin] + if !found { + return "", fmt.Errorf("download info not found for plugin ( %s )", plugin) + } + + link, err := url.Parse(fmt.Sprintf("%s/%s", pluginInfo.BaseUrl, releasePath)) if err != nil { return "", err } - fallbackPath := []string{cwd, hd} - if val, exists := os.LookupEnv(config.CONFIGMANAGER_DIR); exists { - fallbackPath = append([]string{val}, fallbackPath...) + req := &http.Request{ + URL: link, + Method: http.MethodGet, + } + resp, err := s.downloadClient.Do(req) + if err != nil { + return "", err + } + + defer resp.Body.Close() + + bar := progressbar.DefaultBytes( + resp.ContentLength, + "downloading", releaseName, + ) + + if _, err = io.Copy(io.MultiWriter(w, bar), resp.Body); err != nil { + return "", err } - 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 pluginFullPath, nil + +} + +// configManagerDir ensures the directory exists and returns the correct one based on config +// +// It will return the full path to /some/dir/.configmanager and and ensures it exists +func (s *Store) configManagerDir() (string, error) { + var err error + + // if env var provided - it takes precendence over current directory + val, exists := os.LookupEnv(config.CONFIGMANAGER_DIR) + if !exists { + // otherwise we default to the current directory - i.e. from which the command is being run + val, err = s.osOps.Getwd() + if err != nil { + return "", err } } - return "", fmt.Errorf("configmanager provider: ( %s ) %w", plugin, ErrPluginNotFound) + + initialConfigPath := filepath.Join(val, pluginsLocation) + if err := s.osOps.MkdirAll(initialConfigPath, 0777); err != nil { + return "", err + } + return initialConfigPath, nil } diff --git a/tokenstore/README.md b/tokenstore/README.md index 5ed18b5..d574a6f 100644 --- a/tokenstore/README.md +++ b/tokenstore/README.md @@ -2,34 +2,47 @@ The plugin architecture for configmanager is built using the [go-plugin](https://github.com/hashicorp/go-plugin?tab=readme-ov-file#go-plugin-system-over-rpc) from hashicorp. - The existing implementations are converted into plugins using the gRPC model and are built using gRPC [go-plugin](https://github.com/hashicorp/go-plugin?tab=readme-ov-file#go-plugin-system-over-rpc) and generated/updated with the [buf cli](https://buf.build/docs/cli/). +You can run the generator with the below task in eirctl + +`eirctl run pipeline proto:build` ## Plugin Architecture + +### Diagram ```mermaid ``` +### Local dependencies + The plugins will need to be downloaded into any one of these locations on disk, they will be checked in this order -- currentDirectory (directory from which the configmanager executable is run) -- users home directory +- IF PRESENT IT WILL TAKE PRECEDENCE -> `$CONFIGMANAGER_DIR` environment variable that points to the root of configmanager managed/used local dir/files + - e.g. given a path to an AWSPARAMSTR implementation under this path `/my/path/.configmanager/plugins/awsparamstr/awsparamstr-linux-amd64` + the corresponding value would be `CONFIGMANAGER_DIR=/my/path` - the application will create the `.configmanager/plugins/$PLUGIN_PREFIX_LOWERCASE/...` +- Curren tDirectory (directory from which the configmanager executable is run) +- Current Users Home directory The plugin is expected to be found under this path in the above locations > `.configmanager/plugins/$PLUGIN_PREFIX_LOWERCASE/$PLUGIN_PREFIX_LOWERCASE-$GOOS-$GOARCH` e.g. in case of the AWS Parameter Store plugin `.configmanager/plugins/awsparamstr/awsparamstr-linux-amd64` +> NB: `CONFIGMANAGER_DIR` is a global variable to the application and may have other uses in the future beyond the `plugins` directory +### API Flow -## Alternate architecture explored +The existing Go-API flows can stil be used in the same way, however, you will need to pre-download the provider plugins you plan on using as part of your flow. -As part of the decision on which pluging architecture to use we also explored an alternate architecture using WASIP1. +> When using something like an AWS Lambda environment, one can leverage AWS Lambda layers to achieve the above folder structure. +## Alternate architecture explored +As part of the decision on which pluging architecture to use we also explored an alternate architecture using WASIP1. ```go import ( @@ -238,3 +251,7 @@ func main() {} build using the `-buildmode=c-shared` which will convert the module to a reactor module `GOOS=wasip1 GOARCH=wasm go build -buildmode=c-shared -o awsparams.wasm` + +> the outcome of an approach to this was fundamentally flawed in that any WASM code is heavily sandboxed and performing any kind of network calls is impossible in the OOTB set up and only slightly possible by using alternate WASM runtimes or add-ons + +> Good fun to explore though :smiley: From 9c7016b31295f217091f6e2601d3ac681e71f4dc Mon Sep 17 00:00:00 2001 From: dnitsch Date: Tue, 7 Apr 2026 19:10:56 +0100 Subject: [PATCH 17/20] fix: add single initialization to main --- tokenstore/provider/awsparamstr/main.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tokenstore/provider/awsparamstr/main.go b/tokenstore/provider/awsparamstr/main.go index a01ecda..e7b0c06 100644 --- a/tokenstore/provider/awsparamstr/main.go +++ b/tokenstore/provider/awsparamstr/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "os" "github.com/DevLabFoundry/configmanager-plugin/awsparamstr/impl" "github.com/DevLabFoundry/configmanager/v3/tokenstore" @@ -9,26 +10,30 @@ import ( "github.com/hashicorp/go-plugin" ) +type implIface interface { + Value(token string, metadata []byte) (string, error) +} type TokenStorePlugin struct { - log hclog.Logger + impl implIface // Value(token string, metadata []byte) (string, error) } 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) + return ts.impl.Value(key, metadata) } func main() { log := hclog.New(hclog.DefaultOptions) log.SetLevel(hclog.LevelFromString("error")) - // if os.Getenv("CONFIGMANAGER_LOG") - ts := TokenStorePlugin{log: log} + i, err := impl.NewParamStore(context.Background(), log) + if err != nil { + log.Error("error", err) + os.Exit(1) + } + + ts := TokenStorePlugin{impl: i} + plugin.Serve(&plugin.ServeConfig{ - // Logger: , HandshakeConfig: tokenstore.Handshake, Plugins: map[string]plugin.Plugin{ "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: ts}, From 41c8097a14ff229504bd827ac3f6f034bc761921 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Thu, 9 Apr 2026 19:43:43 +0100 Subject: [PATCH 18/20] fix: init command --- cmd/configmanager/configmanager.go | 1 + cmd/configmanager/init.go | 54 ++++++++++-------- docs/adding-provider.md | 73 ++----------------------- generator/generator.go | 11 +++- internal/store/store.go | 2 +- tokenstore/provider/awsparamstr/main.go | 5 ++ 6 files changed, 53 insertions(+), 93 deletions(-) diff --git a/cmd/configmanager/configmanager.go b/cmd/configmanager/configmanager.go index 557ea84..c989e55 100644 --- a/cmd/configmanager/configmanager.go +++ b/cmd/configmanager/configmanager.go @@ -58,6 +58,7 @@ func addSubCmds(rootCmd *Root) { newFromStrCmd(rootCmd) newRetrieveCmd(rootCmd) newInsertCmd(rootCmd) + newInitCmd(rootCmd) } func (rc *Root) Execute(ctx context.Context) error { diff --git a/cmd/configmanager/init.go b/cmd/configmanager/init.go index 7942176..e51d68e 100644 --- a/cmd/configmanager/init.go +++ b/cmd/configmanager/init.go @@ -1,44 +1,52 @@ package cmd import ( + "strings" + + "github.com/DevLabFoundry/configmanager/v3/config" + "github.com/DevLabFoundry/configmanager/v3/generator" + "github.com/DevLabFoundry/configmanager/v3/internal/log" "github.com/spf13/cobra" ) type initFlags struct { + tokens []string } func newInitCmd(rootCmd *Root) { - // f := &initFlags{} + f := &initFlags{} initCmd := &cobra.Command{ Use: "init", Aliases: []string{}, - Short: ``, - Long: ``, + Short: `Initialises the plugins required for config retrieval.`, + Long: `Initialises the plugins required by creating the relevant folder structure and plugin downloads for the current architecture and operating system`, RunE: func(cmd *cobra.Command, args []string) error { - // c := generator.New(cmd.Context(), func(gv *generator.Generator) { - // if rootCmd.rootFlags.verbose { - // rootCmd.logger.SetLevel(log.DebugLvl) - // } - // gv.Logger = rootCmd.logger - // }).WithConfig(config.NewConfig().WithTokenSeparator(rootCmd.rootFlags.tokenSeparator).WithKeySeparator(rootCmd.rootFlags.keySeparator)) - - // // ntm, err := c.DiscoverTokens("") - // if err != nil { - // return err - // } - - // initialise pugins here based on discovered tokens - // // - // // this can only be done once the tokens are known - // if err := c.store.Init(cmd.Context(), ntm.TokenSet()); err != nil { - // return nil, fmt.Errorf("%w, %v", ErrProvidersNotFound, err) - // } - // defer c.store.PluginCleanUp() + if f.tokens != nil { + lt := []string{} + for _, v := range f.tokens { + lt = append(lt, strings.ToLower(v)) + } + c := generator.New(cmd.Context(), func(gv *generator.Generator) { + if rootCmd.rootFlags.verbose { + rootCmd.logger.SetLevel(log.DebugLvl) + } + gv.Logger = rootCmd.logger + // still need to parse the root level flags in case the key or token separator is specified + }).WithConfig(config.NewConfig().WithTokenSeparator(rootCmd.rootFlags.tokenSeparator).WithKeySeparator(rootCmd.rootFlags.keySeparator)) + + // NOTE: add additional bells and whistles here to allow for config file parsing and lock management + return c.InitPlugins(lt) + } + // logrus.Debug("zero tokens specified not initialising") return nil }, } - + initCmd.PersistentFlags().StringArrayVarP(&f.tokens, "plugin", "", []string{}, `Multi-valued flag to specify the plugins to use with configmanager. +When not all are specified they will not be pre-initialised and instead will be initialised during the generate run, +NOTE: this may cause issues in more controlled environments +`) + _ = initCmd.MarkPersistentFlagRequired("plugin") rootCmd.Cmd.AddCommand(initCmd) } diff --git a/docs/adding-provider.md b/docs/adding-provider.md index 9c9be84..3c5b362 100644 --- a/docs/adding-provider.md +++ b/docs/adding-provider.md @@ -1,72 +1,9 @@ -# Adding a provider +# Providers -Add Token Prefix +The providers architecture and design is [documented here](../tokenstore/README.md), refer to it for additional details. -```go -const ( - // AWS SecretsManager prefix - SecretMgrPrefix ImplementationPrefix = "AWSSECRETS" - // AWS Parameter Store prefix - ParamStorePrefix ImplementationPrefix = "AWSPARAMSTR" - // Azure Key Vault Secrets prefix - AzKeyVaultSecretsPrefix ImplementationPrefix = "AZKVSECRET" - // Hashicorp Vault prefix - HashicorpVaultPrefix ImplementationPrefix = "VAULT" - // GcpSecrets - GcpSecretsPrefix ImplementationPrefix = "GCPSECRETS" -) -``` +## Adding a provider -```go -var ( - // default varPrefix used by the replacer function - // any token must beging with one of these else - // it will be skipped as not a replaceable token - VarPrefix = map[ImplementationPrefix]bool{SecretMgrPrefix: true, ParamStorePrefix: true, AzKeyVaultSecretsPrefix: true, GcpSecretsPrefix: true, HashicorpVaultPrefix: true} // <-- ADD here -) -``` +Currently there are only the core providers (from v2) and they are stored in independent repositories on github for now. -ensure your implementation satisfy the `genVarsStrategy` interface - -```go -type genVarsStrategy interface { - tokenVal(rs *retrieveStrategy) (s string, e error) - setTokenVal(s string) -} -``` - -Even if the native type is K/V return a marshalled version of the JSON as the rest of the flow will decide how to present it back to the final consumer. - -Custom properties inside the GetValue request, you could specify your own Config struct for the provider, e.g. HashiVault implementation - -```go -// VaultConfig holds the parseable metadata struct -type VaultConfig struct { - Version string `json:"version"` - Role string `json:"iam_role"` -} -``` - -You could then use it on the backingStore object - -```go -type VaultStore struct { - svc hashiVaultApi - ctx context.Context - config *VaultConfig - token string -} -``` - -On initialize of the instance or in the setTokenVal method (see GCPSecrets or AWSSecrets/ParamStore examples). - -```go -storeConf := &VaultConfig{} -initialToken := ParseMetadata(token, storeConf) -imp := &VaultStore{ - ctx: ctx, - config: storeConf, -} -``` - -Where the initialToken is the original Token without the metadata in brackets and the `storeConf` pointer will have been filled with any of the parsed metadata and used in the actual provider implementation, see any of the providers for a sample implementation. +If you want to add an additional provider it needs \ No newline at end of file diff --git a/generator/generator.go b/generator/generator.go index 3c87e9d..ced2a4c 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -96,7 +96,16 @@ func (c *Generator) Config() *config.GenVarsConfig { return &c.config } -func (c *Generator) InitPlugins() +func (c *Generator) InitPlugins(tokenset []string) error { + // initialise pugins here based on discovered tokens + // + // this can only be done once the tokens are known + if err := c.store.Init(c.ctx, tokenset); err != nil { + return fmt.Errorf("%w, %v", ErrProvidersNotFound, err) + } + defer c.store.PluginCleanUp() + return nil +} // Generate generates a k/v map of the tokens with their corresponding secret/paramstore values // the standard pattern of a token should follow a path like string diff --git a/internal/store/store.go b/internal/store/store.go index c2ed1fb..4f694ee 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -226,7 +226,7 @@ func (s *Store) downloadPlugin(pluginFullPath, plugin string) (string, error) { bar := progressbar.DefaultBytes( resp.ContentLength, - "downloading", releaseName, + fmt.Sprintf("downloading: %s", releaseName), ) if _, err = io.Copy(io.MultiWriter(w, bar), resp.Body); err != nil { diff --git a/tokenstore/provider/awsparamstr/main.go b/tokenstore/provider/awsparamstr/main.go index e7b0c06..14ab277 100644 --- a/tokenstore/provider/awsparamstr/main.go +++ b/tokenstore/provider/awsparamstr/main.go @@ -38,6 +38,11 @@ func main() { Plugins: map[string]plugin.Plugin{ "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: ts}, }, + VersionedPlugins: map[int]plugin.PluginSet{ + 1: { + "configmanager_token_store": &tokenstore.GRPCPlugin{Impl: ts}, + }, + }, // A non-nil value here enables gRPC serving for this plugin... GRPCServer: plugin.DefaultGRPCServer, }) From 66fcfd7c81581cd6238f0b32882c06e998a78737 Mon Sep 17 00:00:00 2001 From: dnitsch Date: Thu, 9 Apr 2026 19:45:03 +0100 Subject: [PATCH 19/20] fix: update token cookie value --- tokenstore/interface.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokenstore/interface.go b/tokenstore/interface.go index faaa4c5..e403934 100644 --- a/tokenstore/interface.go +++ b/tokenstore/interface.go @@ -14,7 +14,7 @@ var Handshake = plugin.HandshakeConfig{ // This isn't required when using VersionedPlugins ProtocolVersion: 1, MagicCookieKey: "CONFIGMANAGER_PLUGIN", - MagicCookieValue: "hello", + MagicCookieValue: "configmanager-plugin-hello", } // // PluginMap is the map of plugins we can dispense. From 4b8db7d72c69a3fb1540335dc1b12a9f3eea48bc Mon Sep 17 00:00:00 2001 From: dnitsch Date: Fri, 10 Apr 2026 19:57:33 +0100 Subject: [PATCH 20/20] fix: add constant for LOG --- config/config.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/config.go b/config/config.go index aeec5ff..8702e5b 100644 --- a/config/config.go +++ b/config/config.go @@ -13,6 +13,8 @@ const ( // // If it is empty or unset the default locations for these is `$PWD/.configmanager` and then `~/.configmanager` CONFIGMANAGER_DIR string = "CONFIGMANAGER_DIR" + + CONFIGMANAGER_LOG string = "CONFIGMANAGER_LOG" ) const (