This is the offending line where the error seems to be:
- name: Generate random password
vars:
jupyter_pwd: "{{ lookup('password', '/root/jupyter_password length=10') }}"
I'm trying to generate a random password and store it in the jupyter_pwd variable.
This is not how Ansible works. It's not like i.e. bash's variable expansion. In Ansible you need to use specified action (command) to do the task - in this case you need to use the task that would support executing shell commands or scripts. This middle layer is needed because Ansible can run your playbook on any target host, be it remote or local machine, with the same unaltered playbook. Therefore it needs to provide middle man for any task to be able to do that and this is what Ansible's actions are for.
In your case you most likely need command action:
- name: "Generate random password"
command: command-to-execute
register: jupyter_pwd
Where command-to-execute is binary or script on remote host you want to run. Whatever that command-to-execute returns is captured and made available as jupyter_pwd variable to the rest of your playbook.
See [docs] for more information.
If command action is too strict (which may be if you want to chain several shell tools), then shell action may be the way to go instead.
Related
When using Ansible, the controller is the machine you execute playbooks from.
I want to only run an Ansible task in a playbook when the controller has a file.
Is there a way to write something like this and exists(x.tgz), below?
- name: "copy up and unarchive the x for x if we have it on our controller"
unarchive:
src: x.tgz
dest: /usr/local/lib/x
when: "{{ { enable_me | bool } and exists(x.tgz) }}"
I want to only run an Ansible task in a playbook when the controller has a file.
By default, lookup plugins execute and are evaluated on the Ansible control machine only.
Is there a way to write something like this and exists(x.tgz), below?
Therefore and instead of using multiple tasks to check for the file existence, addressing delegation and running once, registering the result, running tasks conditionally on the registered result, you could probably write something like this
when: ( enable_x | bool ) and lookup('file', './x.tgz', errors='warn')
since it will
Check if the file exists on the Ansible Control Node
Returns the path to file found
Filter returns true if there is a path
Further Documentation
Lookup plugins
Like all templating, lookups execute and are evaluated on the Ansible control machine.
first_found lookup – return first file found from list
Further Q&A
How to search for ... a file using Ansible when condition
I'm aware I can add host variables dynamically via an inventory script. I'm wondering if I can scripts in the host_vars directory which will be executed instead of simple read.
I have tried to create a simple script that outputs some variables. It seems only .json and .yml or no extension are read by the ansible-playbook. Since these are not executed the raw source will result in an error.
So, hence the question. Is this even possible and if not, would you be aware of a method to achieve the same results: Query a (local) dynamic source for variables of a particular host.
I'm pretty sure lookup("pipe") will do what you want, provided the script is available on the target host:
- set_fact:
my_vars: '{{ lookup("pipe", "./my_script.py") | from_json }}'
(substituting from_json with from_yaml or whatever to coerce the textual output from the script into a python datastructure; it's possible that ansible would coerce it automagically, but explicit is better than implicit)
If you want the script that is on the control machine to run, you'll likely have to do some hoopjumpery with delegate_to: and some hostvars ninjary to promote the set_fact: off of the control host over to all playbook hosts
I have a issue when i am trying to have a conditional statement based on the variable set in my inventory file. Below is the details. My inventory file looks like this.
[webserver]
server1
server2
[appserver]
server1
[webserver:vars]
TYPE=w
[appserver:vars]
TYPE=a
Now when i am trying to add a condition in my task like
name: abc
shell: run this task
when: TYPE == "w"
name: cde
shell: run this task
when: TYPE == "a"
Now when i run the Play 1 it picks up the first variable and stores it but when it tries to run the task second time (Play2) its still have the same variable and fails. I have two plays 1 for web and other for app. Please let me know what may be the issue.
You can't define the same variable twice in an inventory file, even if it belongs to another group.
After the parsing of your inventory file the only "TYPE" known to Ansible will be the last one defined in your inventory file. In your case that would be one within appserver.
If you switch the order of your variables you will face the exact opposite behavior I guess.
In my opinion you should change the way to determine which tasks to run.
Little example:
- name: some task for webservers
shell: run this webserver task
when: run_webserver_task
- name: some task for appservers
shell: run this appserver task
when: run_appserver_task
Please see this link as evidence for my bold statement:
https://github.com/ansible/ansible/issues/6538
OR
You could try to design your inventory file the yamlish way instead of using an ini file. Please see:
http://docs.ansible.com/ansible/latest/intro_inventory.html
With the command module, if the command creates a file, you can check to see if that file exists. If it does it prevents the command executing again.
- command: touch ~/myfile
args:
creates: ~/myfile
However, if the command does not create a file then on a re-run it executes again.
To avoid a second execution, I create some random file on a change (notify) as follows:
- command: dothisonceonly # this does not create a file
args:
creates: ~/somefile
notify: done
then the handler:
- name: done
command: touch ~/somefile
This approach works but is a bit ugly. Can anyone shed let on best practice? Maybe setting some fact? Maybe a whole new approach?
It is a fact (in common language) that a command was run successfully on a specific target host, so the most appropriate would be to use local facts (in Ansible vernacular).
In your handler save the state as a JSON file under /etc/ansible/facts.d with copy module and content parameter.
It will be retrieved and accessible whenever you run a play against the host with a regular fact-gathering process.
Then you can control the tasks with when condition (you need to include the default filter for the situation when no fact exists yet).
Ideally with Ansible, check for the state that was changed by the first command rather than using a file as a proxy. The reason being that checking the actual state of something provides better immutability since it is tested on every pass.
If there is a reason not to use that approach. Then register the result of the command and use that instead of a notifier to trigger creation of the file.
- command: dothisonceonly # this does not create a file
creates: ~/somefile
register: result
- file:
path: ~/somefile
state: touch
when: result|succeeded
If you are curious to see what is happening here, add:
- debug: var=result
Be aware with notifiers that they are run at the end of the play. This means that if a notifier is triggered by a task but then then play fails to complete, the notifier will not be run. Conversely there are Ansible options which cause notifiers to run even when not triggered by tasks.
This is my actual implementation based on the answer by #techraf
- command: echo 'phew'
register: itworks
notify: Done itworks
when: ansible_local.itworks | d(0) == 0
and handler:
- name: Done itworks
copy:
content: '{{ itworks }}'
dest: /etc/ansible/facts.d/itworks.fact
Docs:
http://docs.ansible.com/ansible/playbooks_variables.html#local-facts-facts-d
Thanks #techraf this works great and persists facts.
EDIT
Applied default value logic in #techraf's comment.
Is it possible to detect within a playbook what the Ansible invocation was? Specifically I'd like to detect whether the "--ask-vault-pass" option was supplied, and if not, exit the playbook.
I think the easiest way might be to use another task to check the content of your file. For example,
- name: check if a string is in the content
failed_when: "expected_string" not in "{{ file.content }}"
If the vault-pass/file is not supplied, the file can not be decrypted. However, you will also need to make sure the ansible.cfg file is not configured with the file path already.