How to regex replace nested values in Ansible

2k views Asked by At

This question is about looping in Ansible, not about AWS, but for the sake of clarity I will use an AWS deployment problem as an example.

For our deployment scripts I am trying to loop over some clusters in the Amazon EC2 container service. What I will ultimately do is restart each service on the cluster. I am able to restart a service, given it's name. However I need the simple name, not the fully qualified ARN. So I look up the services per cluster and get something like this:

results:
- _ansible_item_result: true
  _ansible_no_log: false
  _ansible_parsed: true
  ansible_facts:
    services:
    - arn:aws:ecs:eu-central-1:55:service/test-services
  changed: false
  failed: false
  invocation:
    module_args:
      aws_access_key: null
      aws_secret_key: null
      cluster: services
      details: false
      ec2_url: null
      profile: null
      region: null
      security_token: null
      service: null
      validate_certs: true
  item: services
- _ansible_item_result: true
  _ansible_no_log: false
  _ansible_parsed: true
  ansible_facts:
    services:
    - arn:aws:ecs:eu-central-1:55:service/test-service
    - arn:aws:ecs:eu-central-1:55:service/frontend
    - arn:aws:ecs:eu-central-1:55:service/beats
  changed: false
  failed: false
  invocation:
    module_args:
      aws_access_key: null
      aws_secret_key: null
      cluster: test-service
      details: false
      ec2_url: null
      profile: null
      region: null
      security_token: null
      service: null
      validate_certs: true
  item: test-service    module_args:
  aws_access_key: null
  aws_secret_key: null
  cluster: test-service
  details: false
  ec2_url: null
  profile: null
  region: null
  security_token: null
  service: null
  validate_certs: true

item: test-service

Now I want to replace each ARN by the short name of the service. For example: arn:aws:ecs:eu-central-1:55:service/test-service becomes test-service.

After the replacement I can do loop over the services and turn them off by setting the desired count to 0 (later I will turn them back on again):

- name: "Turn services off"
  ecs_service:
    name: "{{ item[1]}}"
    desired_count: 0
    task_definition: "{{ taskdefinitions[item[1]] }}"
    cluster: "{{ item[0].item }}"
    state: present
with_subelements:
    - "{{ result.results }}"
    - ansible_facts.services
register: turnOffServiceResult

Where taskdefinitions is a simple dict I defined in the playbook:

taskdefinitions:
  services:
  - test-services
  test-xde-worker-service:
  - test-service

So after I get the AWS list shown above into a variable result I try to regex replace by doing the following:

- set_fact:
  result:
    results:
      ansible_facts:
        services: "{{ result.results.1.ansible_facts.services | map('regex_replace', '.*/(.*?)$', '\\1' ) | list }}"

This works fine, but it obviously only replaces the service names for one cluster and I lose any other fields in the dict ansible_facts. The latter is acceptable, the former not. So here is the question: how can I replace text in a nested list? Another problem would be to skip turning off the services that are not included in taskdefinitions, but that is not the matter at hand.

1

There are 1 answers

5
Konstantin Suvorov On BEST ANSWER

I'm not aware of any built-in method to modify arbitrary items in complex objects in-place (at least in current Ansible 2.3).

You either select required items from original object (with select, map(attribute=...), json_query, etc) and then modify items in that reduced set/list. In your hypothetical example with JMESPath like result.results[].ansible_facts.services[] to select all services across all clusters and map('regex_replace',... this list.

Or iterate over complex object and apply modification inside a loop, for example:

- name: "Turn services off"
  ecs_service:
    name: "{{ myname }}"
    desired_count: 0
    task_definition: "{{ taskdefinitions[myname] }}"
    cluster: "{{ mycluster }}"
    state: present
  vars:
    mycluster: "{{ item[0].item }}"
    myname: "{{ item[1] | regex_search('[^/]*$') }}"
  with_subelements:
    - "{{ result.results }}"
    - ansible_facts.services