From 8f6bd48cf8edc093f86644bc20790f0ff843defb Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 3 Apr 2026 10:39:45 +0000 Subject: [PATCH] feat(ansible): support meta end_role --- executor.go | 7 +++++++ executor_test.go | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/executor.go b/executor.go index e2d1f36..d88f309 100644 --- a/executor.go +++ b/executor.go @@ -25,6 +25,7 @@ import ( var errEndPlay = errors.New("end play") var errEndHost = errors.New("end host") var errEndBatch = errors.New("end batch") +var errEndRole = errors.New("end role") var errTaskFailed = errors.New("task failed") // sshExecutorClient is the client contract used by the executor. @@ -754,6 +755,10 @@ func (e *Executor) runRole(ctx context.Context, hosts []string, roleRef *RoleRef effectiveTask.When = mergeConditions(inheritedWhen, effectiveTask.When) } if err := e.runTaskOnHosts(ctx, eligibleHosts, &effectiveTask, play); err != nil { + if errors.Is(err, errEndRole) { + e.vars = oldVars + return nil + } // Restore vars e.vars = oldVars return err @@ -3629,6 +3634,8 @@ func (e *Executor) handleMetaAction(ctx context.Context, host string, hosts []st case "reset_connection": e.resetConnection(host) return nil + case "end_role": + return errEndRole default: return nil } diff --git a/executor_test.go b/executor_test.go index ef3a7eb..c073c94 100644 --- a/executor_test.go +++ b/executor_test.go @@ -648,6 +648,49 @@ func TestExecutor_RunRole_Good_AppliesRoleTagsToTasks(t *testing.T) { assert.Equal(t, "role ran", e.results["host1"]["role_result"].Msg) } +func TestExecutor_RunRole_Good_MetaEndRoleStopsRemainingRoleTasks(t *testing.T) { + dir := t.TempDir() + roleTasks := `--- +- name: before end_role + debug: + msg: before + register: before_result +- name: stop role + meta: end_role +- name: after end_role + debug: + msg: after + register: after_result +` + require.NoError(t, writeTestFile(joinPath(dir, "roles", "deploy", "tasks", "main.yml"), []byte(roleTasks), 0644)) + + e := NewExecutor(dir) + e.SetInventoryDirect(&Inventory{ + All: &InventoryGroup{ + Hosts: map[string]*Host{ + "host1": {}, + }, + }, + }) + + play := &Play{Connection: "local"} + + var executed []string + e.OnTaskEnd = func(_ string, task *Task, _ *TaskResult) { + executed = append(executed, task.Name) + } + + err := e.runRole(context.Background(), []string{"host1"}, &RoleRef{ + Role: "deploy", + }, play, nil) + require.NoError(t, err) + + assert.Equal(t, []string{"before end_role", "stop role"}, executed) + require.NotNil(t, e.results["host1"]["before_result"]) + _, hasAfter := e.results["host1"]["after_result"] + assert.False(t, hasAfter) +} + func TestExecutor_RunRole_Good_AppliesRoleDefaultsAndVars(t *testing.T) { dir := t.TempDir()