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
|
# clear any filters which may have been applied to the inventory
|
||||||
self._inventory.remove_restriction()
|
self._inventory.remove_restriction()
|
||||||
|
|
||||||
# Create a temporary copy of the play here, so we can run post_validate
|
# Allow variables to be used in vars_prompt fields.
|
||||||
# on it without the templating changes affecting the original object.
|
|
||||||
# Doing this before vars_prompt to allow for using variables in prompt.
|
|
||||||
all_vars = self._variable_manager.get_vars(play=play)
|
all_vars = self._variable_manager.get_vars(play=play)
|
||||||
templar = Templar(loader=self._loader, variables=all_vars)
|
templar = Templar(loader=self._loader, variables=all_vars)
|
||||||
new_play = play.copy()
|
setattr(play, 'vars_prompt', templar.template(play.vars_prompt))
|
||||||
new_play.post_validate(templar)
|
|
||||||
|
|
||||||
if play.vars_prompt:
|
if play.vars_prompt:
|
||||||
for var in new_play.vars_prompt:
|
for var in play.vars_prompt:
|
||||||
vname = var['name']
|
vname = var['name']
|
||||||
prompt = var.get("prompt", vname)
|
prompt = var.get("prompt", vname)
|
||||||
default = var.get("default", None)
|
default = var.get("default", None)
|
||||||
|
@ -128,17 +125,17 @@ class PlaybookExecutor:
|
||||||
else: # we are either in --list-<option> or syntax check
|
else: # we are either in --list-<option> or syntax check
|
||||||
play.vars[vname] = default
|
play.vars[vname] = default
|
||||||
|
|
||||||
# Post validating again in case variables were entered in the prompt.
|
# Post validate so any play level variables are templated
|
||||||
all_vars = self._variable_manager.get_vars(play=play)
|
all_vars = self._variable_manager.get_vars(play=play)
|
||||||
templar = Templar(loader=self._loader, variables=all_vars)
|
templar = Templar(loader=self._loader, variables=all_vars)
|
||||||
new_play.post_validate(templar)
|
play.post_validate(templar)
|
||||||
|
|
||||||
if self._options.syntax:
|
if self._options.syntax:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self._tqm is None:
|
if self._tqm is None:
|
||||||
# we are just doing a listing
|
# we are just doing a listing
|
||||||
entry['plays'].append(new_play)
|
entry['plays'].append(play)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._tqm._unreachable_hosts.update(self._unreachable_hosts)
|
self._tqm._unreachable_hosts.update(self._unreachable_hosts)
|
||||||
|
@ -148,9 +145,9 @@ class PlaybookExecutor:
|
||||||
|
|
||||||
break_play = False
|
break_play = False
|
||||||
# we are actually running plays
|
# we are actually running plays
|
||||||
batches = self._get_serialized_batches(new_play)
|
batches = self._get_serialized_batches(play)
|
||||||
if len(batches) == 0:
|
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')
|
self._tqm.send_callback('v2_playbook_on_no_hosts_matched')
|
||||||
for batch in batches:
|
for batch in batches:
|
||||||
# restrict the inventory to the hosts in the serialized batch
|
# restrict the inventory to the hosts in the serialized batch
|
||||||
|
|
|
@ -64,7 +64,7 @@ class Play(Base, Taggable, Become):
|
||||||
|
|
||||||
# Variable Attributes
|
# Variable Attributes
|
||||||
_vars_files = FieldAttribute(isa='list', default=[], priority=99)
|
_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
|
# Role Attributes
|
||||||
_roles = FieldAttribute(isa='list', default=[], priority=90)
|
_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