Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix value processor. Cover with tests. #14

Merged
merged 2 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
48 changes: 28 additions & 20 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -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."
)
Expand Down Expand Up @@ -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
Expand Down
80 changes: 80 additions & 0 deletions plugin_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
Loading