2

[First time questioner. I think that I've targeted this as an Ansible question. If not, gentle redirection is welcome!]

Given:

gid: 80
ports: [80, 443]

where the number of ports may vary from 0 to many

I'd like to produce a string like this:

"gid:80:tcp:80,gid:80:tcp:443"

(which happens to be a FreeBSD mac_portacl rule string)

The furthest I've gotten is:

portacl_rules: "{{ ports | zip_longest([], fillvalue='80') | list }}"

Which gives me somethign like this:

    "msg": [
        [
            80,
            "80"
        ],
        [
            443,
            "80"
        ]
    ]

but:

  • the gid is hardcoded, I can't figure out how to interpolate the variable value; and
  • I can't translate the list of into the final string.

I can create the gid string, gid:80 by defining a temporary variable:

gid: 80
_tmp_gid: "gid:{{ gid }}"

but since I can't interpolate a string into the fillvalue, I'm stuck.

I monkeyed around the format filter, but it appears to take the output string as its input and the values as its arguments, which is the inverse of my situation.

Any suggestions?

hartzell
  • 93
  • 6
  • Thanks for the solution. The two-step answer works, but the single step does not (in my hands...), it gives ` "rules_str_2": "gid:80:tcp:80gid:80:tcp:,gid:80:tcp:443gid:80:tcp:,gid:80:tcp:987gid:80:tcp:"`. I can't figure out how to do a longer reply that includes all fo the info.... – hartzell May 24 '19 at 18:46

1 Answers1

0

If you don't mind pair of set_fact tasks, you can do it like this:

- set_fact:
    rules_list: "{{ rules_list|default([]) + ['gid:{}:tcp:{}'.format(gid, item)] }}"
  loop: "{{ ports }}"

- set_fact:
    rules_str_1: "{{ ','.join(rules_list) }}"

- debug:
    var: rules_str_1

The first task creates a list of the form:

[
  "gid:80:tcp:80",
  "gid:80:tcp:443"
]

The second task joins those items using ,.

You can complete that in a single operation using a slightly hairier expression involving the regex_replace filter:

- set_fact:
    rules_str_2: '{{ ",".join(ports|map("regex_replace", "^(.*)$", "gid:{}:tcp:\1".format(gid))) }}'

- debug:
    var: rules_str_2

For that set_fact task to work as written, you must use single quotes on the outside (this inhibits the use of \ as an escape character). You could swap the quotes, but then you would need to write \\ instead of \. Recall that (...) in the match expression creates a capture group, and \1 in the replacement string expands to the value of the first capture group.


Putting it all together in a playbook:

---
- hosts: localhost
  gather_facts: false
  vars:
    gid: 80
    ports: [80, 443]
  tasks:
    - set_fact:
        rules_list: "{{ rules_list|default([]) + ['gid:{}:tcp:{}'.format(gid, item)] }}"
      loop: "{{ ports }}"

    - set_fact:
        rules_str_1: "{{ ','.join(rules_list) }}"

    - debug:
        var: rules_str_1

    - set_fact:
        rules_str_2: '{{ ",".join(ports|map("regex_replace", "(.*)", "gid:{}:tcp:\1".format(gid))) }}'

    - debug:
        var: rules_str_2

Which will yield the following output:

PLAY [localhost] ******************************************************************************************************************************************************************************

TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost] => (item=80)
ok: [localhost] => (item=443)

TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]

TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
    "rules_str_1": "gid:80:tcp:80,gid:80:tcp:443"
}

TASK [set_fact] *******************************************************************************************************************************************************************************
ok: [localhost]

TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => {
    "rules_str_2": "gid:80:tcp:80,gid:80:tcp:443"
}

PLAY RECAP ************************************************************************************************************************************************************************************
localhost                  : ok=5    changed=0    unreachable=0    failed=0
larsks
  • 277,717
  • 41
  • 399
  • 399
  • See my comment w/ my original question, the two step version works, the single step version doesn't work for me. – hartzell May 25 '19 at 15:46
  • Try the updated version (I had to anchor the regex so it now reads `^(.*)$`). – larsks May 25 '19 at 16:51
  • Ah, I see. You edited the prose, but the "Putting it all together in a playbook" example still contains the older version (which is what I was cut and pasting). With the anchored regexp, works great. Thanks again!!! – hartzell May 26 '19 at 17:15