Ansible: How to iterate through the list of dictionaries using Jinja - ansible

I get stuck and hope you can help me
I want to build file based on the following yml file with vars:
---
users:
- name: "user1"
db:
- name: "main"
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
schema:
- name: "public"
owner: no
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
- name: "notpublic"
owner: no
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
- name: "user2"
db:
- name: "main2"
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
schema:
- name: "public"
owner: no
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
- name: "nonpublic"
owner: no
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
How can I iterate through declared schema list in the Jinja template?
If I'm using the following construct
{% for user in users %}
{% for userdb in user.db %}
{% for s in userdb.schema %}
{{ s.name }}
{% endfor %}
{% endfor %}
{% endfor %}
I get the following error during the execution:
FAILED! => {"changed": false, "msg": "AnsibleUndefinedVariable: 'dict object' has no attribute 'schema'"}

The following playbook f.yml
---
- hosts: localhost
connection: local
gather_facts: False
vars:
users:
- name: "user1"
db:
- name: "main"
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
schema:
- name: "public"
owner: no
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
- name: "notpublic"
owner: no
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
- name: "user2"
db:
- name: "main2"
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
schema:
- name: "public"
owner: no
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
- name: "nonpublic"
owner: no
default_privileges:
tables: ['ALL']
sequences: ['ALL']
functions: ['EXECUTE']
types: ['USAGE']
tasks:
- name: templating
template:
src: "f.jj"
dest: "f.txt"
with this template f.jj
{% for user in users %}
{% for userdb in user.db %}
{% for s in userdb.schema %}
{{ s.name }}
{% endfor %}
{% endfor %}
{% endfor %}
produces with
$ ansible-playbook f.yml
PLAY [localhost] ***************************************************************
TASK [templating] **************************************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0
the output file f.txt
public
notpublic
public
nonpublic

Related

how to generate list arguments of ansible task with Jinja2 template?

ansible theforeman.foreman.content_view module has repositories arguments need to be used like this:
- name: "Create or update Fedora content view"
theforeman.foreman.content_view:
username: "admin"
password: "changeme"
server_url: "https://foreman.example.com"
name: "Fedora CV"
organization: "My Cool new Organization"
repositories:
- name: 'Fedora 26'
product: 'Fedora'
I need to generate it dynamically. tried to do like following:
- name: "Create or update content views"
theforeman.foreman.content_view:
username: "{{ foreman.user }}"
password: "{{ foreman.password }}"
server_url: "{{ foreman.url }}"
organization: "{{ org.name }}"
name: "{{ item.0.name }}_content"
repositories: |
- name: '{{ item.1.name }}'
product: "{{ item.0.name }}_repos"
loop: "{{ os|subelements('repos') }}"
loop_control:
label: "{{ item.0.name }}"
this also not worked
- name: "Create or update content views"
theforeman.foreman.content_view:
username: "{{ foreman.user }}"
password: "{{ foreman.password }}"
server_url: "{{ foreman.url }}"
organization: "{{ org.name }}"
name: "{{ item.0.name }}_content"
repositories: |
{% for n in item.0.name %}
- name: '{{ item.1.name }}'
product: "{{ item.0.name }}_repos"
{% endfor %}
loop: "{{ os|subelements('repos') }}"
loop_control:
label: "{{ item.0.name }}"
but it always overwrites previous one and last executed remains. Howcould we achive to generate repositories with loop?
example vars:
os:
- name: alma9
repos:
- name: "almalinux9_base_x86_64"
url: "https://repo.almalinux.org/almalinux/9/BaseOS/x86_64/os/"
gpg_key: "RPM-GPG-KEY-AlmaLinux9"
- name: "almalinux9_appstream_x86_64"
url: "https://repo.almalinux.org/almalinux/9/AppStream/x86_64/os/"
gpg_key: "RPM-GPG-KEY-AlmaLinux9"

How would I configure a dictionary which requires variables in a loop?

