Ansible how to find out dictionary out of list of dictionaries

1.5k Views Asked by At
$ more defaults/mail.yaml

---
envs:
  - dev:
      acr-names:
        - intake.azurecr.io
        - dit.azurecr.io
        - dev.azurecr.io
      subscription-id: xxx

  - uat:
      acr-names:
        - stagreg.azurecr.io
      subscription-id: yyy

  - prod:
      acr-names:
        - prodreg.azurecr.io
      subscription-id: zzz

I want to write a ansible play to copy the image between registries in azure https://learn.microsoft.com/en-us/azure/container-registry/container-registry-import-images#import-from-a-registry-in-a-different-subscription

The play should accept 2 parameters. source_image and target_image, so the play will import the image from source to destination

For Ex:

ansible-playbook sync-docker-image.yml -e source_image=dit.azurecr.io/repo1:v1.0.0.0 -e target_image=stagreg.azurecr.io/stage-repo:latest

2 questions:

  1. Here how can I find out the which env(dev,uat or prod) the source_image or target_image belongs to in ansible playbook, based on env, I want to choose the subscription-id. So from the above example, I want to create 2 variables called source_subscription and target_subscription and assign them to dev, uat subscriptions respectively.

  2. In YAML, is it possible to access a variable in list of dictionaries based on key, for example something like envs[dev]?

Thanks

1

There are 1 best solutions below

0
On

First - if possible - when you only have the three stages, don't use a list of dict items in envs. I asume they are already named, so use:

envs:
  dev:
    acr-names:
      - ...
    subscription-id: xxx
  uat:
    acr-names:
      - ...
    subscription-id: yyy
  prod:
    acr-names:
      - ...
    subscription-id: zzz

This would make it easier to access the stages via envs.dev or envs.uat etc. So you need to iterate only over envs.dev.acr-names (maybe use _ instead of -, otherwise you'll get in trouble later). Inside the iteration you can use the when condition to check the item against your source:

- name: "Facts"
  set_fact:
    envs:
      dev:
        acr_names:
          - intake.azurecr.io
          - dit.azurecr.io
          - dev.azurecr.io
        subscription_id: xxx
      uat:
        acr_names:
          - stagreg.azurecr.io
        subscription_id: yyy
      prod:
        acr_names:
          - prodreg.azurecr.io
        subscription_id: zzz
    source_image: "dit.azurecr.io/repo1:v1.0.0.0"
    target_image: "stagreg.azurecr.io/stage-repo:latest"

- name: "Identify source subscription"
  set_fact:
    source_subscription: "{{ envs.dev.subscription_id }}"
  when:
    - "item in source_image"
    - "source_subscription is undefined"
  loop: "{{ envs.dev.acr_names }}"

If it isn't possible to change the dict (because you have "many"), you need to iterate over the items in envs. If possible, do not create "random" keys but use "name"d item. So a structure like this would be better

envs:
  - name: dev
    acr_names:
      - ...
    subscription_id: xxx
  - name: uat
    acr_names:
      - ...
    subscription_id: yyy
  ...

So you iterate over the items in envs and then iterate over item.acr_names to find your system. This is more complicated, because you loop over a list and iterate then over items in that list. I think, this isn't possible with one single task. But with the given structure the problem is - the string in source_target is not exactly what is in acr_names. So remove anything after the slash and then you can use a different method to search for a string in a list.

- name: "Identify source subscription"
  set_fact:
    source_subscription: "{{ env.subscription_id }}"
  when:
    - "source_image.split('/')[0] in env.acr_names"
    - "source_subscription is undefined"
  loop: "{{ envs }}"
  loop_control:
    loop_var: env

You could also use the split filter in the first example without looping over envs.dev etc.

- name: "Show result"
  set_fact:
    source_subscription: "{{ envs.dev.subscription_id }}"
  when:
    - "source_image.split('/')[0] in envs.dev.acr_names"

If you really need to use your given structure, then you need to iterate over the envs. It countains a dictionary with a random key as root element. That makes it very complicated. In that case you need to loop over it, include a separate tasks file with include_tasks and inside that tasks list, you need the filter lookup('dict',env) to get a special dict and you can access item.keyanditem.value.acr_namesanditem.value.subscription_id` to access the values inside the dict. I wouldn't recommend that.

- name: "Identify source subscription"
  include_tasks: find_env.yml
  loop: "{{ envs }}"
  loop_control:
    loop_var: env

and find_env.yml contains:

- name: "Show result"
  set_fact:
    source_subscription: "{{ env[item.key].subscription_id }}"
  when:
    - "source_image.split('/')[0] in env[item.key].acr_names"
    - "source_subscription is undefined"
  loop: "{{ env | dict2items }}"

All of this must be done twice for source and target.