Fix block inheritance in executor
Some checks are pending
CI / test (push) Waiting to run
CI / auto-fix (push) Waiting to run
CI / auto-merge (push) Waiting to run

This commit is contained in:
Virgil 2026-04-03 11:14:37 +00:00
parent 8ebfafd6cc
commit 2b32f453db
2 changed files with 95 additions and 6 deletions

View file

@ -970,8 +970,11 @@ func (e *Executor) runTaskOnHost(ctx context.Context, host string, hosts []strin
e.results[host] = make(map[string]*TaskResult)
}
// Check when condition
if task.When != nil {
hasLoop := task.Loop != nil || task.WithFile != nil || task.WithFileGlob != nil || task.WithSequence != nil || task.WithTogether != nil || task.WithSubelements != nil
// Check when condition before allocating a client for simple tasks. Loop
// tasks evaluate when per item.
if task.When != nil && !hasLoop {
if !e.evaluateWhen(task.When, host, task) {
result := &TaskResult{Skipped: true, Msg: "Skipped due to when condition"}
if task.Register != "" {
@ -1012,7 +1015,7 @@ func (e *Executor) runTaskOnHost(ctx context.Context, host string, hosts []strin
// Handle loops, including legacy with_file, with_fileglob, with_sequence,
// with_together, and with_subelements syntax.
if task.Loop != nil || task.WithFile != nil || task.WithFileGlob != nil || task.WithSequence != nil || task.WithTogether != nil || task.WithSubelements != nil {
if hasLoop {
return e.runLoop(ctx, host, client, task, play, start)
}
@ -1949,9 +1952,44 @@ func (e *Executor) runBlock(ctx context.Context, hosts []string, task *Task, pla
var blockErr error
var rescueErr error
inherit := func(child *Task) {
if child == nil || task == nil {
return
}
child.Vars = mergeTaskVars(task.Vars, child.Vars)
child.Environment = mergeStringMap(task.Environment, child.Environment)
if task.When != nil {
child.When = mergeConditions(task.When, child.When)
}
if len(task.Tags) > 0 {
child.Tags = mergeStringSlices(task.Tags, child.Tags)
}
if task.Become != nil && child.Become == nil {
child.Become = task.Become
}
if task.BecomeUser != "" && child.BecomeUser == "" {
child.BecomeUser = task.BecomeUser
}
if task.Delegate != "" && child.Delegate == "" {
child.Delegate = task.Delegate
}
if task.RunOnce {
child.RunOnce = true
}
if task.NoLog {
child.NoLog = true
}
if task.IgnoreErrors {
child.IgnoreErrors = true
}
}
// Try block
for _, t := range task.Block {
if err := e.runTaskOnHosts(ctx, hosts, &t, play); err != nil {
effective := t
inherit(&effective)
if err := e.runTaskOnHosts(ctx, hosts, &effective, play); err != nil {
blockErr = err
break
}
@ -1960,7 +1998,9 @@ func (e *Executor) runBlock(ctx context.Context, hosts []string, task *Task, pla
// Run rescue if block failed
if blockErr != nil && len(task.Rescue) > 0 {
for _, t := range task.Rescue {
if err := e.runTaskOnHosts(ctx, hosts, &t, play); err != nil {
effective := t
inherit(&effective)
if err := e.runTaskOnHosts(ctx, hosts, &effective, play); err != nil {
rescueErr = err
break
}
@ -1969,7 +2009,9 @@ func (e *Executor) runBlock(ctx context.Context, hosts []string, task *Task, pla
// Always run always block
for _, t := range task.Always {
if err := e.runTaskOnHosts(ctx, hosts, &t, play); err != nil {
effective := t
inherit(&effective)
if err := e.runTaskOnHosts(ctx, hosts, &effective, play); err != nil {
if blockErr == nil {
blockErr = err
}

View file

@ -539,6 +539,53 @@ func TestExecutorExtra_RunBlock_Good_RescueSuccessClearsBlockFailure(t *testing.
require.NoError(t, err)
}
func TestExecutorExtra_RunBlock_Good_InheritsBlockVars(t *testing.T) {
e, mock := newTestExecutorWithMock("host1")
mock.expectCommand("echo inherited", "inherited\n", "", 0)
task := &Task{
Vars: map[string]any{
"message": "inherited",
},
Block: []Task{
{
Name: "use inherited vars",
Module: "command",
Args: map[string]any{
"_raw_params": "echo {{ message }}",
},
},
},
}
err := e.runBlock(context.Background(), []string{"host1"}, task, &Play{})
require.NoError(t, err)
assert.True(t, mock.hasExecuted("echo inherited"))
}
func TestExecutorExtra_RunBlock_Good_InheritsBlockWhen(t *testing.T) {
e, mock := newTestExecutorWithMock("host1")
task := &Task{
When: "false",
Block: []Task{
{
Name: "should be skipped",
Module: "command",
Args: map[string]any{
"_raw_params": "echo blocked",
},
},
},
}
err := e.runBlock(context.Background(), []string{"host1"}, task, &Play{})
require.NoError(t, err)
assert.Equal(t, 0, mock.commandCount())
}
func TestExecutorExtra_RunTaskOnHosts_Good_EndHostSkipsFutureTasks(t *testing.T) {
e := NewExecutor("/tmp")
e.SetInventoryDirect(&Inventory{