diff --git a/executor.go b/executor.go index 48e81d9..2591b57 100644 --- a/executor.go +++ b/executor.go @@ -481,7 +481,9 @@ func (e *Executor) runRole(ctx context.Context, hosts []string, roleRef *RoleRef } // Restore vars - e.vars = oldVars + if roleRef == nil || !roleRef.Public { + e.vars = oldVars + } return nil } @@ -1703,6 +1705,7 @@ func (e *Executor) resolveIncludeRoleRef(host string, task *Task) *RoleRef { VarsFrom: e.templateString(varsFrom, host, task), Vars: renderedVars, Apply: apply, + Public: task.IncludeRole != nil && task.IncludeRole.Public || task.ImportRole != nil && task.ImportRole.Public, When: task.When, Tags: task.Tags, } diff --git a/executor_extra_test.go b/executor_extra_test.go index b0dd790..3b2e5b9 100644 --- a/executor_extra_test.go +++ b/executor_extra_test.go @@ -786,6 +786,7 @@ func TestExecutorExtra_RunIncludeRole_Good_InheritsTaskVars(t *testing.T) { VarsFrom string `yaml:"vars_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` + Public bool `yaml:"public,omitempty"` }{ Name: "demo", }, @@ -839,6 +840,7 @@ func TestExecutorExtra_RunIncludeRole_Good_AppliesRoleDefaults(t *testing.T) { VarsFrom string `yaml:"vars_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` + Public bool `yaml:"public,omitempty"` }{ Name: "app", Apply: &TaskApply{ @@ -863,6 +865,65 @@ func TestExecutorExtra_RunIncludeRole_Good_AppliesRoleDefaults(t *testing.T) { assert.Equal(t, "production|from-apply|from-task", e.results["localhost"]["role_result"].Stdout) } +func TestExecutorExtra_RunIncludeRole_Good_PublicVarsPersist(t *testing.T) { + dir := t.TempDir() + require.NoError(t, writeTestFile(joinPath(dir, "roles", "shared", "tasks", "main.yml"), []byte(`--- +- name: Shared role task + debug: + msg: "{{ shared_message }}" + register: shared_role_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, + Tasks: []Task{ + { + Name: "Load public role", + IncludeRole: &struct { + Name string `yaml:"name"` + TasksFrom string `yaml:"tasks_from,omitempty"` + DefaultsFrom string `yaml:"defaults_from,omitempty"` + VarsFrom string `yaml:"vars_from,omitempty"` + Vars map[string]any `yaml:"vars,omitempty"` + Apply *TaskApply `yaml:"apply,omitempty"` + Public bool `yaml:"public,omitempty"` + }{ + Name: "shared", + Public: true, + }, + Vars: map[string]any{"shared_message": "hello from public role"}, + }, + { + Name: "Use public role vars", + Module: "debug", + Args: map[string]any{ + "msg": "{{ shared_message }}", + }, + Register: "after_public_role", + }, + }, + } + + require.NoError(t, e.runPlay(context.Background(), play)) + + require.NotNil(t, e.results["localhost"]["shared_role_result"]) + assert.Equal(t, "hello from public role", e.results["localhost"]["shared_role_result"].Msg) + require.NotNil(t, e.results["localhost"]["after_public_role"]) + assert.Equal(t, "hello from public role", e.results["localhost"]["after_public_role"].Msg) +} + func TestExecutorExtra_GetHostsIter_Good(t *testing.T) { inv := &Inventory{ All: &InventoryGroup{ diff --git a/executor_test.go b/executor_test.go index 8dfe409..124fe03 100644 --- a/executor_test.go +++ b/executor_test.go @@ -663,6 +663,7 @@ func TestExecutor_RunIncludeRole_Good_TemplatesRoleName(t *testing.T) { VarsFrom string `yaml:"vars_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` + Public bool `yaml:"public,omitempty"` }{ Name: "{{ role_name }}", }, diff --git a/parser.go b/parser.go index 735ed39..c90ce8d 100644 --- a/parser.go +++ b/parser.go @@ -356,7 +356,7 @@ func (t *Task) UnmarshalYAML(node *yaml.Node) error { "retries": true, "delay": true, "until": true, "action": true, "local_action": true, "include_tasks": true, "import_tasks": true, - "include_role": true, "import_role": true, + "include_role": true, "import_role": true, "public": true, "with_items": true, "with_dict": true, "with_indexed_items": true, "with_nested": true, "with_together": true, "with_subelements": true, "with_file": true, "with_fileglob": true, "with_sequence": true, } diff --git a/types.go b/types.go index 6940f69..de9c053 100644 --- a/types.go +++ b/types.go @@ -52,6 +52,7 @@ type RoleRef struct { VarsFrom string `yaml:"vars_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` + Public bool `yaml:"public,omitempty"` When any `yaml:"when,omitempty"` Tags []string `yaml:"tags,omitempty"` } @@ -131,6 +132,7 @@ type Task struct { VarsFrom string `yaml:"vars_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` + Public bool `yaml:"public,omitempty"` } `yaml:"include_role,omitempty"` ImportRole *struct { Name string `yaml:"name"` @@ -139,6 +141,7 @@ type Task struct { VarsFrom string `yaml:"vars_from,omitempty"` Vars map[string]any `yaml:"vars,omitempty"` Apply *TaskApply `yaml:"apply,omitempty"` + Public bool `yaml:"public,omitempty"` } `yaml:"import_role,omitempty"` // Raw YAML for module extraction diff --git a/types_test.go b/types_test.go index 1776e11..abb7c88 100644 --- a/types_test.go +++ b/types_test.go @@ -69,6 +69,7 @@ name: web tasks_from: setup.yml defaults_from: custom-defaults.yml vars_from: custom-vars.yml +public: true ` var ref RoleRef err := yaml.Unmarshal([]byte(input), &ref) @@ -78,6 +79,7 @@ vars_from: custom-vars.yml assert.Equal(t, "setup.yml", ref.TasksFrom) assert.Equal(t, "custom-defaults.yml", ref.DefaultsFrom) assert.Equal(t, "custom-vars.yml", ref.VarsFrom) + assert.True(t, ref.Public) } // --- Task UnmarshalYAML --- @@ -580,6 +582,7 @@ include_role: tasks_from: setup.yml defaults_from: defaults.yml vars_from: vars.yml + public: true apply: tags: - deploy @@ -597,6 +600,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.True(t, task.IncludeRole.Public) require.NotNil(t, task.IncludeRole.Apply) assert.Equal(t, []string{"deploy"}, task.IncludeRole.Apply.Tags) require.NotNil(t, task.IncludeRole.Apply.Become)