diff --git a/executor.go b/executor.go index 259e550..42042e9 100644 --- a/executor.go +++ b/executor.go @@ -420,10 +420,20 @@ func (e *Executor) runTaskOnHost(ctx context.Context, host string, task *Task, p } } + // delegate_to changes the execution target but keeps the task context + // anchored to the original host for templating and registration. + executionHost := host + if task.Delegate != "" { + executionHost = e.templateString(task.Delegate, host, task) + if executionHost == "" { + executionHost = host + } + } + // Get SSH client - client, err := e.getClient(host, play) + client, err := e.getClient(executionHost, play) if err != nil { - return coreerr.E("Executor.runTaskOnHost", sprintf("get client for %s", host), err) + return coreerr.E("Executor.runTaskOnHost", sprintf("get client for %s", executionHost), err) } // Handle loops diff --git a/executor_test.go b/executor_test.go index d65812f..27c2722 100644 --- a/executor_test.go +++ b/executor_test.go @@ -283,6 +283,32 @@ func TestExecutor_RunPlay_Good_RunOnceTaskOnlyRunsOnFirstHost(t *testing.T) { assert.False(t, ok) } +func TestExecutor_RunTaskOnHost_Good_DelegateToUsesDelegateHostClient(t *testing.T) { + e := NewExecutor("/tmp") + e.SetInventoryDirect(&Inventory{ + All: &InventoryGroup{ + Hosts: map[string]*Host{ + "source": {}, + }, + }, + }) + + task := &Task{ + Name: "delegated debug", + Module: "debug", + Args: map[string]any{"msg": "ok"}, + Delegate: "delegate-host", + } + play := &Play{} + + require.NoError(t, e.runTaskOnHost(context.Background(), "source", task, play)) + + _, ok := e.clients["delegate-host"] + assert.True(t, ok) + _, ok = e.clients["source"] + assert.False(t, ok) +} + func TestExecutor_RunPlay_Good_PlayTagsApplyToUntaggedTasks(t *testing.T) { e := NewExecutor("/tmp") e.SetInventoryDirect(&Inventory{