Passing multiple file types in a shell command through a loop

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.

Can someone provide some advice?

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.

I’m creating dir in the following manner:

  • name: Playbook
    hosts: CA
    become: yes
    gather_facts: no
    vars:
    cert_dir: /etc/ws_certs
    csr: “{{out_csr.files | map(attribute=‘path’ | map(‘basename’) }}”
    dir: “{{out_dir.files | map(attribute=‘path’ | map(‘basename’) }}”
    req: “{{out_req.files | map(attribute=‘path’ | map(‘basename’) }}”
    crt: “{{out_crt.files | map(attribute=‘path’ | map(‘basename’) }}”

    tasks:
    - name: Find host dirs
    ansible.builtin.find:
    path: “{{ cert_dir }}”
    file_type: directory
    register: out_dir

      - name: print out_dir
        debug:
           var: dir
    
      - name: find crt files
        find: 
           path: "{{ cert_dir }}"
           patterns: "*.our-domain.crt"
           file_type: file
            recurse: true
        register: out_crt
    
       - name: print out_crt
        debug:
           var: crt
    
    Repeat the two above plays for out_req and out_csr

I wanted to register cert because I want to add a when statement to not run the openssl command if a cert if found in cert_dir