From fbfc2a6c7e9a3c0454e795204eac4825a04be74c Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 02:29:36 +0000 Subject: [PATCH] feat(ansible): add builtin ping module Co-Authored-By: Virgil --- executor.go | 1 + executor_extra_test.go | 31 +++++++++++++++++++++++++++++++ mock_ssh_test.go | 3 +++ modules.go | 15 +++++++++++++++ parser_test.go | 2 ++ types.go | 2 ++ types_test.go | 3 ++- 7 files changed, 56 insertions(+), 1 deletion(-) diff --git a/executor.go b/executor.go index 9a3e00b..1d8f252 100644 --- a/executor.go +++ b/executor.go @@ -1581,6 +1581,7 @@ func isCheckModeSafeTask(task *Task) bool { case "ansible.builtin.debug", "ansible.builtin.fail", "ansible.builtin.assert", + "ansible.builtin.ping", "ansible.builtin.pause", "ansible.builtin.wait_for", "ansible.builtin.stat", diff --git a/executor_extra_test.go b/executor_extra_test.go index 4529a8f..f7544c8 100644 --- a/executor_extra_test.go +++ b/executor_extra_test.go @@ -61,6 +61,37 @@ func TestExecutorExtra_ModuleFail_Good_CustomMessage(t *testing.T) { assert.Equal(t, "deployment blocked", result.Msg) } +// --- modulePing --- + +func TestExecutorExtra_ModulePing_Good_DefaultPong(t *testing.T) { + e, mock := newTestExecutorWithMock("host1") + + result, err := executeModuleWithMock(e, mock, "host1", &Task{ + Module: "ping", + }) + + require.NoError(t, err) + assert.False(t, result.Failed) + assert.False(t, result.Changed) + assert.Equal(t, "pong", result.Msg) + assert.True(t, mock.hasExecuted(`^true$`)) +} + +func TestExecutorExtra_ModulePing_Good_CustomData(t *testing.T) { + e, mock := newTestExecutorWithMock("host1") + + result, err := executeModuleWithMock(e, mock, "host1", &Task{ + Module: "ansible.builtin.ping", + Args: map[string]any{ + "data": "hello", + }, + }) + + require.NoError(t, err) + assert.False(t, result.Failed) + assert.Equal(t, "hello", result.Msg) +} + // --- moduleAssert --- func TestExecutorExtra_ModuleAssert_Good_PassingAssertion(t *testing.T) { diff --git a/mock_ssh_test.go b/mock_ssh_test.go index 63a0624..4412348 100644 --- a/mock_ssh_test.go +++ b/mock_ssh_test.go @@ -480,6 +480,9 @@ func executeModuleWithMock(e *Executor, mock *MockSSHClient, host string, task * case "ansible.builtin.archive": return moduleArchiveWithClient(e, mock, args) + case "ansible.builtin.ping", "ping": + return e.modulePing(context.Background(), mock, args) + case "ansible.builtin.setup": return e.moduleSetup(context.Background(), host, mock, args) diff --git a/modules.go b/modules.go index 9c263b7..41ffec5 100644 --- a/modules.go +++ b/modules.go @@ -129,6 +129,8 @@ func (e *Executor) executeModule(ctx context.Context, host string, client sshExe return e.moduleFail(args) case "ansible.builtin.assert": return e.moduleAssert(args, host) + case "ansible.builtin.ping": + return e.modulePing(ctx, client, args) case "ansible.builtin.set_fact": return e.moduleSetFact(args) case "ansible.builtin.add_host": @@ -1483,6 +1485,19 @@ func (e *Executor) moduleFail(args map[string]any) (*TaskResult, error) { }, nil } +func (e *Executor) modulePing(ctx context.Context, client sshExecutorClient, args map[string]any) (*TaskResult, error) { + data := getStringArg(args, "data", "pong") + stdout, stderr, rc, err := client.Run(ctx, "true") + if err != nil { + return &TaskResult{Failed: true, Msg: err.Error(), Stdout: stdout, Stderr: stderr, RC: rc}, nil + } + if rc != 0 { + return &TaskResult{Failed: true, Msg: stderr, Stdout: stdout, Stderr: stderr, RC: rc}, nil + } + + return &TaskResult{Msg: data}, nil +} + func (e *Executor) moduleAssert(args map[string]any, host string) (*TaskResult, error) { that, ok := args["that"] if !ok { diff --git a/parser_test.go b/parser_test.go index e7432fc..990152c 100644 --- a/parser_test.go +++ b/parser_test.go @@ -850,6 +850,7 @@ func TestParser_IsModule_Good_KnownModules(t *testing.T) { assert.True(t, isModule("rpm")) assert.True(t, isModule("debug")) assert.True(t, isModule("set_fact")) + assert.True(t, isModule("ping")) } func TestParser_IsModule_Good_FQCN(t *testing.T) { @@ -877,6 +878,7 @@ func TestParser_NormalizeModule_Good(t *testing.T) { assert.Equal(t, "ansible.builtin.copy", NormalizeModule("copy")) assert.Equal(t, "ansible.builtin.apt", NormalizeModule("apt")) assert.Equal(t, "ansible.builtin.rpm", NormalizeModule("rpm")) + assert.Equal(t, "ansible.builtin.ping", NormalizeModule("ping")) } func TestParser_NormalizeModule_Good_CommunityAliases(t *testing.T) { diff --git a/types.go b/types.go index 1ca06ab..12e5bba 100644 --- a/types.go +++ b/types.go @@ -394,6 +394,7 @@ var KnownModules = []string{ "ansible.builtin.debug", "ansible.builtin.fail", "ansible.builtin.assert", + "ansible.builtin.ping", "ansible.builtin.pause", "ansible.builtin.wait_for", "ansible.builtin.set_fact", @@ -442,6 +443,7 @@ var KnownModules = []string{ "debug", "fail", "assert", + "ping", "pause", "wait_for", "set_fact", diff --git a/types_test.go b/types_test.go index abb7c88..375b55b 100644 --- a/types_test.go +++ b/types_test.go @@ -761,6 +761,7 @@ func TestTypes_KnownModules_Good_ContainsExpected(t *testing.T) { "ansible.builtin.rpm", "ansible.builtin.debug", "ansible.builtin.set_fact", + "ansible.builtin.ping", "community.general.ufw", "ansible.posix.authorized_key", "ansible.builtin.docker_compose", @@ -773,7 +774,7 @@ func TestTypes_KnownModules_Good_ContainsExpected(t *testing.T) { shortModules := []string{ "shell", "command", "copy", "file", "apt", "service", - "systemd", "rpm", "debug", "set_fact", "template", "user", "group", + "systemd", "rpm", "debug", "set_fact", "ping", "template", "user", "group", } for _, mod := range shortModules { assert.Contains(t, KnownModules, mod, "expected short-form module %s", mod)