I’m still having a lot of trouble understanding how a loop handles passing multiple files through a shell command.
I have a directory on a CA Server: certdir
Inside ‘certdir’ are directories named for all of my hosts (only have two right now for testing speed) - host1 and - host2
Inside each of these dirs are host1.our-domain.csr and host1.our-domain.req files
I’m trying to pass both of these files in this shell command: openssl ca -batch -config {{ dir }}.our-domain.req -extensions server_cert -days 720 -notext -md sha256 -in {{ dir }}.our-domain.csr -out {{ dir }}.dgs.mil.crt
What I’m hoping to get out of this command is a file created in each host directory, ex: host1.our-domain.crt, host2.our-domain.crt, in their respective host directories and so on.
I’m registering each host directory in an earlier part of the play as “dir”. I’m registering the csr and req files as well (csr and req) respectively.
Here’s the part of the play that errors out:
name: create crt file
ansible.builtin.shell: openssl ca -batch -config {{ dir }}.our-domain.req -extensions server_cert -days 720 -notext -md sha256 -in {{ dir }}.our-domain.csr -out {{ dir }}.dgs.mil.crt
loop: “{{ csr.files | map(attribute=‘path’) | list }}”
loop_control:
label: "{{ item | basename }}
when: out_crt == False (I only want the shell command to run if a crt file doesn’t already exist)
The above is just the latest iteration of what I’ve tried. I suspect it has something to do with “item” is the reason it’s failing. I don’t really even get an error. It just keeps skipping that task in the play with this: skipping: [CA_host_dir] . So it’s not even referencing the host dirs, just the CA dir.
It could be that your boolean comparison is not working properly. Try when: out_crt or when: out_crt | bool instead.
Additionally, there should be {{ item }} or a variable derived from {{ item }} somewhere in your command or you will run the same exact command for each item which is meaningless. What is the value of csr.files?
I think you are fundamentally misunderstanding loops, they run the shell command multiple times, once per item, templating and using the current item each time. Since it is completely skipped you either have an empty list or your when condition is returning False, as has already been pointed out.
In general also adding -vvv output would be helpful in debugging your issues.
All I’m asking is how to pass two variables into a shell command and loop that command through all the directories. I can find many working examples of where this is done with only one variable, but how does the play change when it’s two? I can’t find any examples of that.
My playbook is on a closed network so I can’t spend too much time typing the whole thing out here. Most of it is pretty easy. Find the host directories inside a directory on the CA server called “certdir”. I register all three of those as variables ( out_dir, out_csr, and out_req). For a sanity check I put a debug/msg print statement in the play just so I can see on-screen that yes, ansible understands that there are multiple host directories, and it shows me the files in the form - host.our-domain.csr and host.our-domain.key It does that just fine.
All that was simple.
But how do you get Ansible to understand passing two of the above variables in a command like:
openssl ca -batch -config $out_req -extensions server_cert -days 730 -notext -md sha256 -in $out_csr -out $out_dir.crt ( I understand that Ansible doesn’t use $variable, that is a bash form of variable, I just typed it like that for simplicity).
I guess it’s actually 3 variables since I need the cert to pick up the name of the host.
I’m close to just giving up on this part of the playbook and hand-jamming that above command. The playbook will still save time moving files around, creating an rsyslog.conf from template and pushing that out to all the workstations, and restarting the service.
It’s actually not that bad if it’s just one variable:
- name: Find CSR files
ansible.builtin.find:
paths: "{{ playbook_dir }}"
file_type: file
patterns: '*.csr'
recurse: yes
register: csr
- name: Set file names into new variable
set_fact:
CSR: "{{ csr.files | map(attribute='path') | map('basename') | list }}"
- name: Print CSR variable
debug:
msg: "Print result of CSR search: {{ CSR }}"
- name: Test variable usage against the found CSR files
ansible.builtin.shell:
cmd: "openssl req -text -noout -verify -in {{ item }}"
loop: "{{ CSR }}"
But the above was just to learn about loops to see if a shell command would act on a varibale, and it did. I did add a statement to print the output of the openssl verify
There is no difference between using 1 or 100 variables, also you don’t pass variables, you template (on the ansible machine) using variables and pass the resulting action to the target. You are using a single variable {{dir}} not 2 or 3. Also I don’t see how you create dir, but i suspect you just need a vars entry:
- name: create crt file
ansible.builtin.shell: openssl ca -batch -config {{ dir }}.our-domain.req -extensions server_cert -days 720 -notext -md sha256 -in {{ dir }}.our-domain.csr -out {{ dir }}.dgs.mil.crt
loop: “{{ csr.files | map(attribute=‘path’) | list }}”
vars:
dir: '{{item|dirname}}' # or whatever dir is supposed to be PER item, having the same across all items keeps it as a the same command N times
That aside from your skip issue, which I cannot help without more information.