Using different files for different hosts - ansible

I am using Ansible (with Ansible Tower) and want to create a task which runs a test.
I have a properties file for (SoapUI tests) which is of the form key=value. The value will change according to the host that the test is running on.
Although there is a way to specify different variables per host there does not seem to be a way to specify different files for different hosts.
The best way there seems to be (to my knowledge) is to create a template (http://docs.ansible.com/ansible/template_module.html) instead of the properties file and populate the template values using host variables. Is there an alternative or better way?

You can simply set the file path to be a variable.
So if you have a task that is copying a file to the box that looks like:
- name: Copy properties file to box
copy:
src: path/to/file
dest: path/to/dest
You could simply change this to be:
- name: Copy properties file to box
copy:
src: "{{ properties_file }}"
dest: path/to/dest
And then you simply need to set your properties_file variable for the appropriate environment group or host like so:
group_vars/dev:
properties_file: path/to/dev/file
group_vars/prod:
properties_file: path/to/prod/file

Related

Generate Ansible template variable only once

Context
I have an Ansible template that creates a configuration file. Most variables used by this template come from some stable source, such as variables defined in the playbook. However one variable contains a secret key which somehow needs to be generated if it does not already exist.
I want Ansible to generate the key on an as-needed basis. I do not want the key to be stored on the OS running Ansible, like is done with the password module. And naturally I want idempotence.
I currently have a working solution but I'm not happy with it, hence my question here. My current solution uses an extra file which is include by the configuration file created by my template. This is possible as it is a PHP file. I have a task using the shell module that generates the secret when this extra file does not exist, and then another that then creates it using the registered variable.
- name: Generate secret key
shell: openssl rand -hex 32
register: secret_key_command
when: not SecretKey.stat.exists
- name: Create SecretKey.php
template:
src: "SecretKey.php.j2"
dest: "{{ some_dir }}/SecretKey.php"
when: not SecretKey.stat.exists
Surely there is a less contrived way to do this?
Question
Is there a nice way to have a variable that gets generated only once in an Ansible template?
I am not sure, but I understood correctly, we want to generate your template if it doesn't already exists. So you can just do as follow:
- name: Create SecretKey.php
template:
src: "SecretKey.php.j2"
dest: "{{ some_dir }}/SecretKey.php"
force: no
force: no tells to don't overwrite a file if it already exists. No need to do extra check.

Is it possible to use dynamic host_vars files?

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

Ansible lookup file plugin with variable inside the file

Hei guys,
After a few days of struggling, I've decided to write my issue here.
I have an ansible(2.7) task that that has a single variable, which points to a host var that uses the file lookup plugin.
Thing is that this works, for one host, but I have 6 hosts, where a value inside the lookup file should be different for each of the hosts.
Can you pass a variable inside the file that is looked up?
I'm new to ansible and don't master it fully.
Has someone encountered this before?
Task:
- name: Copy the file to its directory
template:
src: file.conf
dest: /path/to/file
vars:
file_contents: "{{file_configuration}}"
-----
hostvar file:
file_configuration:
- "{{lookup('file', './path/to/file') | from_yaml}}"
----
file that is looked up:
name: {{ value that should be different per host }}
driver:
long_name: unchanged value.
You should have 6 host_vars files, one for each host. In that host_var file, set your desired value.
Ansible documentation is here
E.g.
https://imgur.com/a/JCbnNBT
Content of host1.yml
---
my_value: something
Content of host2.yml
---
my_value: else
Ansible automagicly sees the host_var folder. It looks in that folder and searches for files which exactly match a host in the play.
So ensure your host_var/filename.yml matches the hostname in your play!
If there is a match, then it'll use that .yml file for that specific host.

Save vars back in a file automatically after including and modifying it

Is there anything like save_vars in Ansible that would automatically save modified var/fact imported with include_vars?
The following code serves the purpose but it is not an elegant solution.
# vars/my_vars.json
{
"my_password": "Velicanstveni",
"my_username": "Franjo"
}
# playbook.yaml
---
- hosts: localhost
gather_facts: no
tasks:
- name: fetch vars
include_vars:
file: my_vars.json
name: playbook_var
- name: change var
set_fact:
playbook_var: "{{ playbook_var|combine({'my_username': 'Njofra' }) }}"
- name: save var to file
copy:
content: "{{ playbook_var | to_nice_json }}"
dest: vars/my_vars.json
I was wondering if there is an option in Ansible we can turn on so that whenever the var/fact is changed, during the playbook execution, this is automatically updated in the file var was imported from. The background problem I am trying to solve is having global variables in Ansible. The idea is to save global vars in a file. Ansible has system of hostavars which is collection of per-hostname variables but not all data is always host-related.
I think a fact cache will plus or minus do that, although it may have some sharp edges to getting it right, since that's not exactly the problem it is trying to solve. The json cache plugin sounds like it is most likely what you want, but there is a yaml cache plugin, too.
The bad news is that, as far as I can tell, one cannot configure those caching URIs via the playbook, rather one must use either the environment variable or an ansible.cfg setting.

Finding file name in files section of current Ansible role

I'm fairly new to Ansible and I'm trying to create a role that copies a file to a remote server. The local file can have a different name every time I'm running the playbook, but it needs to be copied to the same name remotely, something like this:
- name: copy file
copy:
src=*.txt
dest=/path/to/fixedname.txt
Ansible doesn't allow wildcards, so when I wrote a simple playbook with the tasks in the main playbook I could do:
- name: find the filename
connection: local
shell: "ls -1 files/*.txt"
register: myfile
- name: copy file
copy:
src="files/{{ item }}"
dest=/path/to/fixedname.txt
with_items:
- myfile.stdout_lines
However, when I moved the tasks to a role, the first action didn't work anymore, because the relative path is relative to the role while the playbook executes in the root dir of the 'roles' directory. I could add the path to the role's files dir, but is there a more elegant way?
It looks like you need access to a task that looks up information locally, and then uses that information as input to the copy module.
There are two ways to get local information.
use local_action:. That's shorthand for running the task agains 127.0.0.1, more info found here. (this is what you've been using)
use a lookup. This is a plugin system specifically designed for getting information locally. More info here.
In your case, I would go for the second method, using lookup. You could set it up like this example:
vars:
local_file_name: "{{ lookup('pipe', 'ls -1 files/*.txt') }}"
tasks:
- name: copy file
copy: src="{{ local_file_name }}" dest=/path/to/fixedname.txt
Or, more directly:
tasks:
- name: copy file
copy: src="{{ lookup('pipe', 'ls -1 files/*.txt') }}" dest=/path/to/fixedname.txt
With regards to paths
the lookup plugin is run from the context of the task (playbook vs role). This means that it will behave differently depending on where it's used.
In the setup above, the tasks are run directly from a playbook, so the working dir will be:
/path/to/project -- this is the folder where your playbook is.
If you where to add the task to a role, the working dir would be:
/path/to/project/roles/role_name/tasks
In addition, the file and pipe plugins run from within the role/files folder if it exists:
/path/to/project/roles/role_name/files -- this means your command is ls -1 *.txt
caveat:
The plugin is called every time you access the variable. This means you cannot trust debugging the variable in your playbook, and then relying on the variable to have the same value when used later in a role!
I do wonder though, about the use-case for a file that resides inside a projects ansible folders, but who's name is not known in advance. Where does such a file come from? Isn't it possible to add a layer in between the generation of the file and using it in Ansible... or having a fixed local path as a variable? Just curious ;)
Just wanted to throw in an additional answer... I have the same problem as you, where I build an ansible bundle on the fly and copy artifacts (rpms) into a role's files folder, and my rpms have versions in the filename.
When I run the ansible play, I want it to install all rpms, regardless of filenames.
I solved this by using the with_fileglob mechanism in ansible:
- name: Copy RPMs
copy: src="{{ item }}" dest="{{ rpm_cache }}"
with_fileglob: "*.rpm"
register: rpm_files
- name: Install RPMs
yum: name={{ item }} state=present
with_items: "{{ rpm_files.results | map(attribute='dest') | list }}"
I find it a little bit cleaner than the lookup mechanism.

Resources