diff --git a/docs/index.md b/docs/index.md index a448dfd..a215ad0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -126,13 +126,13 @@ go-ansible/ ## Supported Modules -42 module handlers are implemented, covering the most commonly used Ansible modules: +43 module handlers are implemented, covering the most commonly used Ansible modules: | Category | Modules | |----------|---------| | **Command execution** | `shell`, `command`, `raw`, `script` | | **File operations** | `copy`, `template`, `file`, `lineinfile`, `blockinfile`, `stat`, `slurp`, `fetch`, `get_url` | -| **Package management** | `apt`, `apt_key`, `apt_repository`, `package`, `pip` | +| **Package management** | `apt`, `apt_key`, `apt_repository`, `package`, `pip`, `rpm` | | **Service management** | `service`, `systemd` | | **User and group** | `user`, `group` | | **HTTP** | `uri` | diff --git a/mock_ssh_test.go b/mock_ssh_test.go index c88fce5..447b665 100644 --- a/mock_ssh_test.go +++ b/mock_ssh_test.go @@ -447,6 +447,8 @@ func executeModuleWithMock(e *Executor, mock *MockSSHClient, host string, task * return moduleYumWithClient(e, mock, args) case "ansible.builtin.dnf": return moduleDnfWithClient(e, mock, args) + case "ansible.builtin.rpm": + return moduleRPMWithClient(mock, args, "rpm") case "ansible.builtin.package": return modulePackageWithClient(e, mock, args) case "ansible.builtin.pip": @@ -1142,7 +1144,7 @@ func moduleRPMWithClient(client sshRunner, args map[string]any, manager string) state := getStringArg(args, "state", "present") updateCache := getBoolArg(args, "update_cache", false) - if updateCache { + if updateCache && manager != "rpm" { _, _, _, _ = client.Run(context.Background(), sprintf("%s makecache -y", manager)) } @@ -1150,15 +1152,25 @@ func moduleRPMWithClient(client sshRunner, args map[string]any, manager string) switch state { case "present", "installed": if name != "" { - cmd = sprintf("%s install -y -q %s", manager, name) + if manager == "rpm" { + cmd = sprintf("rpm -ivh %s", name) + } else { + cmd = sprintf("%s install -y -q %s", manager, name) + } } case "absent", "removed": if name != "" { - cmd = sprintf("%s remove -y -q %s", manager, name) + if manager == "rpm" { + cmd = sprintf("rpm -e %s", name) + } else { + cmd = sprintf("%s remove -y -q %s", manager, name) + } } case "latest": if name != "" { - if manager == "dnf" { + if manager == "rpm" { + cmd = sprintf("rpm -Uvh %s", name) + } else if manager == "dnf" { cmd = sprintf("%s upgrade -y -q %s", manager, name) } else { cmd = sprintf("%s update -y -q %s", manager, name) diff --git a/modules.go b/modules.go index 6124298..d899bde 100644 --- a/modules.go +++ b/modules.go @@ -93,6 +93,8 @@ func (e *Executor) executeModule(ctx context.Context, host string, client sshExe return e.moduleYum(ctx, client, args) case "ansible.builtin.dnf": return e.moduleDnf(ctx, client, args) + case "ansible.builtin.rpm": + return e.moduleRPM(ctx, client, args, "rpm") case "ansible.builtin.package": return e.modulePackage(ctx, client, args) case "ansible.builtin.pip": @@ -942,7 +944,7 @@ func (e *Executor) moduleRPM(ctx context.Context, client sshExecutorClient, args state := getStringArg(args, "state", "present") updateCache := getBoolArg(args, "update_cache", false) - if updateCache { + if updateCache && manager != "rpm" { _, _, _, _ = client.Run(ctx, sprintf("%s makecache -y", manager)) } @@ -950,15 +952,25 @@ func (e *Executor) moduleRPM(ctx context.Context, client sshExecutorClient, args switch state { case "present", "installed": if name != "" { - cmd = sprintf("%s install -y -q %s", manager, name) + if manager == "rpm" { + cmd = sprintf("rpm -ivh %s", name) + } else { + cmd = sprintf("%s install -y -q %s", manager, name) + } } case "absent", "removed": if name != "" { - cmd = sprintf("%s remove -y -q %s", manager, name) + if manager == "rpm" { + cmd = sprintf("rpm -e %s", name) + } else { + cmd = sprintf("%s remove -y -q %s", manager, name) + } } case "latest": if name != "" { - if manager == "dnf" { + if manager == "rpm" { + cmd = sprintf("rpm -Uvh %s", name) + } else if manager == "dnf" { cmd = sprintf("%s upgrade -y -q %s", manager, name) } else { cmd = sprintf("%s update -y -q %s", manager, name) diff --git a/modules_svc_test.go b/modules_svc_test.go index c48b972..c9ec714 100644 --- a/modules_svc_test.go +++ b/modules_svc_test.go @@ -762,6 +762,40 @@ func TestModulesSvc_ExecuteModuleWithMock_Good_DispatchDnf(t *testing.T) { assert.True(t, mock.hasExecuted(`dnf remove -y -q nano`)) } +func TestModulesSvc_ModuleRpm_Good_InstallPackage(t *testing.T) { + mock := NewMockSSHClient() + mock.expectCommand(`rpm -ivh /tmp/nginx.rpm`, "", "", 0) + + result, err := moduleRPMWithClient(mock, map[string]any{ + "name": "/tmp/nginx.rpm", + "state": "present", + }, "rpm") + + require.NoError(t, err) + assert.True(t, result.Changed) + assert.False(t, result.Failed) + assert.True(t, mock.hasExecuted(`rpm -ivh /tmp/nginx.rpm`)) +} + +func TestModulesSvc_ExecuteModuleWithMock_Good_DispatchRpm(t *testing.T) { + e, mock := newTestExecutorWithMock("host1") + mock.expectCommand(`rpm -e nginx`, "", "", 0) + + task := &Task{ + Module: "rpm", + Args: map[string]any{ + "name": "nginx", + "state": "absent", + }, + } + + result, err := executeModuleWithMock(e, mock, "host1", task) + + require.NoError(t, err) + assert.True(t, result.Changed) + assert.True(t, mock.hasExecuted(`rpm -e nginx`)) +} + // --- pip module --- func TestModulesSvc_ModulePip_Good_InstallPresent(t *testing.T) { diff --git a/parser_test.go b/parser_test.go index b4cfc32..1dd9ce7 100644 --- a/parser_test.go +++ b/parser_test.go @@ -812,6 +812,7 @@ func TestParser_IsModule_Good_KnownModules(t *testing.T) { assert.True(t, isModule("apt")) assert.True(t, isModule("service")) assert.True(t, isModule("systemd")) + assert.True(t, isModule("rpm")) assert.True(t, isModule("debug")) assert.True(t, isModule("set_fact")) } @@ -820,6 +821,7 @@ func TestParser_IsModule_Good_FQCN(t *testing.T) { assert.True(t, isModule("ansible.builtin.shell")) assert.True(t, isModule("ansible.builtin.copy")) assert.True(t, isModule("ansible.builtin.apt")) + assert.True(t, isModule("ansible.builtin.rpm")) } func TestParser_IsModule_Good_DottedUnknown(t *testing.T) { @@ -839,6 +841,7 @@ func TestParser_NormalizeModule_Good(t *testing.T) { assert.Equal(t, "ansible.builtin.shell", NormalizeModule("shell")) assert.Equal(t, "ansible.builtin.copy", NormalizeModule("copy")) assert.Equal(t, "ansible.builtin.apt", NormalizeModule("apt")) + assert.Equal(t, "ansible.builtin.rpm", NormalizeModule("rpm")) } func TestParser_NormalizeModule_Good_CommunityAliases(t *testing.T) { diff --git a/types.go b/types.go index 78a29b1..6940f69 100644 --- a/types.go +++ b/types.go @@ -277,6 +277,7 @@ var KnownModules = []string{ "ansible.builtin.apt_repository", "ansible.builtin.yum", "ansible.builtin.dnf", + "ansible.builtin.rpm", "ansible.builtin.package", "ansible.builtin.pip", "ansible.builtin.service", @@ -324,6 +325,7 @@ var KnownModules = []string{ "apt_repository", "yum", "dnf", + "rpm", "package", "pip", "service", @@ -362,4 +364,5 @@ var ModuleAliases = map[string]string{ "docker_compose": "community.docker.docker_compose", "docker_compose_v2": "community.docker.docker_compose_v2", "ansible.builtin.docker_compose": "community.docker.docker_compose", + "rpm": "ansible.builtin.rpm", } diff --git a/types_test.go b/types_test.go index 48f6d51..1776e11 100644 --- a/types_test.go +++ b/types_test.go @@ -754,6 +754,7 @@ func TestTypes_KnownModules_Good_ContainsExpected(t *testing.T) { "ansible.builtin.apt", "ansible.builtin.service", "ansible.builtin.systemd", + "ansible.builtin.rpm", "ansible.builtin.debug", "ansible.builtin.set_fact", "community.general.ufw", @@ -768,7 +769,7 @@ func TestTypes_KnownModules_Good_ContainsExpected(t *testing.T) { shortModules := []string{ "shell", "command", "copy", "file", "apt", "service", - "systemd", "debug", "set_fact", "template", "user", "group", + "systemd", "rpm", "debug", "set_fact", "template", "user", "group", } for _, mod := range shortModules { assert.Contains(t, KnownModules, mod, "expected short-form module %s", mod)