Skip to content

Commit

Permalink
Only template values in vars_prompt rather than all vars (ansible#39304)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
samdoran authored Aug 13, 2018
1 parent d5662df commit 6d38167
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bugfixes:
- vars_prompt - properly template play level variables in vars_prompt (https://github.com/ansible/ansible/issues/37984)
23 changes: 10 additions & 13 deletions lib/ansible/executor/playbook_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,13 @@ def run(self):
# 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)
Expand All @@ -128,17 +125,17 @@ def run(self):
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)
Expand All @@ -148,9 +145,9 @@ def run(self):

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
Expand Down
2 changes: 1 addition & 1 deletion lib/ansible/playbook/play.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions test/integration/targets/vars_prompt/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
shippable/posix/group2
15 changes: 15 additions & 0 deletions test/integration/targets/vars_prompt/runme.sh
Original file line number Diff line number Diff line change
@@ -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 changes: 115 additions & 0 deletions test/integration/targets/vars_prompt/test-vars_prompt.py
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 0 deletions test/integration/targets/vars_prompt/vars_prompt-1.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions test/integration/targets/vars_prompt/vars_prompt-2.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 17 additions & 0 deletions test/integration/targets/vars_prompt/vars_prompt-3.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 16 additions & 0 deletions test/integration/targets/vars_prompt/vars_prompt-4.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 14 additions & 0 deletions test/integration/targets/vars_prompt/vars_prompt-5.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions test/integration/targets/vars_prompt/vars_prompt-6.yml
Original file line number Diff line number Diff line change
@@ -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 changes: 12 additions & 0 deletions test/integration/targets/vars_prompt/vars_prompt-7.yml
Original file line number Diff line number Diff line change
@@ -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:

0 comments on commit 6d38167

Please sign in to comment.