9

How can I concatenate a contents of several files into a variable?

Here's the problem: I'm trying to set public keys for a user on a remote machine. The example from the authorized_key documentation that almost works:

- name: Set up authorized_keys for the deploy user
  authorized_key: user=deploy
                  key="{{ item }}"
  with_file:
    - public_keys/doe-jane
    - public_keys/doe-john

But in fact I need to use exclusive=yes, so after the update all non-provided public keys are removed.

If the exclusive=yes is provided then only the last public key listed remains in the .ssh/authorized_keys file (also reported as a bug).

My current approach:

- name: create empty temporary keys file
  local_action: "shell > /tmp/auth_keys"

- name: concat keys to temporary file
  local_action: "shell echo {{ item }} >> /tmp/auth_keys"
  with_file:
   - public_keys/doe-jane
   - public_keys/doe-john

- name: set up authorized_keys
  authorized_key: user=deploy
                  key="{{ lookup('file', '/tmp/auth_keys') }}"
                  exclusive=yes

This works but the first two commands always produce "changed". Also I feel there must be a more elegant solution for this.

So, is there a way how to concatenate contents of several files into a variable? Or is there any better approach in general for this task?

Ikar Pohorský
  • 4,617
  • 6
  • 39
  • 56
  • Thinking of this, to get rid of the "changed" status `changed_when: False` can be appended to the first two commands but still I'm not quite happy with the result. – Ikar Pohorský Oct 09 '15 at 07:55

2 Answers2

10

There's nothing overly wrong with your first option and then, as your comment mentions, simply using changed_when: False to acknowledge that this isn't something that you care about the result of it changing is a valid option.

To answer the actual question title you can, as mentioned in the GitHub "issue" you linked, simply concatenate the lookups directly in the task like so:

- name: set up authorized_keys
  authorized_key: user=deploy
                  key="{{ lookup('file', 'public_keys/doe-jane') + lookup('file', 'public_keys/doe-john')}}"
                  exclusive=yes

However, a cleaner option may be to use the assemble module to concatenate your keys.

This would then change your current approach into something more like:

- name: create concatenated keys file
  local_action: "assemble src=roles/ssh_keys/files/ssh_keys/ dest=/tmp/ssh_keys_file"

- name: set up authorized_keys
  authorized_key: user=deploy
                  key="{{ lookup('file', '/tmp/ssh_keys_file' }}"
                  exclusive=yes

This will only be marked as changed if the destination file has changed at all so running it over and over leaves a lovely wall of green.

This relies on your ssh keys all being files and in the same folder (assemble is typically used for turning conf.d style directories into a single .conf file for programs that don't use the conf.d style configuration) but this is probably the most sensible way of holding them anyway.

The benefit of this is that you can simply add/remove ssh keys from the folder specified and it will be picked up on the next play without any need to add/remove keys being explicitly defined in the task itself as well.

mike
  • 1,854
  • 17
  • 23
ydaetskcoR
  • 53,225
  • 8
  • 158
  • 177
  • Thanks, that makes sense. Though I'd still love to e.g. define one list of keys for staging, one for testing and one list for the production hostgroups without the need to duplicate the keys in the repository. Never mind, I'm glad that there is no "too obvious" solution I wasn't able to find out;) – Ikar Pohorský Oct 12 '15 at 07:30
  • 2
    As mentioned with the first code block, you can simply concatenate all the lookups in the variable directly and that will work. It looks horrible to me and I'm not convinced it's a good way to manage them but it would do exactly as you wanted. Personally I prefer the idea of having different directories full of public keys that get applied to different environments types even if there is some duplication of the keys. That way if you see "doe-jane" in both dev and test folders you know straight away that Jane Doe has access to dev and test. – ydaetskcoR Oct 12 '15 at 12:30
  • 1
    I don't know if this has changed in a later version of Ansible, but `lookup() + lookup()` concatenates files with no spacing between them. If you use this, only the first SSH key you specify will work, the others will not (confirmed on 2.7.10). This will work however: `{{ lookup('file', 'myfile') }}\n{{ lookup('file', 'myfile2') }}` – pwaring Jun 16 '19 at 09:37
2

If you use the assemble module, you can as well just use it to generate the remote authorized_keys file in one step instead of pre-assembling it locally, it stays green, too:

- name: deploy authorized keys
  assemble:
    remote_src: no
    src: "{{ ssh_key_dir }}"
    dest: "/home/{{ user }}/.ssh/authorized_keys"
Remigius Stalder
  • 1,921
  • 2
  • 26
  • 31