From 9ab631fffcf60891ffc30575c507c28916d5600c Mon Sep 17 00:00:00 2001 From: Aleksandr Britvin Date: Tue, 21 Jan 2025 17:22:51 +0100 Subject: [PATCH] Fix value processor. Cover with tests. (#14) Fix value processor. Cover with tests. --- go.mod | 13 ++++---- go.sum | 20 ++++++------- plugin.go | 48 +++++++++++++++++------------- plugin_test.go | 80 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 35 deletions(-) create mode 100644 plugin_test.go diff --git a/go.mod b/go.mod index 73f8228..47dbf0d 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,8 @@ toolchain go1.23.4 require ( filippo.io/age v1.2.1 - github.com/launchrctl/launchr v0.17.1 + github.com/launchrctl/launchr v0.17.2 + github.com/stretchr/testify v1.10.0 golang.org/x/term v0.28.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -20,6 +21,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/containerd/console v1.0.4 // indirect github.com/containerd/log v0.1.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.5.0+incompatible // indirect github.com/docker/go-connections v0.5.0 // indirect @@ -49,6 +51,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pterm/pterm v0.12.80 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect @@ -57,11 +60,11 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/otel v1.33.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect - go.opentelemetry.io/otel/metric v1.33.0 // indirect - go.opentelemetry.io/otel/trace v1.33.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/crypto v0.32.0 // indirect diff --git a/go.sum b/go.sum index cae8ea9..f2ca249 100644 --- a/go.sum +++ b/go.sum @@ -227,8 +227,8 @@ 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/launchrctl/launchr v0.17.1 h1:0mzOBE2M776MVQf62IuT686u/cZKQJDruMlVRq6RQw0= -github.com/launchrctl/launchr v0.17.1/go.mod h1:aI/DfDXJLk7xya2jowNAAykWeRoe1Tp6qI2NGGJwmj8= +github.com/launchrctl/launchr v0.17.2 h1:3wFUxdIvoWERREy+xgi2N69VXyy2Vkq2Lh75nw0fhBw= +github.com/launchrctl/launchr v0.17.2/go.mod h1:aI/DfDXJLk7xya2jowNAAykWeRoe1Tp6qI2NGGJwmj8= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -374,20 +374,20 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3 go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= -go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= -go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= -go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= -go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= diff --git a/plugin.go b/plugin.go index 5320a58..7e5c9c9 100644 --- a/plugin.go +++ b/plugin.go @@ -18,7 +18,7 @@ import ( ) const ( - getByKeyProc = "keyring.GetKeyValue" + procGetKeyValue = "keyring.GetKeyValue" errTplNotFoundURL = "%s not found in keyring. Use `%s keyring:login` to add it." errTplNotFoundKey = "%s not found in keyring. Use `%s keyring:set` to add it." ) @@ -62,43 +62,51 @@ func (p *Plugin) OnAppInit(app launchr.App) error { var m action.Manager app.GetService(&m) - AddValueProcessors(m, p.k) + addValueProcessors(m, p.k) return nil } -// AddValueProcessors adds a keyring [action.ValueProcessor] to [action.Manager]. -func AddValueProcessors(m action.Manager, keyring Keyring) { - getByKey := func(value any, options map[string]any) (any, error) { - return getByKeyProcessor(value, options, keyring) +// GetKeyValueProcessorOptions is a [action.ValueProcessorOptions] struct. +type GetKeyValueProcessorOptions struct { + Key string `yaml:"key"` +} + +// Validate implements [action.ValueProcessorOptions] interface. +func (o *GetKeyValueProcessorOptions) Validate() error { + if o.Key == "" { + return fmt.Errorf(`option "key" is required for %q processor`, procGetKeyValue) } + return nil +} - proc := action.NewFuncProcessor([]jsonschema.Type{jsonschema.String}, getByKey) - m.AddValueProcessor(getByKeyProc, proc) +// addValueProcessors adds a keyring [action.ValueProcessor] to [action.Manager]. +func addValueProcessors(m action.Manager, keyring Keyring) { + m.AddValueProcessor(procGetKeyValue, action.GenericValueProcessor[*GetKeyValueProcessorOptions]{ + Types: []jsonschema.Type{jsonschema.String}, + Fn: func(v any, opts *GetKeyValueProcessorOptions, ctx action.ValueProcessorContext) (any, error) { + return processGetByKey(v, opts, ctx, keyring) + }, + }) } -func getByKeyProcessor(value any, options map[string]any, k Keyring) (any, error) { +func processGetByKey(value any, opts *GetKeyValueProcessorOptions, ctx action.ValueProcessorContext, k Keyring) (any, error) { val, ok := value.(string) if !ok && value != nil { return val, fmt.Errorf( "string type is expected for %q processor. Change value type or remove the processor", - getByKeyProc, + procGetKeyValue, ) } - if val != "" { - launchr.Term().Warning().Printfln("Skipping processor %q, value is not empty. Value will remain unchanged", getByKeyProc) - launchr.Log().Warn("skipping processor, value is not empty", "processor", getByKeyProc) + if ctx.IsChanged { + launchr.Term().Warning().Printfln("Skipping processor %q, value is not empty. Value will remain unchanged", procGetKeyValue) + launchr.Log().Warn("skipping processor, value is not empty", "processor", procGetKeyValue) return value, nil } - key, ok := options["key"].(string) - if !ok { - return value, fmt.Errorf(`option "key" is required for %q processor`, getByKeyProc) - } - - v, err := k.GetForKey(key) + v, err := k.GetForKey(opts.Key) if err != nil { - return value, buildNotFoundError(key, errTplNotFoundKey, err) + return value, buildNotFoundError(opts.Key, errTplNotFoundKey, err) } return v.Value, nil diff --git a/plugin_test.go b/plugin_test.go new file mode 100644 index 0000000..b659478 --- /dev/null +++ b/plugin_test.go @@ -0,0 +1,80 @@ +package keyring + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/launchrctl/launchr/pkg/action" +) + +const testActionYaml = ` +runtime: plugin +action: + title: test keyring + options: + - name: secret + process: + - processor: keyring.GetKeyValue + options: + key: storedsecret +` + +const testActionYamlWithDefault = ` +runtime: plugin +action: + title: test keyring + options: + - name: secret + default: "mydefault" + process: + - processor: keyring.GetKeyValue + options: + key: storedsecret +` + +const testActionYamlWrongOptions = ` +runtime: plugin +action: + title: test keyring + options: + - name: secret + process: + - processor: keyring.GetKeyValue +` + +func Test_KeyringProcessor(t *testing.T) { + // Prepare services. + k := &keyringService{ + store: &dataStoreYaml{file: &plainFile{fname: "teststorage.yaml"}}, + } + am := action.NewManager() + addValueProcessors(am, k) + + // Prepare test data. + expected := "my_secret" + err := k.AddItem(KeyValueItem{Key: "storedsecret", Value: expected}) + require.NoError(t, err) + + expConfig := action.InputParams{ + "secret": expected, + } + expGiven := action.InputParams{ + "secret": "my_user_secret", + } + errOpts := fmt.Errorf("option %q is required for %q processor", "key", procGetKeyValue) + tt := []action.TestCaseValueProcessor{ + {Name: "get keyring keyvalue - no input given", Yaml: testActionYaml, ExpOpts: expConfig}, + {Name: "get keyring keyvalue - default and no input given", Yaml: testActionYamlWithDefault, ExpOpts: expConfig}, + {Name: "get keyring keyvalue - input given", Yaml: testActionYaml, Opts: expGiven, ExpOpts: expGiven}, + {Name: "get keyring keyvalue - wrong options", Yaml: testActionYamlWrongOptions, ErrInit: errOpts}, + } + for _, tt := range tt { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + t.Parallel() + tt.Test(t, am) + }) + } +}