diff --git a/executor_test.go b/executor_test.go index 3afc373..3f45b8e 100644 --- a/executor_test.go +++ b/executor_test.go @@ -231,6 +231,28 @@ func TestExecutor_RunTaskOnHost_Good_DelegateToUsesDelegatedClient(t *testing.T) assert.Equal(t, 1, mock.commandCount()) } +func TestExecutor_ExecuteModule_Good_ShortFormCommunityAlias(t *testing.T) { + e := NewExecutor("/tmp") + mock := NewMockSSHClient() + + task := &Task{ + Name: "Install SSH key", + Module: "authorized_key", + Args: map[string]any{ + "user": "deploy", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDshortformalias", + }, + } + + result, err := e.executeModule(context.Background(), "host1", mock, task, &Play{}) + require.NoError(t, err) + require.NotNil(t, result) + assert.True(t, result.Changed) + assert.False(t, result.Failed) + assert.True(t, mock.hasExecuted(`getent passwd deploy`)) + assert.True(t, mock.hasExecuted(`authorized_keys`)) +} + func TestExecutor_RunTaskOnHost_Good_EnvironmentMergesForCommand(t *testing.T) { e, mock := newTestExecutorWithMock("host1") diff --git a/parser.go b/parser.go index be5de1e..a2fa112 100644 --- a/parser.go +++ b/parser.go @@ -414,6 +414,9 @@ func isModule(key string) bool { // // module := NormalizeModule("shell") func NormalizeModule(name string) string { + if canonical, ok := ModuleAliases[name]; ok { + return canonical + } // Add ansible.builtin. prefix if missing if !contains(name, ".") { return "ansible.builtin." + name diff --git a/parser_test.go b/parser_test.go index 6181207..16523e9 100644 --- a/parser_test.go +++ b/parser_test.go @@ -809,6 +809,13 @@ func TestParser_NormalizeModule_Good(t *testing.T) { assert.Equal(t, "ansible.builtin.apt", NormalizeModule("apt")) } +func TestParser_NormalizeModule_Good_CommunityAliases(t *testing.T) { + assert.Equal(t, "ansible.posix.authorized_key", NormalizeModule("authorized_key")) + assert.Equal(t, "community.general.ufw", NormalizeModule("ufw")) + assert.Equal(t, "community.docker.docker_compose", NormalizeModule("docker_compose")) + assert.Equal(t, "community.docker.docker_compose_v2", NormalizeModule("docker_compose_v2")) +} + func TestParser_NormalizeModule_Good_AlreadyFQCN(t *testing.T) { assert.Equal(t, "ansible.builtin.shell", NormalizeModule("ansible.builtin.shell")) assert.Equal(t, "community.general.ufw", NormalizeModule("community.general.ufw")) diff --git a/types.go b/types.go index e772895..fc9cba0 100644 --- a/types.go +++ b/types.go @@ -314,4 +314,17 @@ var KnownModules = []string{ "hostname", "sysctl", "reboot", + "authorized_key", + "ufw", + "docker_compose", + "docker_compose_v2", +} + +// ModuleAliases maps accepted short-form module names to their canonical +// fully-qualified collection names. +var ModuleAliases = map[string]string{ + "authorized_key": "ansible.posix.authorized_key", + "ufw": "community.general.ufw", + "docker_compose": "community.docker.docker_compose", + "docker_compose_v2": "community.docker.docker_compose_v2", } diff --git a/types_test.go b/types_test.go index 0e2adb9..dabba99 100644 --- a/types_test.go +++ b/types_test.go @@ -265,6 +265,54 @@ reboot: } } +func TestTypes_Task_UnmarshalYAML_Good_ShortFormCommunityModules(t *testing.T) { + cases := []struct { + name string + input string + wantModule string + }{ + { + name: "authorized_key", + input: ` +name: Install SSH key +authorized_key: + user: deploy + key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD +`, + wantModule: "authorized_key", + }, + { + name: "ufw", + input: ` +name: Allow SSH +ufw: + rule: allow + port: "22" +`, + wantModule: "ufw", + }, + { + name: "docker_compose", + input: ` +name: Start stack +docker_compose: + project_src: /opt/app +`, + wantModule: "docker_compose", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var task Task + err := yaml.Unmarshal([]byte(tc.input), &task) + + require.NoError(t, err) + assert.Equal(t, tc.wantModule, task.Module) + }) + } +} + func TestTypes_Task_UnmarshalYAML_Good_WithNotifyList(t *testing.T) { input := ` name: Install package