I have a docker_container which I want to deploy for multiple users and name traefik routes after the users. But i'm confused on how I can achieve this.
Here is what I have:
- name: Run syncthing
docker_container:
name: "{{ item.name }}-syncthing"
image: "lscr.io/linuxserver/syncthing"
state: started
restart_policy: "always"
env:
PUID: "1000"
PGID: "1000"
volumes:
- "{{ item.data_dir }}:/data"
... other volumes
labels:
traefik.enable: true
"traefik.http.routers.{{ item.name }}-syncthing.entrypoints": websecure
"traefik.http.routers.{{ item.name }}-syncthing.rule": Host(`{{ item.name }}.{{ fqdn_real }}`)
"traefik.http.routers.{{ item.name }}-syncthing.tls": true
"traefik.http.routers.{{ item.name }}-syncthing.tls.certresolver": le
"traefik.http.routers.{{ item.name }}-syncthing.service": "{{ item.name }}-syncthing"
"traefik.http.routers.{{ item.name }}-syncthing.middlewares": "{{ item.name }}-basicauth"
"traefik.http.services.{{ item.name }}-syncthing.loadbalancer.server.port": 8080
"traefik.http.middlewares.{{ item.name }}-syncthing-basicauth.basicauth.users": "{{ item.auth }}"
with_items: "{{ syncthing_containers_info }}"
And a syncthing_config_info like this:
syncthing_containers_info:
- { name: "c1", data_dir: "/mnt/data/c1/data", auth: "..." }
- { name: "c2", data_dir: "/mnt/data/c2/data", auth: "..." }
- { name: "c3", data_dir: "/mnt/data/c3/data", auth: "..." }
That snippet doesn't work because ansible doesn't like the syntax so I have tried this with a with_nested but I faced a similar problem there with the nested loop issue while trying to set_fact as in the example since the set of labels depends on syncthing_containers_info. Is there a better way for me to do this?
It sounds like you need the labels: to be an actual dict since yaml keys are not subject to jinja2 interpolation
labels: >-
{%- set key_prefix = "traefik.http.routers." ~ item.name ~"-syncthing" -%}
{{ {
"traefik.enable": True,
key_prefix ~ ".entrypoints": "websecure",
key_prefix ~ ".rule": "Host(`" ~ item.name ~"."~ fqdn_real ~"`)",
key_prefix ~ ".tls": True,
key_prefix ~ ".tls.certresolver": "le",
key_prefix ~ ".service": item.name ~ "-syncthing",
key_prefix ~ ".middlewares": item.name ~ "-basicauth",
"traefik.http.services." ~ item.name ~ "-syncthing.loadbalancer.server.port": 8080,
"traefik.http.middlewares." ~ item.name ~"-syncthing-basicauth.basicauth.users": item.auth,
} }}
(be aware I didn't test that, just eyeballed it from your question, but that's the general idea)

check if all loop items have been skipped

Is there a way to check if all loop items from a previous step have been skipped?
I want to download the latest files from the GitHub API and compare them to the templated ones. Only the files that have changed will be commited as blobs. Based on the skipped property, I can check what needs to be included in the tree.
But how can I skip the Create tree action when all items in blobs have been skipped?
when: "not skipped(blobs)" in Create tree only for illustration purposes.
- name: Download current files
uri:
url: https://github.com/api/v3/repos{{ repository.path }}/contents{{ item.path }}
user: "{{ API_USER }}"
password: "{{ API_TOKEN }}"
force_basic_auth: yes
status_code: [ 200, 404 ]
register: current_files
loop:
- {path: "/.github/workflows/build.yml"}
- {path: "/.github/workflows/deploy-dev.yml"}
- {path: "/.github/workflows/deploy-int.yml"}
- {path: "/.github/workflows/deploy-prod.yml"}
- name: Commit file blob
uri:
url: https://github.com/api/v3/repos{{ repository.path }}/git/blobs
method: "POST"
user: "{{ API_USER }}"
password: "{{ API_TOKEN }}"
force_basic_auth: yes
body_format: json
status_code: [200, 201]
body: |
{
"encoding": "base64",
"content": "{{ lookup('template', item.src) | b64encode }}",
}
vars:
target_group: "{{ item.target_group }}"
service_deployment_env: "{{ item.service_deployment_env }}"
when: "(lookup('template', item.src) | b64encode) != current_files['results'][index]['json']['content'].replace('\n', '')"
register: blobs
loop:
- { src: 'build.yml.jinja2', dest: '.github/workflows/build.yml', service_deployment_env: '' }
- { src: 'deploy.yml.jinja2', dest: '.github/workflows/deploy-dev.yml', service_deployment_env: 'dev' }
- { src: 'deploy.yml.jinja2', dest: '.github/workflows/deploy-int.yml', service_deployment_env: 'int' }
- { src: 'deploy.yml.jinja2', dest: '.github/workflows/deploy-prod.yml', service_deployment_env: 'prod' }
loop_control:
index_var: index
- name: Create tree
uri:
url: https://github.com/api/v3/repos{{ repository.path }}/git/trees
method: "POST"
user: "{{ API_USER }}"
password: "{{ API_TOKEN }}"
force_basic_auth: yes
body_format: json
status_code: [200, 201]
body: "{{ lookup('template', 'create_tree_body.json.jinja2') }}"
when: "not skipped(blobs)" # HOW IS THIS POSSIBLE??
register: tree
Ok... this was too easy. Apparently, ansible resolves the blobs list by itself, so simply using
when: "blobs is not skipped"
works just fine.

