I'm trying to adopt the task in the last answer from this post Ansible - managing multiple SSH keys for multiple users & roles
My variable looks like:
provisioning_user:
- name: ansible
state: present
ssh_public_keyfiles:
- ansible.pub
- user.pub
- name: foo
state: present
ssh_public_keyfiles:
- bar.pub
- key.pub
and my code looks like
- name: lookup ssh pubkeys from keyfiles and create ssh_pubkeys_list
set_fact:
ssh_pubkeys_list: "{{ lookup('file', item.ssh_public_keyfiles) }}"
loop: "{{ provisioning_user }}"
register: ssh_pubkeys_results_list
I want to store keys under the files directory and assign them with the variable to different users so that when a key changes, i only have to change the file and run the playbook instead of changing it in any hostvars file where the old key is used. But I get the following error and dont know how to solve it. I want to do this for every user defined in provisioning_user
fatal: [cloud.xxx.xxx]: FAILED! =>
msg: 'An unhandled exception occurred while running the lookup plugin ''file''. Error was a <class ''AttributeError''>, original message: ''list'' object has no attribute ''startswith''. ''list'' object has no attribute ''startswith'''
Can anyone please help me?
The file
lookup reads the content of a single file, but you're passing it a list. That's not going to work, and is the direct cause of the error message you've reported.
You're also using both set_fact
and register
in the same task, which doesn't make much sense: the whole point of a set_fact
task is to create a new variable; you shouldn't need to register
the result.
Your life is going to be complicated by the fact that each user can have multiple key files. We need to build a data structure that maps each user name to a list of keys; we can do that like this:
- name: lookup ssh pubkeys from keyfiles
set_fact:
pubkeys: >-
{{
pubkeys |
combine({
item.0.name: pubkeys.get(item.0.name, []) + [lookup('file', item.1)]})
}}
vars:
pubkeys: {}
loop: "{{ provisioning_user|subelements('ssh_public_keyfiles') }}"
This creates the variable pubkeys
, which is a dictionary that maps usernames to keys. Assuming that our provisioning_user
variable looks like this:
provisioning_user:
- name: ansible
state: present
ssh_public_keyfiles:
- ansible.pub
- user.pub
- name: foo
state: present
ssh_public_keyfiles:
- bar.pub
- key.pub
- name: bar
state: present
ssh_public_keyfiles: []
After running the above task, pubkeys
looks like:
"pubkeys": {
"ansible": [
"ssh-rsa ...",
"ssh-rsa ..."
],
"foo": [
"ssh-rsa ...",
"ssh-rsa ..."
]
}
We can use pubkeys
in our authorized_keys
task like this:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ '\n'.join(pubkeys[item.name]) }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
loop: "{{ provisioning_user }}"
when: item.name in pubkeys
I think your life would be easier if you were to rethink how you're managing keys. Instead of allowing each user to have a list of multiple key files, have a single public key file for each user -- named after the username -- that may contain multiple public keys.
That reduces your provisioning_user
data to:
provisioning_user:
- name: ansible
state: present
- name: foo
state: present
- name: bar
state: present
And in our files/
directory, we have:
files
├── ansible.keys
└── foo.keys
You no longer need the set_fact
task at all, and the authorized_keys
task looks like:
- name: test key
authorized_key:
user: "{{ item.name }}"
key: "{{ keys }}"
comment: "{{ item.key_comment | default('managed by ansible') }}"
state: "{{ item.state | default('true') }}"
exclusive: "{{ item.key_exclusive | default('true') }}"
key_options: "{{ item.key_options | default(omit) }}"
manage_dir: "{{ item.manage_dir | default('true') }}"
when: >-
('files/%s.keys' % item.name) is exists
vars:
keys: "{{ lookup('file', '%s.keys' % item.name) }}"
loop: "{{ provisioning_user }}"
Note that in the above the when
condition requires an explicit path, while the file
lookup will implicitly look in the files
directory.
These changes dramatically simplify your playbook.