Skip to content
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
1 change: 0 additions & 1 deletion app.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ func (app *appImpl) init() error {
actionMngr := action.NewManager(
action.WithDefaultRuntime,
action.WithContainerRuntimeConfig(config, name+"_"),
action.WithValueProcessors(),
)

// Register services for other modules.
Expand Down
12 changes: 6 additions & 6 deletions internal/launchr/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ type ConfigAware interface {

type cachedProps = map[string]reflect.Value
type config struct {
mx sync.Mutex
root fs.FS
fname fs.DirEntry
rootPath string
cached cachedProps
koanf *koanf.Koanf
mx sync.Mutex // mx is a mutex to read/cache values.
root fs.FS // root is a base dir filesystem.
fname fs.DirEntry // fname is a file storing the config.
rootPath string // rootPath is a base dir path.
cached cachedProps // cached is a map of cached properties read from a file.
koanf *koanf.Koanf // koanf is the driver to read the yaml config.
}

func findConfigFile(root fs.FS) fs.DirEntry {
Expand Down
86 changes: 33 additions & 53 deletions pkg/action/action.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
)

var (
errTplNotApplicableProcessor = "invalid configuration, processor can't be applied to value of type %s"
errTplNotApplicableProcessor = "invalid configuration, processor %q can't be applied to a parameter of type %s"
errTplNonExistProcessor = "requested processor %q doesn't exist"
errTplErrorOnProcessor = "failed to process parameter %q with %q: %w"
)

// Action is an action definition with a contextual id (name), working directory path
Expand Down Expand Up @@ -78,8 +79,18 @@ func (a *Action) Clone() *Action {
}

// SetProcessors sets the value processors for an [Action].
func (a *Action) SetProcessors(list map[string]ValueProcessor) {
a.processors = list
func (a *Action) SetProcessors(list map[string]ValueProcessor) error {
def := a.ActionDef()
for _, params := range []ParametersList{def.Arguments, def.Options} {
for _, p := range params {
err := p.InitProcessors(list)
if err != nil {
return err
}
}
}

return nil
}

// GetProcessors returns processors map.
Expand Down Expand Up @@ -193,13 +204,13 @@ func (a *Action) SetInput(input *Input) (err error) {
def := a.ActionDef()

// Process arguments.
err = a.processInputParams(def.Arguments, input.ArgsNamed(), nil)
err = a.processInputParams(def.Arguments, input.Args(), input.ArgsChanged())
if err != nil {
return err
}

// Process options.
err = a.processInputParams(def.Options, input.OptsAll(), input.OptsChanged())
err = a.processInputParams(def.Options, input.Opts(), input.OptsChanged())
if err != nil {
return err
}
Expand All @@ -216,63 +227,32 @@ func (a *Action) SetInput(input *Input) (err error) {
}

func (a *Action) processInputParams(def ParametersList, inp InputParams, changed InputParams) error {
var err error
for _, p := range def {
if _, ok := inp[p.Name]; !ok {
continue
}

if changed != nil {
if _, ok := changed[p.Name]; ok {
continue
_, isChanged := changed[p.Name]
res := inp[p.Name]
for i, procDef := range p.Process {
handler := p.processors[i]
res, err = handler(res, ValueProcessorContext{
ValOrig: inp[p.Name],
IsChanged: isChanged,
DefParam: p,
Action: a,
})
if err != nil {
return fmt.Errorf(errTplErrorOnProcessor, p.Name, procDef.ID, err)
}
}

value := inp[p.Name]
toApply := p.Process

value, err := a.processValue(value, p.Type, toApply)
if err != nil {
return err
}
// Replace the value.
// Check for nil not to override the default value.
if value != nil {
inp[p.Name] = value
// Cast to []any slice because jsonschema validator supports only this type.
if p.Type == jsonschema.Array {
res = CastSliceTypedToAny(res)
}
inp[p.Name] = res
}

return nil
}

func (a *Action) processValue(v any, vtype jsonschema.Type, applyProc []DefValueProcessor) (any, error) {
res := v
processors := a.GetProcessors()

for _, procDef := range applyProc {
proc, ok := processors[procDef.ID]
if !ok {
return v, fmt.Errorf(errTplNonExistProcessor, procDef.ID)
}

if !proc.IsApplicable(vtype) {
return v, fmt.Errorf(errTplNotApplicableProcessor, vtype)
}

processedValue, err := proc.Execute(res, procDef.Options)
if err != nil {
return v, err
}

res = processedValue
}
// Cast to []any slice because jsonschema validator supports only this type.
if vtype == jsonschema.Array {
res = CastSliceTypedToAny(res)
}

return res, nil
}

// ValidateInput validates action input.
func (a *Action) ValidateInput(input *Input) error {
if input.IsValidated() {
Expand Down
37 changes: 25 additions & 12 deletions pkg/action/action.input.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package action

import (
"maps"
"reflect"
"strings"

Expand Down Expand Up @@ -30,6 +31,8 @@ type Input struct {

// argsPos contains raw positional arguments.
argsPos []string
// argsRaw contains arguments that were input by a user and without default values.
argsRaw InputParams
// optsRaw contains options that were input by a user and without default values.
optsRaw InputParams
}
Expand All @@ -45,6 +48,7 @@ func NewInput(a *Action, args InputParams, opts InputParams, io launchr.Streams)
return &Input{
action: a,
args: setParamDefaults(args, def.Arguments),
argsRaw: args,
argsPos: argsPos,
opts: setParamDefaults(opts, def.Options),
optsRaw: opts,
Expand Down Expand Up @@ -98,31 +102,32 @@ func (input *Input) SetValidated(v bool) {

// Arg returns argument by a name.
func (input *Input) Arg(name string) any {
return input.ArgsNamed()[name]
return input.Args()[name]
}

// SetArg sets an argument value.
func (input *Input) SetArg(name string, val any) {
input.optsRaw[name] = val
input.opts[name] = val
input.argsRaw[name] = val
input.args[name] = val
}

// UnsetArg unsets the arguments and recalculates default and positional values.
func (input *Input) UnsetArg(name string) {
delete(input.args, name)
input.args = setParamDefaults(input.args, input.action.ActionDef().Arguments)
input.argsPos = argsNamedToPos(input.args, input.action.ActionDef().Arguments)
delete(input.argsRaw, name)
input.args = setParamDefaults(input.argsRaw, input.action.ActionDef().Arguments)
input.argsPos = argsNamedToPos(input.argsRaw, input.action.ActionDef().Arguments)
}

// IsArgChanged checks if an argument was changed by user.
func (input *Input) IsArgChanged(name string) bool {
_, ok := input.args[name]
_, ok := input.argsRaw[name]
return ok
}

// Opt returns option by a name.
func (input *Input) Opt(name string) any {
return input.OptsAll()[name]
return input.Opts()[name]
}

// SetOpt sets an option value.
Expand All @@ -144,18 +149,23 @@ func (input *Input) IsOptChanged(name string) bool {
return ok
}

// ArgsNamed returns input named and processed arguments.
func (input *Input) ArgsNamed() InputParams {
// Args returns input named and processed arguments.
func (input *Input) Args() InputParams {
return input.args
}

// ArgsChanged returns arguments that were set manually by user (not processed).
func (input *Input) ArgsChanged() InputParams {
return input.argsRaw
}

// ArgsPositional returns positional arguments set by user (not processed).
func (input *Input) ArgsPositional() []string {
return input.argsPos
}

// OptsAll returns options with default values and processed.
func (input *Input) OptsAll() InputParams {
// Opts returns options with default values and processed.
func (input *Input) Opts() InputParams {
return input.opts
}

Expand Down Expand Up @@ -184,7 +194,10 @@ func argsNamedToPos(args InputParams, argsDef ParametersList) []string {
}

func setParamDefaults(params InputParams, paramDef ParametersList) InputParams {
res := copyMap(params)
res := maps.Clone(params)
if res == nil {
res = make(InputParams)
}
for _, d := range paramDef {
k := d.Name
v, ok := params[k]
Expand Down
27 changes: 18 additions & 9 deletions pkg/action/action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@ func Test_Action(t *testing.T) {
require.NotNil(act.input)
// Option is not defined, but should be there
// because [Action.ValidateInput] decides if the input correct or not.
_, okOpt := act.input.OptsAll()["opt6"]
_, okOpt := act.input.Opts()["opt6"]
assert.True(okOpt)
assert.Equal(inputArgs, act.input.ArgsNamed())
assert.Equal(inputOpts, act.input.OptsAll())
assert.Equal(inputArgs, act.input.Args())
assert.Equal(inputOpts, act.input.Opts())

// Test templating in executable.
envVar1 := "envval1"
Expand Down Expand Up @@ -129,22 +129,30 @@ func Test_ActionInput(t *testing.T) {
// Check argument is changed.
input = NewInput(a, InputParams{"arg_str": "my_string"}, nil, nil)
require.NotNil(input)
changed := input.ArgsNamed()
assert.Equal(InputParams{"arg_str": "my_string", "arg_default": "my_default_string"}, changed)
assert.Equal(InputParams{"arg_str": "my_string", "arg_default": "my_default_string"}, input.Args())
assert.Equal(InputParams{"arg_str": "my_string"}, input.ArgsChanged())
assert.True(input.IsArgChanged("arg_str"))
assert.False(input.IsArgChanged("arg_int"))
assert.False(input.IsArgChanged("arg_str2"))
input.SetArg("arg_str2", "my_str2")
assert.True(input.IsArgChanged("arg_str2"))
assert.Equal(InputParams{"arg_str": "my_string", "arg_str2": "my_str2"}, input.ArgsChanged())
input.UnsetArg("arg_str")
assert.Equal(InputParams{"arg_str2": "my_str2"}, input.ArgsChanged())
assert.False(input.IsArgChanged("arg_str"))
// Check option is changed.
input = NewInput(a, nil, InputParams{"opt_str": "my_string"}, nil)
require.NotNil(input)
changed = input.OptsChanged()
assert.Equal(InputParams{"opt_str": "my_string"}, changed)
assert.Equal(InputParams{"opt_str": "my_string"}, input.OptsChanged())
assert.True(input.IsOptChanged("opt_str"))
assert.False(input.IsOptChanged("opt_int"))
// Set option and check it's changed.
input.SetOpt("opt_int", 24)
assert.True(input.IsOptChanged("opt_int"))
assert.Equal(InputParams{"opt_str": "my_string", "opt_int": 24, "opt_str_default": "optdefault"}, input.OptsAll())
assert.Equal(InputParams{"opt_str": "my_string", "opt_int": 24, "opt_str_default": "optdefault"}, input.Opts())
input.UnsetOpt("opt_str")
assert.Equal(InputParams{"opt_int": 24}, input.OptsChanged())
assert.False(input.IsOptChanged("opt_str"))

// Test create with positional arguments of different types.
argsPos := []string{"42", "str", "str2", "true", "str3", "undstr", "24"}
Expand All @@ -163,7 +171,7 @@ func Test_ActionInput(t *testing.T) {
}
_, posKeyOk = input.args[inputMapKeyArgsPos]
assert.False(posKeyOk)
assert.Equal(expArgs, input.ArgsNamed())
assert.Equal(expArgs, input.Args())
assert.Equal(argsPos, input.ArgsPositional())
}

Expand Down Expand Up @@ -320,6 +328,7 @@ func Test_ActionInputValidate(t *testing.T) {
tt.fnInit(t, a, input)
}
err := a.ValidateInput(input)
assert.Equal(t, err == nil, input.IsValidated())
if tt.expErr == errAny {
assert.True(t, assert.Error(t, err))
} else if assert.IsType(t, tt.expErr, err) {
Expand Down
7 changes: 4 additions & 3 deletions pkg/action/jsonschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package action

import (
"fmt"
"maps"

"github.com/launchrctl/launchr/pkg/jsonschema"
)
Expand All @@ -17,8 +18,8 @@ func validateJSONSchema(a *Action, input *Input) error {
return jsonschema.Validate(
a.JSONSchema(),
map[string]any{
jsonschemaPropArgs: input.ArgsNamed(),
jsonschemaPropOpts: input.OptsAll(),
jsonschemaPropArgs: input.Args(),
jsonschemaPropOpts: input.Opts(),
},
)
}
Expand Down Expand Up @@ -84,5 +85,5 @@ func (l *ParametersList) JSONSchema() (map[string]any, []string) {

// JSONSchema returns json schema definition of an option.
func (p *DefParameter) JSONSchema() map[string]any {
return copyMap(p.raw)
return maps.Clone(p.raw)
}
4 changes: 2 additions & 2 deletions pkg/action/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ Action definition is correct, but dashes are not allowed in templates, replace "

// ConvertInputToTplVars creates a map with input variables suitable for template engine.
func ConvertInputToTplVars(input *Input, ac *DefAction) map[string]any {
args := input.ArgsNamed()
opts := input.OptsAll()
args := input.Args()
opts := input.Opts()
values := make(map[string]any, len(args)+len(opts))

// Collect arguments and options values.
Expand Down
Loading