Ansible: How to create nested dictionary using Jinja2

Here is the output.
"result.containers":[
{
"Image":"ca.docker/webproxy:1.0.0",
"Names":[
"/customer1"
]
},
{
"Image":"docker.local/egacustomer:1.0.1",
"Names":[
"/registrator"
]
}
]
I'm trying to get the following output using jinja2
"containerlist"=>{
"webproxy": {
"name": "customer1"
},
"egacustomer": {
"name": "registrator"
}
}
Here is my jinja2 code.
- set_fact:
containerlist: |
{
{% for item in result.containers %}
{{ item.Image.split('/')[-1].split(':')[0] | replace('\n', '') }}
name : {{ item.Names[0][1:] | replace('\n', '') }}
{% endfor %}
}
I get the below output.
"containerlist": "{\nwebproxy\n name : customer1\negacustome\n name : registrator\n}"
Could someone please help me get the desired output. Any help would be greatly appreciated
The data in YAML
result:
containers:
- Image: ca.docker/webproxy:1.0.0
Names:
- /customer1
- Image: docker.local/egacustomer:1.0.1
Names:
- /registrator
The tasks below
- set_fact:
containerlist: "{{ containerlist|default({})|
combine({key: {'name': name}}) }}"
loop: "{{ result.containers }}"
vars:
key: "{{ (item.Image.split(':')|first).split('/')|last }}"
name: "{{ item.Names[0][1:] }}"
- debug:
var: containerlist
give
containerlist:
egacustomer:
name: registrator
webproxy:
name: customer1
But, the result is not a list. It's a dictionary. If you want a list use this
- set_fact:
containerlist: "{{ containerlist|default([]) +
[{key: {'name': name}}] }}"
loop: "{{ result.containers }}"
vars:
key: "{{ (item.Image.split(':')|first).split('/')|last }}"
name: "{{ item.Names[0][1:] }}"
- debug:
var: containerlist
give
containerlist:
- webproxy:
name: customer1
- egacustomer:
name: registrator

Creating dictionary with same values in all elements

I have dictionary like this:
abc:
xyz1:
url: "{{ url }}"
api_key: "{{ key }}"
xyz2:
url: "{{ url }}"
api_key: "{{ key }}"
xyz3:
url: "{{ url }}"
api_key: "{{ key }}"
I know that all xyz{number} have the same values in url and api_key and will have same values in the future.
I need this nested format, but what will be more elegant way to write this instead the way I did it (which will be quite long with few more elements)?
Create template
shell> cat myvars.yml.j2
abc:
{% for index in range(1, size+1) %}
xyz{{ index }}:
url: "{{ '{{' }} url {{ '}}' }}"
api_key: "{{ '{{' }} key {{ '}}' }}"
{% endfor %}
The playbook
- hosts: localhost
vars:
size: 3
key: mykey
url: myurl
tasks:
- template:
src: myvars.yml.j2
dest: myvars.yml
- include_vars: myvars.yml
- debug:
var: abc
will create file myvars.yml
shel> cat myvars.yml
abc:
xyz1:
url: "{{ url }}"
api_key: "{{ key }}"
xyz2:
url: "{{ url }}"
api_key: "{{ key }}"
xyz3:
url: "{{ url }}"
api_key: "{{ key }}"
and debug will display the included variable
"abc": {
"xyz1": {
"api_key": "mykey",
"url": "myurl"
},
"xyz2": {
"api_key": "mykey",
"url": "myurl"
},
"xyz3": {
"api_key": "mykey",
"url": "myurl"
}
}

Resources