fix(ansible): isolate host var resolution
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
6f78c5187c
commit
ffd0b5dbd1
2 changed files with 103 additions and 51 deletions
105
parser.go
105
parser.go
|
|
@ -516,66 +516,69 @@ func hasHost(group *InventoryGroup, name string) bool {
|
|||
func GetHostVars(inv *Inventory, hostname string) map[string]any {
|
||||
vars := make(map[string]any)
|
||||
|
||||
// Collect vars from all levels
|
||||
collectHostVars(inv.All, hostname, vars)
|
||||
if inv == nil || inv.All == nil {
|
||||
return vars
|
||||
}
|
||||
|
||||
path, host, ok := findHostPath(inv.All, hostname)
|
||||
if !ok {
|
||||
return vars
|
||||
}
|
||||
|
||||
// Merge vars from outermost to innermost so nearer scopes win.
|
||||
for _, group := range path {
|
||||
for k, v := range group.Vars {
|
||||
vars[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Host connection settings and inline vars override group vars.
|
||||
if host != nil {
|
||||
if host.AnsibleHost != "" {
|
||||
vars["ansible_host"] = host.AnsibleHost
|
||||
}
|
||||
if host.AnsiblePort != 0 {
|
||||
vars["ansible_port"] = host.AnsiblePort
|
||||
}
|
||||
if host.AnsibleUser != "" {
|
||||
vars["ansible_user"] = host.AnsibleUser
|
||||
}
|
||||
if host.AnsiblePassword != "" {
|
||||
vars["ansible_password"] = host.AnsiblePassword
|
||||
}
|
||||
if host.AnsibleSSHPrivateKeyFile != "" {
|
||||
vars["ansible_ssh_private_key_file"] = host.AnsibleSSHPrivateKeyFile
|
||||
}
|
||||
if host.AnsibleConnection != "" {
|
||||
vars["ansible_connection"] = host.AnsibleConnection
|
||||
}
|
||||
if host.AnsibleBecomePassword != "" {
|
||||
vars["ansible_become_password"] = host.AnsibleBecomePassword
|
||||
}
|
||||
for k, v := range host.Vars {
|
||||
vars[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
func collectHostVars(group *InventoryGroup, hostname string, vars map[string]any) bool {
|
||||
func findHostPath(group *InventoryGroup, hostname string) ([]*InventoryGroup, *Host, bool) {
|
||||
if group == nil {
|
||||
return false
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
// Check if host is in this group
|
||||
found := false
|
||||
if host, ok := group.Hosts[hostname]; ok {
|
||||
found = true
|
||||
// Apply group vars first
|
||||
for k, v := range group.Vars {
|
||||
vars[k] = v
|
||||
}
|
||||
// Then host vars
|
||||
if host != nil {
|
||||
if host.AnsibleHost != "" {
|
||||
vars["ansible_host"] = host.AnsibleHost
|
||||
}
|
||||
if host.AnsiblePort != 0 {
|
||||
vars["ansible_port"] = host.AnsiblePort
|
||||
}
|
||||
if host.AnsibleUser != "" {
|
||||
vars["ansible_user"] = host.AnsibleUser
|
||||
}
|
||||
if host.AnsiblePassword != "" {
|
||||
vars["ansible_password"] = host.AnsiblePassword
|
||||
}
|
||||
if host.AnsibleSSHPrivateKeyFile != "" {
|
||||
vars["ansible_ssh_private_key_file"] = host.AnsibleSSHPrivateKeyFile
|
||||
}
|
||||
if host.AnsibleConnection != "" {
|
||||
vars["ansible_connection"] = host.AnsibleConnection
|
||||
}
|
||||
if host.AnsibleBecomePassword != "" {
|
||||
vars["ansible_become_password"] = host.AnsibleBecomePassword
|
||||
}
|
||||
for k, v := range host.Vars {
|
||||
vars[k] = v
|
||||
}
|
||||
return []*InventoryGroup{group}, host, true
|
||||
}
|
||||
|
||||
for _, name := range slices.Sorted(maps.Keys(group.Children)) {
|
||||
child := group.Children[name]
|
||||
path, host, ok := findHostPath(child, hostname)
|
||||
if ok {
|
||||
return append([]*InventoryGroup{group}, path...), host, true
|
||||
}
|
||||
}
|
||||
|
||||
// Check children
|
||||
for _, child := range group.Children {
|
||||
if collectHostVars(child, hostname, vars) {
|
||||
// Apply this group's vars (parent vars)
|
||||
for k, v := range group.Vars {
|
||||
if _, exists := vars[k]; !exists {
|
||||
vars[k] = v
|
||||
}
|
||||
}
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
return found
|
||||
return nil, nil, false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -716,6 +716,55 @@ func TestParser_GetHostVars_Good_InheritedGroupVars(t *testing.T) {
|
|||
assert.Equal(t, "prod", vars["env"])
|
||||
}
|
||||
|
||||
func TestParser_GetHostVars_Good_SiblingVarsDoNotLeak(t *testing.T) {
|
||||
inv := &Inventory{
|
||||
All: &InventoryGroup{
|
||||
Children: map[string]*InventoryGroup{
|
||||
"a-group": {
|
||||
Vars: map[string]any{"leaked": "from-a"},
|
||||
},
|
||||
"b-group": {
|
||||
Vars: map[string]any{"env": "prod"},
|
||||
Hosts: map[string]*Host{
|
||||
"prod1": {
|
||||
AnsibleHost: "10.0.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
vars := GetHostVars(inv, "prod1")
|
||||
assert.Equal(t, "10.0.0.2", vars["ansible_host"])
|
||||
assert.Equal(t, "prod", vars["env"])
|
||||
assert.NotContains(t, vars, "leaked")
|
||||
}
|
||||
|
||||
func TestParser_GetHostVars_Good_NearestScopeWins(t *testing.T) {
|
||||
inv := &Inventory{
|
||||
All: &InventoryGroup{
|
||||
Vars: map[string]any{"shared": "root"},
|
||||
Children: map[string]*InventoryGroup{
|
||||
"group": {
|
||||
Vars: map[string]any{"shared": "group"},
|
||||
Hosts: map[string]*Host{
|
||||
"app1": {
|
||||
AnsibleHost: "10.0.0.3",
|
||||
AnsibleUser: "deploy",
|
||||
Vars: map[string]any{"shared": "host"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
vars := GetHostVars(inv, "app1")
|
||||
assert.Equal(t, "host", vars["shared"])
|
||||
assert.Equal(t, "deploy", vars["ansible_user"])
|
||||
}
|
||||
|
||||
func TestParser_GetHostVars_Good_HostNotFound(t *testing.T) {
|
||||
inv := &Inventory{
|
||||
All: &InventoryGroup{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue