feat(ansible): support legacy action shorthands
This commit is contained in:
parent
bfa9a8d0ba
commit
0e3a362269
3 changed files with 135 additions and 0 deletions
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// --- NewExecutor ---
|
||||
|
|
@ -273,6 +274,25 @@ func TestExecutor_RunTaskOnHost_Good_DelegateToUsesDelegatedClient(t *testing.T)
|
|||
assert.Equal(t, 1, mock.commandCount())
|
||||
}
|
||||
|
||||
func TestExecutor_RunTaskOnHost_Good_ActionAliasExecutesCommand(t *testing.T) {
|
||||
e, mock := newTestExecutorWithMock("host1")
|
||||
mock.expectCommand(`echo action-alias`, "action-alias", "", 0)
|
||||
|
||||
var task Task
|
||||
require.NoError(t, yaml.Unmarshal([]byte(`
|
||||
name: Legacy action
|
||||
action: command echo action-alias
|
||||
`), &task))
|
||||
task.Register = "action_result"
|
||||
|
||||
err := e.runTaskOnHost(context.Background(), "host1", []string{"host1"}, &task, &Play{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, e.results["host1"]["action_result"])
|
||||
assert.Equal(t, "action-alias", e.results["host1"]["action_result"].Stdout)
|
||||
assert.True(t, mock.hasExecuted(`echo action-alias`))
|
||||
}
|
||||
|
||||
func TestExecutor_Run_Good_VarsFilesMergeIntoPlayVars(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
|
|
|
|||
86
parser.go
86
parser.go
|
|
@ -4,6 +4,8 @@ import (
|
|||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
|
|
@ -352,6 +354,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
|||
"delegate_to": true, "run_once": true, "tags": true,
|
||||
"block": true, "rescue": true, "always": true, "notify": true, "listen": true,
|
||||
"retries": true, "delay": true, "until": true,
|
||||
"action": true, "local_action": true,
|
||||
"include_tasks": true, "import_tasks": true,
|
||||
"include_role": true, "import_role": true,
|
||||
"with_items": true, "with_dict": true, "with_indexed_items": true, "with_nested": true, "with_file": true, "with_fileglob": true, "with_sequence": true,
|
||||
|
|
@ -459,9 +462,92 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
|||
t.WithSequence = sequence
|
||||
}
|
||||
|
||||
// Support legacy action/local_action shorthands.
|
||||
if t.Module == "" {
|
||||
if localAction, ok := m["local_action"]; ok {
|
||||
if module, args := parseActionSpec(localAction); module != "" {
|
||||
t.Module = module
|
||||
t.Args = args
|
||||
t.Delegate = "localhost"
|
||||
}
|
||||
}
|
||||
}
|
||||
if t.Module == "" {
|
||||
if action, ok := m["action"]; ok {
|
||||
if module, args := parseActionSpec(action); module != "" {
|
||||
t.Module = module
|
||||
t.Args = args
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseActionSpec converts action/local_action values into a module name and
|
||||
// argument map.
|
||||
func parseActionSpec(value any) (string, map[string]any) {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return parseActionSpecString(v)
|
||||
case map[string]any:
|
||||
module := getStringArg(v, "module", "")
|
||||
if module == "" {
|
||||
module = getStringArg(v, "_raw_params", "")
|
||||
}
|
||||
|
||||
args := make(map[string]any)
|
||||
for key, val := range v {
|
||||
if key == "module" || key == "_raw_params" {
|
||||
continue
|
||||
}
|
||||
args[key] = val
|
||||
}
|
||||
|
||||
if raw, ok := v["_raw_params"]; ok {
|
||||
args["_raw_params"] = raw
|
||||
}
|
||||
|
||||
if module == "" {
|
||||
return "", nil
|
||||
}
|
||||
if len(args) == 0 {
|
||||
args = nil
|
||||
}
|
||||
return module, args
|
||||
default:
|
||||
return parseActionSpecString(sprintf("%v", value))
|
||||
}
|
||||
}
|
||||
|
||||
func parseActionSpecString(raw string) (string, map[string]any) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
parts := fields(raw)
|
||||
if len(parts) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
module := parts[0]
|
||||
if module == "" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
if len(parts) == 1 {
|
||||
return module, nil
|
||||
}
|
||||
|
||||
sepIndex := strings.IndexFunc(raw, unicode.IsSpace)
|
||||
if sepIndex < 0 || sepIndex >= len(raw)-1 {
|
||||
return module, nil
|
||||
}
|
||||
|
||||
return module, map[string]any{"_raw_params": strings.TrimSpace(raw[sepIndex:])}
|
||||
}
|
||||
|
||||
// expandNestedLoop converts with_nested input into a loop of cartesian
|
||||
// product items. Each output item is a slice containing one value from each
|
||||
// nested list.
|
||||
|
|
|
|||
|
|
@ -304,6 +304,35 @@ with_sequence: "start=1 end=3 format=%02d"
|
|||
assert.Equal(t, "start=1 end=3 format=%02d", sequence)
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_ActionAlias(t *testing.T) {
|
||||
input := `
|
||||
name: Legacy action
|
||||
action: command echo hello world
|
||||
`
|
||||
var task Task
|
||||
err := yaml.Unmarshal([]byte(input), &task)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "command", task.Module)
|
||||
require.NotNil(t, task.Args)
|
||||
assert.Equal(t, "echo hello world", task.Args["_raw_params"])
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_LocalAction(t *testing.T) {
|
||||
input := `
|
||||
name: Legacy local action
|
||||
local_action: shell echo local
|
||||
`
|
||||
var task Task
|
||||
err := yaml.Unmarshal([]byte(input), &task)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "shell", task.Module)
|
||||
assert.Equal(t, "localhost", task.Delegate)
|
||||
require.NotNil(t, task.Args)
|
||||
assert.Equal(t, "echo local", task.Args["_raw_params"])
|
||||
}
|
||||
|
||||
func TestTypes_Task_UnmarshalYAML_Good_WithNested(t *testing.T) {
|
||||
input := `
|
||||
name: Nested loop values
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue