From 80fb75baab0b708c5ee77ce34bf8840614ea6d43 Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 3 Apr 2026 13:38:19 +0000 Subject: [PATCH] feat(ansible): honour task check mode overrides --- executor.go | 15 ++++++++++++ executor_test.go | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ parser.go | 1 + types.go | 2 ++ types_test.go | 17 ++++++++++++++ 5 files changed, 94 insertions(+) diff --git a/executor.go b/executor.go index e8a5481..5c041de 100644 --- a/executor.go +++ b/executor.go @@ -1032,6 +1032,21 @@ func (e *Executor) runTaskOnHost(ctx context.Context, host string, hosts []strin e.mu.Unlock() }() + savedCheckMode := e.CheckMode + savedDiff := e.Diff + if task != nil { + if task.CheckMode != nil { + e.CheckMode = *task.CheckMode + } + if task.Diff != nil { + e.Diff = *task.Diff + } + } + defer func() { + e.CheckMode = savedCheckMode + e.Diff = savedDiff + }() + if e.OnTaskStart != nil { e.OnTaskStart(host, task) } diff --git a/executor_test.go b/executor_test.go index ada2b60..0ac2417 100644 --- a/executor_test.go +++ b/executor_test.go @@ -32,6 +32,10 @@ func (c *trackingMockClient) SetBecome(become bool, user, password string) { c.MockSSHClient.SetBecome(become, user, password) } +func boolPtr(v bool) *bool { + return &v +} + // --- NewExecutor --- func TestExecutor_NewExecutor_Good(t *testing.T) { @@ -1845,6 +1849,61 @@ func TestExecutor_RunTaskOnHost_Good_CheckModeSkipsMutatingTask(t *testing.T) { assert.True(t, e.results["host1"]["shell_result"].Skipped) } +func TestExecutor_RunTaskOnHost_Good_TaskCheckModeOverridesExecutorCheckMode(t *testing.T) { + e := NewExecutor("/tmp") + e.CheckMode = true + e.SetInventoryDirect(&Inventory{ + All: &InventoryGroup{ + Hosts: map[string]*Host{ + "host1": {}, + }, + }, + }) + e.clients["host1"] = NewMockSSHClient() + + task := &Task{ + Name: "Run despite global check mode", + Module: "shell", + Args: map[string]any{"_raw_params": "echo hello"}, + CheckMode: boolPtr(false), + Register: "shell_result", + } + + err := e.runTaskOnHost(context.Background(), "host1", []string{"host1"}, task, &Play{}) + require.NoError(t, err) + + require.NotNil(t, e.results["host1"]["shell_result"]) + assert.False(t, e.results["host1"]["shell_result"].Skipped) + assert.True(t, e.results["host1"]["shell_result"].Changed) +} + +func TestExecutor_RunTaskOnHost_Good_TaskDiffOverridesExecutorDiff(t *testing.T) { + e := NewExecutor("/tmp") + e.SetInventoryDirect(&Inventory{ + All: &InventoryGroup{ + Hosts: map[string]*Host{ + "host1": {}, + }, + }, + }) + e.clients["host1"] = NewMockSSHClient() + + task := &Task{ + Name: "Inspect task diff mode", + Module: "debug", + Args: map[string]any{"msg": "{{ ansible_diff_mode }}"}, + Diff: boolPtr(true), + Register: "diff_result", + } + + err := e.runTaskOnHost(context.Background(), "host1", []string{"host1"}, task, &Play{}) + require.NoError(t, err) + + require.NotNil(t, e.results["host1"]) + require.NotNil(t, e.results["host1"]["diff_result"]) + assert.Equal(t, "true", e.results["host1"]["diff_result"].Msg) +} + // --- no_log --- func TestExecutor_RunTaskOnHost_Good_NoLogRedactsCallbackResult(t *testing.T) { diff --git a/parser.go b/parser.go index 3b553f8..fa97b09 100644 --- a/parser.go +++ b/parser.go @@ -448,6 +448,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { "loop_control": true, "vars": true, "environment": true, "changed_when": true, "failed_when": true, "ignore_errors": true, "no_log": true, "become": true, "become_user": true, + "check_mode": true, "diff": true, "delegate_to": true, "delegate_facts": true, "run_once": true, "tags": true, "block": true, "rescue": true, "always": true, "notify": true, "listen": true, "module_defaults": true, diff --git a/types.go b/types.go index 2762c4b..12d0b4f 100644 --- a/types.go +++ b/types.go @@ -137,6 +137,8 @@ type Task struct { Args map[string]any `yaml:"-"` // Module arguments Register string `yaml:"register,omitempty"` When any `yaml:"when,omitempty"` // string or []string + CheckMode *bool `yaml:"check_mode,omitempty"` + Diff *bool `yaml:"diff,omitempty"` Loop any `yaml:"loop,omitempty"` // string or []any LoopControl *LoopControl `yaml:"loop_control,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` diff --git a/types_test.go b/types_test.go index 43ff2af..9dbe39c 100644 --- a/types_test.go +++ b/types_test.go @@ -156,6 +156,23 @@ when: some_var is defined assert.NotNil(t, task.When) } +func TestTypes_Task_UnmarshalYAML_Good_WithCheckModeAndDiff(t *testing.T) { + input := ` +name: Force a dry run +shell: echo hello +check_mode: false +diff: true +` + var task Task + err := yaml.Unmarshal([]byte(input), &task) + + require.NoError(t, err) + require.NotNil(t, task.CheckMode) + require.NotNil(t, task.Diff) + assert.False(t, *task.CheckMode) + assert.True(t, *task.Diff) +} + func TestTypes_Task_UnmarshalYAML_Good_WithLoop(t *testing.T) { input := ` name: Install packages