Here is something I finally understood in Ansible that has been annoying the hell out of me for a while: relative paths. The idea of relative paths is so if you have to tell Ansible to grab a file -- like some config file or even an image or certificate -- and put it somehwere else you do not have to give the entire source path.
Imagine if you wrote your playbook in your Linux desktop which has a task that grabs the file /home/bob/bobs-development-stuff/server.stuff/ansible/roles/webservers/files/main-logo.png and put it somewhere in the target machine. That is a mouthfull of a path. But then since you are clever you have this playbook in an internal git repository and then check it out in your Mac laptop to do some work. Thing is the path now is /Users/bobremote/ansible/roles/webservers/files/main-logo.png and now your task ain't working no more. And that is the short version for why absolute paths in your ansible box are not a great idea.
So we use relative paths instead and the main question of the day is what are those paths relative to?
Do you remember when we talked about creating and uploading ssh key pairs? Well, there was a bit of a fudge right in the key: line
- name: Copy the public key to the user's authorized_keys # It also creates the dir as needed authorized_key: user: "{{user_name}}" key: '{{ lookup("file", "roles/files/" ~ user_name ~ ".ssh.pub") }}' state: present
On a second thought, it is really not a fudge but it hints on the answer to our question.All we are asking is for the task to grab a file inside the roles/files directory. The original layout for this playbook looks like this
ansible hosts ansible.cfg site.yml roles defaults files moose.ssh moose.ssh.pub handlers main.yml meta tasks main.yml template vars
So we really only have just one playbook inside the ansible directory. When we write in the roles/tasks/main.yml tasklist file to grab the .ssh.pub< file, it knows exactly how to get it. With time, we became clever and would like to plan ahead so decided to break the roles into the ones common to every server and the ones that build up on the common one and then do the specialization stuff, say website or database server. So, what we really want is to have a layout like this (inside the same old ansible dir)
site.yml web.yml group_vars roles common defaults files epel.repo iptables-save moose.ssh moose.ssh.pub handlers main.yml meta tasks main.yml template vars website defaults files handlers meta tasks main.yml template vars db defaults files handlers meta tasks main.yml template vars
The playbook that copies the ssh key is in the common role, specifically roles/common/tasks/main.yml (yes I could break it out some more but let's not confuse things just yet and hurt my brain). And we expect if we write our function as
- name: Copy the public key to the user's authorized_keys # It also creates the dir as needed authorized_key: user: "{{user_name}}" key: '{{ lookup("file", "./files/" ~ user_name ~ ".ssh.pub") }}' state: present
our playbook to create roles/common/files/moose.ssh.pub and then upload to whatever machine we are building. Instead it gives us the following friendly message:
fatal: [ansibletest -> localhost]: FAILED! => {"changed": true, "cmd": "ssh- keygen -f ./files/\"moose\".ssh -t rsa -b 4096 -N \"\" -C \"moose\"", "delta": "0: 00:01.056746", "end": "2016-11-30 15:55:19.618226", "failed": true, "rc": 1, "s tart": "2016-11-30 15:55:18.561480", "stderr": "Saving key \"./files/moose.ssh\ " failed: No such file or directory", "stdout": "Generating public/private rsa key pair.", "stdout_lines": ["Generating public/private rsa key pair."], "warni ngs": []}
Clearly we have a bit of a path problem. Don't believe me? Let's ask where the playbook thinks it is running from
- name: Find out where I am locally speaking local_action: shell pwd > /tmp/here
All we are doing here is getting the path this is being executed from and saving in a file so we can see it. And see it we shall:
raub@desktop:~/dev/ansible$ cat /tmp/here /home/raub/dev/ansible raub@desktop:~/dev/ansible$
My guess is that if we use relative paths, they are all from where we ran the playbook from, /home/raub/dev/ansible in my case. Which really sucks! Now, there is an environmental variable in Ansible called role_path, which I found being mentioned in the docs which should show the path to the role we are currently running. Let's try it out
- name: Find out where I am locally speaking debug: msg="Role path is supposed to be {{ role_path }} "
which gives
TASK [website : Find out where I am locally speaking] ****************************** ok: [ansibletest] => { "msg": "Role path is supposed to be /home/raub/dev/ansible/roles/website " }
So, the solution is to rewrite our little function as
- name: Copy the public key to the user's authorized_keys # It also creates the dir as needed authorized_key: user: "{{user_name}}" key: '{{ lookup("file", "{{ role_path }}/files/" ~ user_name ~ ".ssh.pub") }}' state: present
so the path becomes /home/raub/dev/ansible/roles/website/files/moose.ssh.pub