Skip to content

Commit 1142f40

Browse files
fix block module in ansible_python
1 parent 285a216 commit 1142f40

12 files changed

Lines changed: 935 additions & 80 deletions

pkg/compile.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -612,23 +612,23 @@ func TextToGraphNodes(blocks []map[string]interface{}) ([]GraphNode, error) {
612612
if pythonModule, ok := GetModule("ansible_python"); ok {
613613
module = pythonModule
614614

615-
// Convert rawParams to map[string]interface{}
616-
var paramsMap map[string]interface{}
617-
if moduleParams != nil {
618-
if pm, ok := moduleParams.(map[string]interface{}); ok {
619-
paramsMap = pm
620-
} else {
621-
// Try to convert other types to a simple parameter
622-
paramsMap = map[string]interface{}{"value": moduleParams}
623-
}
624-
} else {
625-
paramsMap = make(map[string]interface{})
615+
// Preserve map or slice params for args; wrap other types
616+
var args interface{}
617+
switch v := moduleParams.(type) {
618+
case map[string]interface{}:
619+
args = v
620+
case []interface{}:
621+
args = v
622+
case nil:
623+
args = map[string]interface{}{}
624+
default:
625+
args = map[string]interface{}{"value": moduleParams}
626626
}
627627

628628
// Create the AnsiblePythonInput structure
629629
moduleParams = map[string]interface{}{
630630
"module_name": moduleName,
631-
"args": paramsMap,
631+
"args": args,
632632
}
633633
} else {
634634
errors = append(errors, fmt.Errorf("ansible_python module not registered for unknown module %s", moduleName))

pkg/modules/ansible_python.go

Lines changed: 83 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,52 +17,89 @@ import (
1717

1818
// AnsiblePythonInput defines parameters for executing Python Ansible modules
1919
type AnsiblePythonInput struct {
20-
ModuleName string `yaml:"module_name" json:"module_name"`
21-
Args map[string]interface{} `yaml:"args" json:"args"`
20+
ModuleName string `yaml:"module_name" json:"module_name"`
21+
Args interface{} `yaml:"args" json:"args"`
2222
}
2323

2424
func (i AnsiblePythonInput) ToCode() string {
25-
// Convert Args map to Go code format
26-
argsCode := "map[string]interface{}{"
27-
for k, v := range i.Args {
28-
switch val := v.(type) {
29-
case string:
30-
argsCode += fmt.Sprintf("%q:%q,", k, val)
31-
case bool:
32-
argsCode += fmt.Sprintf("%q:%t,", k, val)
33-
case int, int32, int64:
34-
argsCode += fmt.Sprintf("%q:%v,", k, val)
35-
case float32, float64:
36-
argsCode += fmt.Sprintf("%q:%v,", k, val)
37-
case []interface{}:
38-
// Handle slice values like ["hostname test-switch","interface Ethernet1"," no shutdown"]
39-
sliceCode := "[]interface{}{"
40-
for _, item := range val {
41-
switch itemVal := item.(type) {
42-
case string:
43-
sliceCode += fmt.Sprintf("%q,", itemVal)
44-
default:
45-
sliceCode += fmt.Sprintf("%v,", itemVal)
46-
}
47-
}
48-
sliceCode += "}"
49-
argsCode += fmt.Sprintf("%q:%s,", k, sliceCode)
50-
default:
51-
argsCode += fmt.Sprintf("%q:interface{}(%v),", k, val)
25+
// Convert Args (map or slice) to Go code format
26+
var argsCode string
27+
switch v := i.Args.(type) {
28+
case map[string]interface{}:
29+
b := strings.Builder{}
30+
b.WriteString("map[string]interface{}{")
31+
for mk, mv := range v {
32+
b.WriteString(fmt.Sprintf("%q:%s,", mk, generateGoLiteral(mv)))
5233
}
34+
b.WriteString("}")
35+
argsCode = b.String()
36+
case []interface{}:
37+
b := strings.Builder{}
38+
b.WriteString("[]interface{}{")
39+
for _, sv := range v {
40+
b.WriteString(generateGoLiteral(sv))
41+
b.WriteString(",")
42+
}
43+
b.WriteString("}")
44+
argsCode = b.String()
45+
case nil:
46+
argsCode = "nil"
47+
default:
48+
argsCode = fmt.Sprintf("interface{}(%v)", v)
5349
}
54-
argsCode += "}"
5550
return fmt.Sprintf("modules.AnsiblePythonInput{ModuleName: %q, Args: %s}", i.ModuleName, argsCode)
5651
}
5752

53+
// generateGoLiteral renders a best-effort Go code literal for common JSON/YAML-like values
54+
func generateGoLiteral(val interface{}) string {
55+
switch v := val.(type) {
56+
case string:
57+
return fmt.Sprintf("%q", v)
58+
case bool:
59+
return fmt.Sprintf("%t", v)
60+
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
61+
return fmt.Sprintf("%v", v)
62+
case map[string]interface{}:
63+
b := strings.Builder{}
64+
b.WriteString("map[string]interface{}{")
65+
for mk, mv := range v {
66+
b.WriteString(fmt.Sprintf("%q:%s,", mk, generateGoLiteral(mv)))
67+
}
68+
b.WriteString("}")
69+
return b.String()
70+
case []interface{}:
71+
b := strings.Builder{}
72+
b.WriteString("[]interface{}{")
73+
for _, sv := range v {
74+
b.WriteString(generateGoLiteral(sv))
75+
b.WriteString(",")
76+
}
77+
b.WriteString("}")
78+
return b.String()
79+
default:
80+
return fmt.Sprintf("interface{}(%v)", v)
81+
}
82+
}
83+
5884
func (i AnsiblePythonInput) GetVariableUsage() []string {
5985
var variables []string
60-
// Extract variables from arguments recursively
61-
for _, v := range i.Args {
62-
if str, ok := v.(string); ok {
63-
variables = append(variables, pkg.GetVariableUsageFromTemplate(str)...)
86+
// Extract variables from arguments recursively, handling map and slice
87+
var walk func(val interface{})
88+
walk = func(val interface{}) {
89+
switch tv := val.(type) {
90+
case string:
91+
variables = append(variables, pkg.GetVariableUsageFromTemplate(tv)...)
92+
case map[string]interface{}:
93+
for _, mv := range tv {
94+
walk(mv)
95+
}
96+
case []interface{}:
97+
for _, sv := range tv {
98+
walk(sv)
99+
}
64100
}
65101
}
102+
walk(i.Args)
66103
return variables
67104
}
68105

@@ -456,23 +493,23 @@ func (m PythonFallbackModule) ParameterAliases() map[string]string {
456493

457494
// GetPythonFallbackForCompilation creates a Python fallback module and params for compilation phase
458495
func GetPythonFallbackForCompilation(moduleName string, rawParams interface{}) (pkg.Module, interface{}) {
459-
// Convert rawParams to map[string]interface{}
460-
var paramsMap map[string]interface{}
461-
if rawParams != nil {
462-
if pm, ok := rawParams.(map[string]interface{}); ok {
463-
paramsMap = pm
464-
} else {
465-
// Try to convert other types to a simple parameter
466-
paramsMap = map[string]interface{}{"value": rawParams}
467-
}
468-
} else {
469-
paramsMap = make(map[string]interface{})
496+
// Preserve map or slice params as-is; fallback to map with single value otherwise
497+
var args interface{}
498+
switch v := rawParams.(type) {
499+
case map[string]interface{}:
500+
args = v
501+
case []interface{}:
502+
args = v
503+
case nil:
504+
args = map[string]interface{}{}
505+
default:
506+
args = map[string]interface{}{"value": rawParams}
470507
}
471508

472509
// Create the AnsiblePythonInput structure directly
473510
pythonInput := AnsiblePythonInput{
474511
ModuleName: moduleName,
475-
Args: paramsMap,
512+
Args: args,
476513
}
477514

478515
return PythonFallbackModule{}, pythonInput

pkg/modules/ansible_python_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,3 +188,46 @@ func TestParseAnsibleOutput_ErrorWithoutJSON(t *testing.T) {
188188
assert.True(t, ok)
189189
assert.Contains(t, ro, "ERROR!")
190190
}
191+
192+
func TestAnsiblePythonInput_SliceArgs_ToCodeAndVars(t *testing.T) {
193+
apt := &AnsiblePythonInput{
194+
ModuleName: "block",
195+
Args: []interface{}{
196+
map[string]interface{}{"name": "task 1", "command": "/bin/true"},
197+
map[string]interface{}{"name": "task {{ myvar }}", "command": "/bin/true"},
198+
},
199+
}
200+
201+
// Ensure it implements ConcreteModuleInputProvider
202+
var _ pkg.ConcreteModuleInputProvider = apt
203+
204+
mi := &pkg.ModuleInput{Actual: apt}
205+
code := mi.ToCode()
206+
assert.Contains(t, code, "AnsiblePythonInput")
207+
assert.Contains(t, code, "[]interface{}", "ToCode should render slice args when provided a slice")
208+
assert.Contains(t, code, "\"block\"")
209+
210+
vars := mi.GetVariableUsage()
211+
assert.Contains(t, vars, "myvar", "Variable extraction should find variables in slice args")
212+
}
213+
214+
func TestGetPythonFallbackForCompilation_SliceArgs(t *testing.T) {
215+
raw := []interface{}{
216+
map[string]interface{}{"name": "task 1", "command": "/bin/true"},
217+
map[string]interface{}{"name": "task 2", "command": "/bin/true"},
218+
}
219+
mod, params := GetPythonFallbackForCompilation("block", raw)
220+
assert.NotNil(t, mod)
221+
222+
inp, ok := params.(AnsiblePythonInput)
223+
if !ok {
224+
t.Fatalf("expected AnsiblePythonInput, got %T", params)
225+
}
226+
assert.Equal(t, "block", inp.ModuleName)
227+
228+
argsSlice, ok := inp.Args.([]interface{})
229+
if !ok {
230+
t.Fatalf("expected slice args, got %T", inp.Args)
231+
}
232+
assert.Equal(t, 2, len(argsSlice))
233+
}

0 commit comments

Comments
 (0)