diff --git a/executor.go b/executor.go index 1d8f252..5d213a9 100644 --- a/executor.go +++ b/executor.go @@ -440,7 +440,7 @@ func (e *Executor) runRole(ctx context.Context, hosts []string, roleRef *RoleRef e.vars = oldVars return coreerr.E("executor.runRole", sprintf("parse role %s", roleRef.Role), err) } - if err := e.attachRoleHandlers(roleRef.Role, play); err != nil { + if err := e.attachRoleHandlers(roleRef.Role, roleRef.HandlersFrom, play); err != nil { e.vars = oldVars return coreerr.E("executor.runRole", sprintf("load handlers for role %s", roleRef.Role), err) } @@ -494,7 +494,7 @@ func (e *Executor) runRole(ctx context.Context, hosts []string, roleRef *RoleRef return nil } -func (e *Executor) attachRoleHandlers(roleName string, play *Play) error { +func (e *Executor) attachRoleHandlers(roleName, handlersFrom string, play *Play) error { if play == nil || roleName == "" { return nil } @@ -502,12 +502,16 @@ func (e *Executor) attachRoleHandlers(roleName string, play *Play) error { e.loadedRoleHandlers = make(map[string]bool) } - key := roleName + "|handlers/main.yml" + if handlersFrom == "" { + handlersFrom = "main.yml" + } + + key := roleName + "|handlers/" + handlersFrom if e.loadedRoleHandlers[key] { return nil } - handlers, err := e.parser.loadRoleHandlers(roleName, "main.yml") + handlers, err := e.parser.loadRoleHandlers(roleName, handlersFrom) if err != nil { return err } @@ -1705,6 +1709,7 @@ func (e *Executor) resolveIncludeRoleRef(host string, task *Task) *RoleRef { } var roleName, tasksFrom, defaultsFrom, varsFrom string + var handlersFrom string var roleVars map[string]any var apply *TaskApply @@ -1713,6 +1718,7 @@ func (e *Executor) resolveIncludeRoleRef(host string, task *Task) *RoleRef { tasksFrom = task.IncludeRole.TasksFrom defaultsFrom = task.IncludeRole.DefaultsFrom varsFrom = task.IncludeRole.VarsFrom + handlersFrom = task.IncludeRole.HandlersFrom roleVars = task.IncludeRole.Vars apply = task.IncludeRole.Apply } else if task.ImportRole != nil { @@ -1720,6 +1726,7 @@ func (e *Executor) resolveIncludeRoleRef(host string, task *Task) *RoleRef { tasksFrom = task.ImportRole.TasksFrom defaultsFrom = task.ImportRole.DefaultsFrom varsFrom = task.ImportRole.VarsFrom + handlersFrom = task.ImportRole.HandlersFrom roleVars = task.ImportRole.Vars apply = task.ImportRole.Apply } else { @@ -1736,6 +1743,7 @@ func (e *Executor) resolveIncludeRoleRef(host string, task *Task) *RoleRef { TasksFrom: e.templateString(tasksFrom, host, task), DefaultsFrom: e.templateString(defaultsFrom, host, task), VarsFrom: e.templateString(varsFrom, host, task), + HandlersFrom: e.templateString(handlersFrom, host, task), Vars: renderedVars, Apply: apply, Public: task.IncludeRole != nil && task.IncludeRole.Public || task.ImportRole != nil && task.ImportRole.Public, diff --git a/executor_extra_test.go b/executor_extra_test.go index f7544c8..6bc8028 100644 --- a/executor_extra_test.go +++ b/executor_extra_test.go @@ -824,6 +824,7 @@ func TestExecutorExtra_RunIncludeRole_Good_InheritsTaskVars(t *testing.T) { TasksFrom string `yaml:"tasks_from,omitempty"` DefaultsFrom string `yaml:"defaults_from,omitempty"` VarsFrom string `yaml:"vars_from,omitempty"` + HandlersFrom string `yaml:"handlers_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` Public bool `yaml:"public,omitempty"` @@ -878,6 +879,7 @@ func TestExecutorExtra_RunIncludeRole_Good_AppliesRoleDefaults(t *testing.T) { TasksFrom string `yaml:"tasks_from,omitempty"` DefaultsFrom string `yaml:"defaults_from,omitempty"` VarsFrom string `yaml:"vars_from,omitempty"` + HandlersFrom string `yaml:"handlers_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` Public bool `yaml:"public,omitempty"` @@ -936,6 +938,7 @@ func TestExecutorExtra_RunIncludeRole_Good_PublicVarsPersist(t *testing.T) { TasksFrom string `yaml:"tasks_from,omitempty"` DefaultsFrom string `yaml:"defaults_from,omitempty"` VarsFrom string `yaml:"vars_from,omitempty"` + HandlersFrom string `yaml:"handlers_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` Public bool `yaml:"public,omitempty"` diff --git a/executor_test.go b/executor_test.go index 0b494ee..80d96af 100644 --- a/executor_test.go +++ b/executor_test.go @@ -661,6 +661,7 @@ func TestExecutor_RunIncludeRole_Good_TemplatesRoleName(t *testing.T) { TasksFrom string `yaml:"tasks_from,omitempty"` DefaultsFrom string `yaml:"defaults_from,omitempty"` VarsFrom string `yaml:"vars_from,omitempty"` + HandlersFrom string `yaml:"handlers_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` Public bool `yaml:"public,omitempty"` @@ -801,6 +802,55 @@ func TestExecutor_RunPlay_Good_LoadsRoleHandlers(t *testing.T) { assert.Equal(t, "handler ran", e.results["localhost"]["role_handler_result"].Msg) } +func TestExecutor_RunRole_Good_UsesCustomRoleHandlersFile(t *testing.T) { + dir := t.TempDir() + + require.NoError(t, writeTestFile(joinPath(dir, "roles", "demo", "tasks", "main.yml"), []byte(`--- +- name: role task + set_fact: + role_triggered: true + notify: custom role handler +`), 0644)) + require.NoError(t, writeTestFile(joinPath(dir, "roles", "demo", "handlers", "custom.yml"), []byte(`--- +- name: custom role handler + debug: + msg: custom handler ran + register: custom_role_handler_result +`), 0644)) + + e := NewExecutor(dir) + e.SetInventoryDirect(&Inventory{ + All: &InventoryGroup{ + Hosts: map[string]*Host{ + "localhost": {}, + }, + }, + }) + + gatherFacts := false + play := &Play{ + Hosts: "localhost", + Connection: "local", + GatherFacts: &gatherFacts, + Roles: []RoleRef{ + {Role: "demo", HandlersFrom: "custom.yml"}, + }, + } + + var started []string + e.OnTaskStart = func(host string, task *Task) { + started = append(started, host+":"+task.Name) + } + + err := e.runPlay(context.Background(), play) + require.NoError(t, err) + + assert.Contains(t, started, "localhost:role task") + assert.Contains(t, started, "localhost:custom role handler") + require.NotNil(t, e.results["localhost"]["custom_role_handler_result"]) + assert.Equal(t, "custom handler ran", e.results["localhost"]["custom_role_handler_result"].Msg) +} + func TestExecutor_RunPlay_Good_SerialBatchesHosts(t *testing.T) { e := NewExecutor("/tmp") e.SetInventoryDirect(&Inventory{ diff --git a/types.go b/types.go index 12e5bba..ed78443 100644 --- a/types.go +++ b/types.go @@ -53,6 +53,7 @@ type RoleRef struct { TasksFrom string `yaml:"tasks_from,omitempty"` DefaultsFrom string `yaml:"defaults_from,omitempty"` VarsFrom string `yaml:"vars_from,omitempty"` + HandlersFrom string `yaml:"handlers_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` Public bool `yaml:"public,omitempty"` @@ -133,6 +134,7 @@ type Task struct { TasksFrom string `yaml:"tasks_from,omitempty"` DefaultsFrom string `yaml:"defaults_from,omitempty"` VarsFrom string `yaml:"vars_from,omitempty"` + HandlersFrom string `yaml:"handlers_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` Public bool `yaml:"public,omitempty"` @@ -142,6 +144,7 @@ type Task struct { TasksFrom string `yaml:"tasks_from,omitempty"` DefaultsFrom string `yaml:"defaults_from,omitempty"` VarsFrom string `yaml:"vars_from,omitempty"` + HandlersFrom string `yaml:"handlers_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` Public bool `yaml:"public,omitempty"` diff --git a/types_test.go b/types_test.go index 375b55b..f2f20a1 100644 --- a/types_test.go +++ b/types_test.go @@ -582,6 +582,7 @@ include_role: tasks_from: setup.yml defaults_from: defaults.yml vars_from: vars.yml + handlers_from: handlers.yml public: true apply: tags: @@ -600,6 +601,7 @@ include_role: assert.Equal(t, "setup.yml", task.IncludeRole.TasksFrom) assert.Equal(t, "defaults.yml", task.IncludeRole.DefaultsFrom) assert.Equal(t, "vars.yml", task.IncludeRole.VarsFrom) + assert.Equal(t, "handlers.yml", task.IncludeRole.HandlersFrom) assert.True(t, task.IncludeRole.Public) require.NotNil(t, task.IncludeRole.Apply) assert.Equal(t, []string{"deploy"}, task.IncludeRole.Apply.Tags)