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

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)

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"

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.

Jinja2 nested varibles

Below are my jinja2 template file and the variables used to populate it. However I want to include a new section only if aditional_keys = true. Is this possible?
My variable
- { name: 'container1', version: '1.0.0.0', port: '', registry_path: 'container1', replicas: '1', namespace: 'general', aditional_keys: 'false'}
- { name: 'container2', version: '3.6.14.1', port: '8080', registry_path: 'container2', replicas: '1', namespace: 'general', aditional_keys: 'true'}
My template
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: {{ item.name }}
environment: {{ location }}_{{ env }}
name: {{ item.name }}
namespace: "{{ item.namespace }}"
spec:
replicas: {{ item.replicas }}
selector:
matchLabels:
app: {{ item.name }}
environment: {{ location }}_{{ env }}
template:
metadata:
labels:
app: {{ item.name }}
environment: {{ location }}_{{ env }}
spec:
containers:
- envFrom:
- configMapRef:
name: {{ item.name }}
image: registry.com/{{ item.registry_path }}:{{ item.version }}
imagePullPolicy: Always
name: {{ item.name }}
ports:
- containerPort: {{ item.port }}
protocol: TCP
I tried adding this but I am obviously not calling the variable correctly
{% if item.additional_keys == true %}
env:
- name: PRIVATE_KEY
valueFrom:
secretKeyRef:
key: id_{{ item.name }}_rsa
name: id-{{ item.name }}-rsa-priv
optional: false
- name: PUBLIC_KEY
valueFrom:
secretKeyRef:
key: id_{{ item.name }}.pub
name: id-{{ item.name }}-rsa-pub
optional: false
{% else %}
{% endif %}
To start with, literal compare to boolean values is one of the ansible-lint rules you might want to follow.
Now there are 2 real issues in your above example.
You have a typo in your variable definition (aditional_keys) while you spelled it correctly in your template (additional_keys)
Your variable is specified as a string ('false') while you expect a boolean (false). Meanwhile, it often happens in ansible that correct boolean values can be turned into strings upon parsing (e.g. extra_vars on the command line). To overcome this, the good practice is to systematically transform the value to a boolean with the bool filter when you don't totally trust the source.
Once your fix the variable name and boolean definition in your var file as additional_keys: false, the following conditional in your template will make sure you don't get into that trouble again:
{% if item.additional_keys | bool %}

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