Only template values in vars_prompt rather than all vars (#39304)
* Only template values in vars_prompt rather than all vars This allows the use of variables in vars_prompt fields but allows variables entered in the prompt to affect play vars rather than throwing an undefined error. Only post validate if there was a vars_prompt * Add tests for vars_prompt
This commit is contained in:
parent
d5662df695
commit
6d38167d49
13 changed files with 254 additions and 14 deletions
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- vars_prompt - properly template play level variables in vars_prompt (https://github.com/ansible/ansible/issues/37984)
|
|
@ -102,16 +102,13 @@ class PlaybookExecutor:
|
|||
# clear any filters which may have been applied to the inventory
|
||||
self._inventory.remove_restriction()
|
||||
|
||||
# Create a temporary copy of the play here, so we can run post_validate
|
||||
# on it without the templating changes affecting the original object.
|
||||
# Doing this before vars_prompt to allow for using variables in prompt.
|
||||
# Allow variables to be used in vars_prompt fields.
|
||||
all_vars = self._variable_manager.get_vars(play=play)
|
||||
templar = Templar(loader=self._loader, variables=all_vars)
|
||||
new_play = play.copy()
|
||||
new_play.post_validate(templar)
|
||||
setattr(play, 'vars_prompt', templar.template(play.vars_prompt))
|
||||
|
||||
if play.vars_prompt:
|
||||
for var in new_play.vars_prompt:
|
||||
for var in play.vars_prompt:
|
||||
vname = var['name']
|
||||
prompt = var.get("prompt", vname)
|
||||
default = var.get("default", None)
|
||||
|
@ -128,17 +125,17 @@ class PlaybookExecutor:
|
|||
else: # we are either in --list-<option> or syntax check
|
||||
play.vars[vname] = default
|
||||
|
||||
# Post validating again in case variables were entered in the prompt.
|
||||
all_vars = self._variable_manager.get_vars(play=play)
|
||||
templar = Templar(loader=self._loader, variables=all_vars)
|
||||
new_play.post_validate(templar)
|
||||
# Post validate so any play level variables are templated
|
||||
all_vars = self._variable_manager.get_vars(play=play)
|
||||
templar = Templar(loader=self._loader, variables=all_vars)
|
||||
play.post_validate(templar)
|
||||
|
||||
if self._options.syntax:
|
||||
continue
|
||||
|
||||
if self._tqm is None:
|
||||
# we are just doing a listing
|
||||
entry['plays'].append(new_play)
|
||||
entry['plays'].append(play)
|
||||
|
||||
else:
|
||||
self._tqm._unreachable_hosts.update(self._unreachable_hosts)
|
||||
|
@ -148,9 +145,9 @@ class PlaybookExecutor:
|
|||
|
||||
break_play = False
|
||||
# we are actually running plays
|
||||
batches = self._get_serialized_batches(new_play)
|
||||
batches = self._get_serialized_batches(play)
|
||||
if len(batches) == 0:
|
||||
self._tqm.send_callback('v2_playbook_on_play_start', new_play)
|
||||
self._tqm.send_callback('v2_playbook_on_play_start', play)
|
||||
self._tqm.send_callback('v2_playbook_on_no_hosts_matched')
|
||||
for batch in batches:
|
||||
# restrict the inventory to the hosts in the serialized batch
|
||||
|
|
|
@ -64,7 +64,7 @@ class Play(Base, Taggable, Become):
|
|||
|
||||
# Variable Attributes
|
||||
_vars_files = FieldAttribute(isa='list', default=[], priority=99)
|
||||
_vars_prompt = FieldAttribute(isa='list', default=[], always_post_validate=True)
|
||||
_vars_prompt = FieldAttribute(isa='list', default=[], always_post_validate=False)
|
||||
|
||||
# Role Attributes
|
||||
_roles = FieldAttribute(isa='list', default=[], priority=90)
|
||||
|
|
1
test/integration/targets/vars_prompt/aliases
Normal file
1
test/integration/targets/vars_prompt/aliases
Normal file
|
@ -0,0 +1 @@
|
|||
shippable/posix/group2
|
15
test/integration/targets/vars_prompt/runme.sh
Executable file
15
test/integration/targets/vars_prompt/runme.sh
Executable file
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -eux
|
||||
|
||||
# Install passlib on RHEL and FreeBSD
|
||||
dist=$(python -c 'import platform; print(platform.dist()[0])')
|
||||
system=$(python -c 'import platform; print(platform.system())')
|
||||
|
||||
if [[ "$dist" == "redhat" || "$system" == "FreeBSD" ]]; then
|
||||
pip install passlib
|
||||
fi
|
||||
|
||||
# Interactively test vars_prompt
|
||||
pip install pexpect
|
||||
python test-vars_prompt.py -i ../../inventory "$@"
|
115
test/integration/targets/vars_prompt/test-vars_prompt.py
Normal file
115
test/integration/targets/vars_prompt/test-vars_prompt.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import os
|
||||
import pexpect
|
||||
import sys
|
||||
|
||||
from ansible.module_utils.six import PY2
|
||||
|
||||
if PY2:
|
||||
log_buffer = sys.stdout
|
||||
else:
|
||||
log_buffer = sys.stdout.buffer
|
||||
|
||||
env_vars = {
|
||||
'ANSIBLE_ROLES_PATH': './roles',
|
||||
'ANSIBLE_NOCOLOR': 'True',
|
||||
'ANSIBLE_RETRY_FILES_ENABLED': 'False',
|
||||
}
|
||||
|
||||
|
||||
def run_test(playbook, test_spec, args=None, timeout=10, env=None):
|
||||
|
||||
if not env:
|
||||
env = os.environ.copy()
|
||||
env.update(env_vars)
|
||||
|
||||
if not args:
|
||||
args = sys.argv[1:]
|
||||
|
||||
vars_prompt_test = pexpect.spawn(
|
||||
'ansible-playbook',
|
||||
args=[playbook] + args,
|
||||
timeout=timeout,
|
||||
env=env,
|
||||
)
|
||||
|
||||
vars_prompt_test.logfile = log_buffer
|
||||
for item in test_spec[0]:
|
||||
vars_prompt_test.expect(item[0])
|
||||
if item[1]:
|
||||
vars_prompt_test.send(item[1])
|
||||
vars_prompt_test.expect(test_spec[1])
|
||||
vars_prompt_test.expect(pexpect.EOF)
|
||||
vars_prompt_test.close()
|
||||
|
||||
|
||||
# These are the tests to run. Each test is a playbook and a test_spec.
|
||||
#
|
||||
# The test_spec is a list with two elements.
|
||||
#
|
||||
# The first element is a list of two element tuples. The first is the regexp to look
|
||||
# for in the output, the second is the line to send.
|
||||
#
|
||||
# The last element is the last string of text to look for in the output.
|
||||
#
|
||||
tests = [
|
||||
# Basic vars_prompt
|
||||
{'playbook': 'vars_prompt-1.yml',
|
||||
'test_spec': [
|
||||
[('input:', 'some input\r')],
|
||||
'"input": "some input"']},
|
||||
|
||||
# Custom prompt
|
||||
{'playbook': 'vars_prompt-2.yml',
|
||||
'test_spec': [
|
||||
[('Enter some input:', 'some more input\r')],
|
||||
'"input": "some more input"']},
|
||||
|
||||
# Test confirm, both correct and incorrect
|
||||
{'playbook': 'vars_prompt-3.yml',
|
||||
'test_spec': [
|
||||
[('input:', 'confirm me\r'),
|
||||
('confirm input:', 'confirm me\r')],
|
||||
'"input": "confirm me"']},
|
||||
|
||||
{'playbook': 'vars_prompt-3.yml',
|
||||
'test_spec': [
|
||||
[('input:', 'confirm me\r'),
|
||||
('confirm input:', 'incorrect\r'),
|
||||
(r'\*\*\*\*\* VALUES ENTERED DO NOT MATCH \*\*\*\*', ''),
|
||||
('input:', 'confirm me\r'),
|
||||
('confirm input:', 'confirm me\r')],
|
||||
'"input": "confirm me"']},
|
||||
|
||||
# Test private
|
||||
{'playbook': 'vars_prompt-4.yml',
|
||||
'test_spec': [
|
||||
[('not_secret', 'this is displayed\r'),
|
||||
('this is displayed', '')],
|
||||
'"not_secret": "this is displayed"']},
|
||||
|
||||
# Test hashing
|
||||
{'playbook': 'vars_prompt-5.yml',
|
||||
'test_spec': [
|
||||
[('password', 'Scenic-Improving-Payphone\r'),
|
||||
('confirm password', 'Scenic-Improving-Payphone\r')],
|
||||
r'"password": "\$6\$rounds=']},
|
||||
|
||||
# Test variables in prompt field
|
||||
# https://github.com/ansible/ansible/issues/32723
|
||||
{'playbook': 'vars_prompt-6.yml',
|
||||
'test_spec': [
|
||||
[('prompt from variable:', 'input\r')],
|
||||
'']},
|
||||
|
||||
# Test play vars coming from vars_prompt
|
||||
# https://github.com/ansible/ansible/issues/37984
|
||||
{'playbook': 'vars_prompt-7.yml',
|
||||
'test_spec': [
|
||||
[('prompting for host:', 'testhost\r')],
|
||||
r'testhost.*ok=1']},
|
||||
]
|
||||
|
||||
for t in tests:
|
||||
run_test(playbook=t['playbook'], test_spec=t['test_spec'])
|
15
test/integration/targets/vars_prompt/vars_prompt-1.yml
Normal file
15
test/integration/targets/vars_prompt/vars_prompt-1.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
- name: Basic vars_prompt test
|
||||
hosts: testhost
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
vars_prompt:
|
||||
- name: input
|
||||
|
||||
tasks:
|
||||
- assert:
|
||||
that:
|
||||
- input == 'some input'
|
||||
|
||||
- debug:
|
||||
var: input
|
16
test/integration/targets/vars_prompt/vars_prompt-2.yml
Normal file
16
test/integration/targets/vars_prompt/vars_prompt-2.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
- name: Test vars_prompt custom prompt
|
||||
hosts: testhost
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
vars_prompt:
|
||||
- name: input
|
||||
prompt: "Enter some input"
|
||||
|
||||
tasks:
|
||||
- assert:
|
||||
that:
|
||||
- input == 'some more input'
|
||||
|
||||
- debug:
|
||||
var: input
|
17
test/integration/targets/vars_prompt/vars_prompt-3.yml
Normal file
17
test/integration/targets/vars_prompt/vars_prompt-3.yml
Normal file
|
@ -0,0 +1,17 @@
|
|||
- name: Test vars_prompt confirm
|
||||
hosts: testhost
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
vars_prompt:
|
||||
- name: input
|
||||
confirm: yes
|
||||
|
||||
tasks:
|
||||
- name:
|
||||
assert:
|
||||
that:
|
||||
- input == 'confirm me'
|
||||
|
||||
- debug:
|
||||
var: input
|
16
test/integration/targets/vars_prompt/vars_prompt-4.yml
Normal file
16
test/integration/targets/vars_prompt/vars_prompt-4.yml
Normal file
|
@ -0,0 +1,16 @@
|
|||
- name: Test vars_prompt not private
|
||||
hosts: testhost
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
vars_prompt:
|
||||
- name: not_secret
|
||||
private: no
|
||||
|
||||
tasks:
|
||||
- assert:
|
||||
that:
|
||||
- not_secret == 'this is displayed'
|
||||
|
||||
- debug:
|
||||
var: not_secret
|
14
test/integration/targets/vars_prompt/vars_prompt-5.yml
Normal file
14
test/integration/targets/vars_prompt/vars_prompt-5.yml
Normal file
|
@ -0,0 +1,14 @@
|
|||
- name: Test vars_prompt hashing
|
||||
hosts: testhost
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
vars_prompt:
|
||||
- name: password
|
||||
confirm: yes
|
||||
encrypt: sha512_crypt
|
||||
salt: 'jESIyad4F08hP3Ta'
|
||||
|
||||
tasks:
|
||||
- debug:
|
||||
var: password
|
20
test/integration/targets/vars_prompt/vars_prompt-6.yml
Normal file
20
test/integration/targets/vars_prompt/vars_prompt-6.yml
Normal file
|
@ -0,0 +1,20 @@
|
|||
- name: Test vars_prompt custom variables in prompt
|
||||
hosts: testhost
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
vars:
|
||||
prompt_var: prompt from variable
|
||||
|
||||
vars_prompt:
|
||||
- name: input
|
||||
prompt: "{{ prompt_var }}"
|
||||
|
||||
tasks:
|
||||
- name:
|
||||
assert:
|
||||
that:
|
||||
- input == 'input'
|
||||
|
||||
- debug:
|
||||
var: input
|
12
test/integration/targets/vars_prompt/vars_prompt-7.yml
Normal file
12
test/integration/targets/vars_prompt/vars_prompt-7.yml
Normal file
|
@ -0,0 +1,12 @@
|
|||
- name: Test vars_prompt play vars
|
||||
hosts: "{{ target_hosts }}"
|
||||
become: no
|
||||
gather_facts: no
|
||||
|
||||
vars_prompt:
|
||||
- name: target_hosts
|
||||
prompt: prompting for host
|
||||
private: no
|
||||
|
||||
tasks:
|
||||
- ping:
|
Loading…
Reference in a new issue