diff --git a/parser.go b/parser.go index 2de11c3..86cb119 100644 --- a/parser.go +++ b/parser.go @@ -287,7 +287,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { *t = Task(raw) t.raw = m - // Find the module key + // Find the module key in YAML order so the first recognised module wins. knownKeys := map[string]bool{ "name": true, "register": true, "when": true, "loop": true, "loop_control": true, "vars": true, "environment": true, @@ -301,28 +301,43 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { "with_items": true, "with_dict": true, "with_file": true, } - for key, val := range m { - if knownKeys[key] { - continue - } - - // Check if this is a module - if isModule(key) { - t.Module = key - t.Args = make(map[string]any) - - switch v := val.(type) { - case string: - // Free-form args (e.g., shell: echo hello) - t.Args["_raw_params"] = v - case map[string]any: - t.Args = v - case nil: - // Module with no args - default: - t.Args["_raw_params"] = v + if node.Kind == yaml.MappingNode { + for i := 0; i+1 < len(node.Content); i += 2 { + keyNode := node.Content[i] + valueNode := node.Content[i+1] + key := keyNode.Value + if knownKeys[key] { + continue + } + + // Check if this is a module. + if isModule(key) { + t.Module = key + t.Args = make(map[string]any) + + switch valueNode.Kind { + case yaml.MappingNode: + var args map[string]any + if err := valueNode.Decode(&args); err == nil { + t.Args = args + } + case yaml.SequenceNode: + var args any + if err := valueNode.Decode(&args); err == nil { + t.Args["_raw_params"] = args + } + case yaml.ScalarNode: + if valueNode.Tag != "!!null" { + t.Args["_raw_params"] = valueNode.Value + } + default: + var args any + if err := valueNode.Decode(&args); err == nil && args != nil { + t.Args["_raw_params"] = args + } + } + break } - break } } diff --git a/types_test.go b/types_test.go index f074ffe..dceb498 100644 --- a/types_test.go +++ b/types_test.go @@ -196,6 +196,21 @@ with_items: assert.Len(t, items, 2) } +func TestTypes_Task_UnmarshalYAML_Good_FirstRecognisedModuleWins(t *testing.T) { + input := ` +name: Multiple modules +shell: echo first +debug: + msg: "second" +` + var task Task + err := yaml.Unmarshal([]byte(input), &task) + + require.NoError(t, err) + assert.Equal(t, "shell", task.Module) + assert.Equal(t, "echo first", task.Args["_raw_params"]) +} + func TestTypes_Task_UnmarshalYAML_Good_WithNotify(t *testing.T) { input := ` name: Install package