commit 92d1489c5aaa3f58f99a8916b668ccea19e2be74
parent ebbefecb220c420f638e0194e398bf5b56e6c8c3
Author: Asher Morgan <asher@ashermorgan.net>
Date: Fri, 18 Sep 2026 18:13:29 -0700
Replace config playbook option with env option
This option is more general and allows for more flexibility. Setting
env.ANSIBLE_CONFIG is equivalent to setting the old config option.
Diffstat:
10 files changed, 68 insertions(+), 55 deletions(-)
diff --git a/README.md b/README.md
@@ -151,10 +151,6 @@ main.yml: # The playbook name
# $AOH_PLAYBOOKS_FILE. Defaults to the playbook name.
path: my-playbook.yml
- # The path to an associated Ansible config file, if one exists. Relative paths
- # are interpreted as relative to $AOH_PLAYBOOKS_FILE.
- config: ansible.cfg
-
# The hostname to assign to clients. If set to null, clients are assigned the
# hostname that they report for themselves, which could be spoofed. Defaults
# to null.
@@ -166,6 +162,10 @@ main.yml: # The playbook name
- group1
- group2
+ # A dictionary of environment variables to pass to ansible-playbook.
+ env:
+ ANSIBLE_PIPELINING: 'true'
+
# Whether to restrict play execution to the AoH client only using
# ansible-playbook's --limit option. If enabled, the --limit option must not
# be present in allow_opts or extra_args. Defaults to true.
@@ -204,7 +204,9 @@ main.yml: # The playbook name
jinja_args: false
# A list of additional arguments to pass to ansible-playbook. These are not
- # subject to the allow_opts/block_opts/jinja_args restrictions.
+ # subject to the allow_opts/block_opts/jinja_args restrictions. Note that the
+ # --inventory option is not supported and the env.ANSIBLE_INVENTORY playbook
+ # option should be used instead for specifying inventory files.
extra_args:
- '--diff'
diff --git a/aoh/playbook.py b/aoh/playbook.py
@@ -13,14 +13,14 @@ class PlaybookFileError(Exception):
class Playbook:
- def __init__(self, name, dict):
+ def __init__(self, name, raw):
"""Parse a playbook configuration from a dictionary."""
_types = {
'path': str,
- 'config': str,
'host': str,
'groups': list,
+ 'env': dict,
'limit': bool,
'password': bool,
'output': bool,
@@ -32,34 +32,35 @@ class Playbook:
}
for key, _type in _types.items():
- if key in dict:
- if type(dict[key]) != _type:
+ if key in raw:
+ if type(raw[key]) != _type:
raise PlaybookFileError(f'"{key}" field must be of type '
f'{_type}')
- if _type is list and any(type(x) != str for x in dict[key]):
+ if _type is list and any(type(x) != str for x in raw[key]):
raise PlaybookFileError(f'"{key}" elements must be of type '
'str')
+ if _type is dict and any(type(x) != str for x in
+ raw[key].values()):
+ raise PlaybookFileError(f'"{key}" values must be of type '
+ 'str')
+
self.name = name
- self.path = dict.get('path', name)
- self.config = dict.get('config', None)
- self.host = dict.get('host')
- self.groups = dict.get('groups', [])
- self.limit = dict.get('limit', True)
- self.password = dict.get('password', False)
- self.output = dict.get('output', True)
- self.allow_opts = dict.get('allow_opts', [])
- self.block_opts = dict.get('block_opts', [])
- self.jinja_args = dict.get('jinja_args', False)
- self.extra_args = dict.get('extra_args', [])
- self.web_description = dict.get('web_description')
+ self.path = raw.get('path', name)
+ self.host = raw.get('host')
+ self.groups = raw.get('groups', [])
+ self.env = raw.get('env', {})
+ self.limit = raw.get('limit', True)
+ self.password = raw.get('password', False)
+ self.output = raw.get('output', True)
+ self.allow_opts = raw.get('allow_opts', [])
+ self.block_opts = raw.get('block_opts', [])
+ self.jinja_args = raw.get('jinja_args', False)
+ self.extra_args = raw.get('extra_args', [])
+ self.web_description = raw.get('web_description')
self.path = os.path.abspath(os.path.join(_PLAYBOOKS_DIR, self.path))
- if self.config:
- self.config = os.path.abspath(os.path.join(_PLAYBOOKS_DIR,
- self.config))
-
if self.limit:
if '--limit' in self.allow_opts or '-l' in self.allow_opts:
diff --git a/aoh/runner.py b/aoh/runner.py
@@ -25,19 +25,23 @@ class RunnerError(Exception):
"""Raised for AoH runner errors."""
-def get_config_values(config, keys):
+def get_config_values(playbook, keys):
"""Query Ansible config option values."""
with tempfile.TemporaryDirectory(prefix='aoh-') as dir:
+ env = playbook.env.copy()
+ env['ANSIBLE_FORCE_COLOR'] = '0'
+ env['ANSIBLE_NOCOLOR'] = '1'
+
raw_config = ansible_runner.get_ansible_config(
'dump',
- config,
+ envvars=env,
private_data_dir=dir,
quiet=True,
)[0]
if not raw_config:
- raise RunnerError(f'Failed to dump Ansible config from {config}')
+ raise RunnerError('Failed to dump Ansible config')
result = {}
for key in keys:
@@ -107,22 +111,24 @@ class Runner:
f.writelines(f'[{g}]\n{self.host}\n' for g in self.playbook.groups)
config = get_config_values(
- self.playbook.config,
+ self.playbook,
['DEFAULT_CONNECTION_PLUGIN_PATH', 'DEFAULT_HOST_LIST']
)
- env = {
- 'ANSIBLE_AOH_DIR': self._DIR,
- 'ANSIBLE_CONNECTION_PLUGINS': ':'.join(
- [_CONNECTION_PLUGIN_DIR] +
- config['DEFAULT_CONNECTION_PLUGIN_PATH']
- ),
- 'ANSIBLE_CONFIG': self.playbook.config,
- ('ANSIBLE_FORCE_COLOR' if self.color else 'NO_COLOR'): '1',
- 'ANSIBLE_INVENTORY': ','.join(
- [f'{self._DIR}/inventory.ini'] + config['DEFAULT_HOST_LIST']
- ),
- }
+ # Assemble env vars, from low-priority values to high-priority values
+ env = {}
+ env['ANSIBLE_FORCE_COLOR' if self.color else 'NO_COLOR'] = '1'
+ for key, val in self.playbook.env.items():
+ env[key] = val
+ env['ANSIBLE_AOH_DIR'] = self._DIR
+ env['ANSIBLE_CONNECTION_PLUGINS'] = ':'.join(
+ [_CONNECTION_PLUGIN_DIR] +
+ config['DEFAULT_CONNECTION_PLUGIN_PATH']
+ )
+ env['ANSIBLE_INVENTORY'] = ','.join(
+ [f'{self._DIR}/inventory.ini'] + config['DEFAULT_HOST_LIST']
+ )
+
pw_prompt_answers = {}
for pw_type in self.passwords:
pw_prompt_answers[_PASSWORD_PROMPTS[pw_type]] = \
diff --git a/aoh/security.py b/aoh/security.py
@@ -94,7 +94,7 @@ def get_required_passwords(playbook, args):
if opt in opts:
pw_types.add(pw_type)
- opts = get_config_values(playbook.config, _CONFIG_PASSWORD_OPTS.keys())
+ opts = get_config_values(playbook, _CONFIG_PASSWORD_OPTS.keys())
for opt, pw_type in _CONFIG_PASSWORD_OPTS.items():
if opts[opt]:
pw_types.add(pw_type)
diff --git a/demo/ansible.cfg b/demo/ansible.cfg
@@ -1,2 +0,0 @@
-[connection]
-pipelining = true
diff --git a/demo/playbooks.yml b/demo/playbooks.yml
@@ -4,10 +4,6 @@ main.yml: # The playbook name
# $AOH_PLAYBOOKS_FILE. Defaults to the playbook name.
path: my-playbook.yml
- # The path to an associated Ansible config file, if one exists. Relative paths
- # are interpreted as relative to $AOH_PLAYBOOKS_FILE.
- config: ansible.cfg
-
# The hostname to assign to clients. If set to null, clients are assigned the
# hostname that they report for themselves, which could be spoofed. Defaults
# to null.
@@ -19,6 +15,10 @@ main.yml: # The playbook name
- group1
- group2
+ # A dictionary of environment variables to pass to ansible-playbook.
+ env:
+ ANSIBLE_PIPELINING: 'true'
+
# Whether to restrict play execution to the AoH client only using
# ansible-playbook's --limit option. If enabled, the --limit option must not
# be present in allow_opts or extra_args. Defaults to true.
@@ -57,7 +57,9 @@ main.yml: # The playbook name
jinja_args: false
# A list of additional arguments to pass to ansible-playbook. These are not
- # subject to the allow_opts/block_opts/jinja_args restrictions.
+ # subject to the allow_opts/block_opts/jinja_args restrictions. Note that the
+ # --inventory option is not supported and the env.ANSIBLE_INVENTORY playbook
+ # option should be used instead for specifying inventory files.
extra_args:
- '--diff'
diff --git a/tests/e2e/pipelining.cfg b/tests/e2e/pipelining.cfg
@@ -1,2 +0,0 @@
-[connection]
-pipelining = true
diff --git a/tests/e2e/playbooks.yml b/tests/e2e/playbooks.yml
@@ -3,11 +3,13 @@ test1.yml:
test2.yml:
path: test_connection.yml
- config: pipelining.cfg
+ env:
+ ANSIBLE_PIPELINING: 'true'
test3.yml:
path: test_become_password_handling.yml
test4.yml:
path: test_become_password_handling.yml
- config: pipelining.cfg
+ env:
+ ANSIBLE_PIPELINING: 'true'
diff --git a/tests/e2e/run.sh b/tests/e2e/run.sh
@@ -47,3 +47,7 @@ for i in {1..4}; do
exit 1
fi
done
+
+echo ================================
+echo All tests passed.
+echo ================================
diff --git a/tests/unit/test_playbook.py b/tests/unit/test_playbook.py
@@ -13,9 +13,9 @@ class TestPlaybookGetPlaybook(unittest.TestCase):
self.assertEqual(pb.name, 'main.yml')
self.assertEqual(pb.path, os.path.abspath('main.yml'))
- self.assertEqual(pb.config, None)
self.assertEqual(pb.host, None)
self.assertEqual(pb.groups, [])
+ self.assertEqual(pb.env, {})
self.assertEqual(pb.limit, True)
self.assertEqual(pb.password, False)
self.assertEqual(pb.output, True)