Ansible: Prompt for a Variable Only if it is Not Already Set
Ansible's vars_prompt always did strike me as a strange design decision for a framework intended to automate deployment. Automation is all about removing the need for a human in the loop. Why then add ways to force user input?
--- - name: Provision # =============== hosts: localhost connection: local gather_facts: yes vars_prompt: - name: example_var prompt: "Provide a value for example_var:" default: example_value private: no
Nonetheless, there it is. It seems to be widely used as an alternative to preliminary Bash scripts or other command line tooling among those groups who are in fact interested in user input as the primary way of defining a build. You can look at Streisand as an example of a system primarily aimed at less technically sophisticated end users, and which makes use of Ansible to organize questions and answers that will define the server provisioning details.
Rejected Approach #1: Satisfying Prompts via Piped Answers
What about the rest of us, however, who want systems to be quiet and read from config files? Faced with a
vars_prompt that can't be changed, for whatever reason, there is always the fragile fallback of wrapping the Ansible invocation and passing in the answers you want to provide the various prompts. To do this, you have to know the order of the prompts, and write something like this:
# Sort out the answers to the various prompts. cat > responses.txt <<EOF example_value_1 y example_value_2 n EOF # Feed the responses into the invocation of Ansible. cat responses.txt | ansible-playbook "playbooks/example.yml"
This will break horribly, or worse, quietly, as soon as anyone changes the Ansible definitions without also changing the wrapper script appropriately, of course.
Rejected Approach #2: Optional Prompts using Pause and Register
A better course is to make the prompts optional. For example, only fire the prompt if the associated variable is not defined. Ansible makes this is a little challenging due to the order in which various activities take place.
var_prompt occurs prior to gathering facts, so you can't check there to see if a variable is defined - it won't be defined yet regardless of your attempt to set it. An old, pre-Ansible-2.* standard approach was instead to use
pause as shown here:
--- - name: Provision # =============== hosts: localhost connection: local gather_facts: yes pre_tasks: - pause: prompt: "Provide a value for example_var. Enter defaults to 'example_value':" when: example_var is not defined register: example_var - name: Default for example_var set_fact: example_var: example_value when: example_var == ""
So now if you pass in a definition, no prompt will take place:
# Define variables. Prompts will be silenced by their existence. cat > extra-vars.yml <<EOF example_var: example_value EOF ansible-playbook \ --extra-vars="@extra-vars.yml" \ "playbooks/example.yml"
Another possibility is to set a single blanket
force_noninteractive variable and have all uses of
prompt respect it, which is probably simpler to manage in a larger Ansible codebase.
--- - name: Provision # =============== hosts: localhost connection: local gather_facts: yes pre_tasks: - pause: prompt: "Provide a value for example_var_1. Enter defaults to 'example_value_1':" when: force_noninteractive is not defined register: example_var_1 - name: Default for example_var_1 set_fact: example_var: example_value_1 when: example_var_1 == "" - pause: prompt: "Provide a value for example_var_2. Enter defaults to 'example_value_2':" when: force_noninteractive is not defined register: example_var_2 - name: Default for example_var_2 set_fact: example_var: example_value_2 when: example_var_2 == ""
# Define variables. Prompts will be silenced by force_noninteractive. cat > extra-vars.yml <<EOF force_noninteractive: true example_var_1: example_value # Set this one to the default value by passing an empty string. example_var_2: "" EOF ansible-playbook \ --extra-vars="@extra-vars.yml" \ "playbooks/example.yml"
register approach is now not so great in Ansible 2.*, as the user input is invisible. That makes it essentially useless for any text entry.
Desired Approach: Passing in Variables on the Command Line
The currently recommended approach is to put all of the variables requested in
var_prompt blocks into
extra-vars files and pass them on the command line. Any
var_prompt variable that is defined already via the command line will not be prompted for. It is also possible to use the inventory to achieve the same outcome, but that quickly becomes clunky for any significant number of variables. No changes are needed to the playbooks, and you have the option to run Ansible either in an interactive mode without passing in additional variable definitions, or in a non-interactive mode with additional variable definitions provided via a config file.
# Run interactive, without passing in variables. ansible-playbook "playbooks/example.yml"
# Define variables for the non-interactive run. cat > extra-vars.yml <<EOF example_var: example_value EOF # Run non-interactive, passing in variables. ansible-playbook \ --extra-vars="@extra-vars.yml" \ "playbooks/example.yml"
What about prompts that are not intended to set variables, however? How to bypass these?
- pause: prompt: "Press enter to continue:"
In this case, the best thing to do is change the runbook to wrap the prompt in a block keyed to a variable. Then pass in that variable to suppress the contents of the block from executing:
- block pause: prompt: "Press enter to continue:" when: noninteractive is undefined
# Define variables for the non-interactive run. cat > extra-vars.yml <<EOF noninteractive: true example_var: example_value EOF # Run non-interactive, passing in variables. ansible-playbook \ --extra-vars="@extra-vars.yml" \ "playbooks/example.yml"