0

In my ansible I need to make a rest call to get a rather large json object (which comes in as a dict). Then I need to modify a few fields in the json and repost it to update the status of the components I want to change.

I already have the rest response and I'm trying to figure out how to cleanly modify numerous values. My current attempt looks like this (simplified a little):

- name: update locations for remote processor
  set_fact:
    request: "{{ response.json | combine(item)}}"
  loop:
    - { 'component': { 'targetUri': "{{remoteProcessorUri}}"
    - { 'component': { 'targetUris': "{{remoteProcessorUri}}"
    ...

Unfortunately while this does change the request, it replaces the larger component dict with an dict that only contains targetUri, all the other content that was in component was erased where as I want to keep it and only modify targetUri.

I tried a variant where my loop had a location and a value field for each item, but I can't get the location field syntax right to be able to replace response.json with item.location.

So how can I create this to make it easy, and readable, to make various changes across my dict without changing anything other then the specific subfields I call out?

dsollen
  • 6,046
  • 6
  • 43
  • 84

2 Answers2

1

Turns out I had it almost correct. All I needed was to add the recursive=true option to the combine filter. so...

- name: update locations for remote processor
  set_fact:
    request: "{{ response.json | combine(item, recursive=True)}}"
  loop:
    - { 'component': { 'targetUri': "{{remoteProcessorUri}}"
    - { 'component': { 'targetUris': "{{remoteProcessorUri}}"
    ...
dsollen
  • 6,046
  • 6
  • 43
  • 84
0

Dictionaries are "live" in ansible/jinja2, in that you can modify them in-place using most of the python dict methods, with the only caveat that you have to echo the whole structure back out in a set_fact: to "save" the changes

  tasks:
  - set_fact:
      example1:
        {
          "some": {
            "key": {
              "ansible_python": {
                "executable": "/usr/local/opt/python@3.9/bin/python3.9",
                "has_sslcontext": true,
                "type": "cpython",
                "version": {
                  "major": 3,
                  "micro": 5,
                  "minor": 9,
                  "releaselevel": "final",
                  "serial": 0
                },
                "version_info": [
                  3,
                  9,
                  5,
                  "final",
                  0
                ]
              }
            }
          }
        }
  - set_fact:
      example1: >-
        {%- set _ = example1.some.key.ansible_python.version.update({
          "a_new_key": "yup",
          "mojor": 9999,
          }) -%}
        {{ example1 }}
  - debug:
      var: example1.some.key.ansible_python.version

produces

ok: [localhost] => {
    "example1.some.key.ansible_python.version": {
        "a_new_key": "yup",
        "major": 3,
        "micro": 5,
        "minor": 9,
        "mojor": 9999,
        "releaselevel": "final",
        "serial": 0
    }
}

That {% set _ = ... %} business is because the jinja2 versions bundled with ansible don't seem to support the do operator from jinja2, so the only way to get a method with side-effects is to have it be on the right side of an assignment operator, and just throw away the result

mdaniel
  • 31,240
  • 5
  • 55
  • 58
  • It's not as pretty s I would like, but it does seem to work. However, this only allows me to change values in component. I need to be able to change values across various dicts in response. Can this approach work with a loop where I specify a location and a value in the loop and use item.location and item.value in the jinja2? – dsollen Aug 18 '21 at 16:51
  • yes, of course; anything jinja can express so you can script; it's basically python just with much more annoying `{%- -%}` wrappers around each statement; that said, I consider it a _grave_ antipattern to have `loop:` keywords and jinja bodies, since jinja is perfectly capable of looping by itself and pushing the iteration into the body keeps ansible from **repeatedly** serializing and deserializing those tasks to the target host. It'll be unquestionably faster and less log-chatty – mdaniel Aug 18 '21 at 20:26