Objective
I am trying to append a string to a list belonging to a key which is a few layers down within a dictionary.
Explanation
I initialize an empty list buried within some pre-existing variable called info:
tasks:
- set_fact:
info: "{{ info | combine( { host: { repo: { folders: [] }}}, recursive=true ) }}"
In a later task, I wish to append a string to that empty list. This may happen over multiple tasks, so I don't want to replace the empty list, but add onto it as needed. I am currently trying this:
tasks:
- set_fact:
info: "{{ info | combine( { 'host': { 'repo': { 'folders': [] }}}, recursive=true ) }}"
- set_fact:
info: "{{ info.host.repo.folders + ['ERROR. folderX does not exist'] }}"
when: folderX does not exist
- set_fact:
info: "{{ info.host.repo.folders + ['ERROR. folderY does not exist'] }}"
when: folderY does not exist
However, I receive a template error:
FAILED! => {"msg": "template error while templating string: expected name or number. String: {{ info.host.repo.folder + ['ERROR. folderX does not exist'] }}"}
I know that you can simply add elements to a list when the destination is on topmost layer. For example:
- set_fact:
folders: []
- set_fact:
folders: "{{ folders + ['ERROR. folderX does not exist'] }}"
when: folderX does not exist
- set_fact:
folders: "{{ folders + ['ERROR. folderY does not exist'] }}"
when: folderY does not exist
- debug: var=folders
Which, as desired, gives:
TASK [debug] ***************************************************************************************************
"folders": [
"ERROR. folderX does not exist",
"ERROR. folderY does not exist"
]
So, how does the syntax change when I am trying to descend multiple layers and access a list that resides in a nested dictionary? Thank you!
dicts and lists in ansible are "live", so you can update them via set statements
- set_fact:
info: >-
{%- set _ = info.host.repo.update({"folders": []}) -%}
{{ info }}
- set_fact:
info: >-
{%- set _ = info.host.repo.folders.append("ERROR. folderY does not exist") -%}
{{ info }}
when: folderY does not exist
that set _ = business is because ansible's jinja does not support the do statement so one cannot have an assignment statement by itself
Related
Heaving some trouble getting something to work in the argo-helm helm chart.
I am trying to make use of the very conveinet extraObjects value in their argo-cd chart.
Setup
values.yaml
extraObjects:
- apiVersion: v1
kind: ConfigMap
metadata:
labels: "{{ include `argo-cd.labels` (dict `context` . `component` `configmap` `name` `some-config-map`) | nindent 4 | trim }}"
name: some-config-map
data:
something: something
template/extra-manifests.yaml
{{ range .Values.extraObjects }}
---
{{ tpl (toYaml .) $ }}
{{ end }}
Result
sadly it will produce
# Source: argo-cd/templates/extra-manifests.yaml
apiVersion: v1
data:
something: something
kind: ConfigMap
metadata:
labels: '
helm.sh/chart: argo-cd-5.19.12
app.kubernetes.io/name: some-config-map
app.kubernetes.io/instance: argocd
app.kubernetes.io/component: configmap
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/part-of: argocd'
name: some-config-map
---
The problem
This is unsurprising really. We put the template into a string so it makes sense that we see a string with the templated content.
I want the template to "float" on the labels field. But I can't do that because {{ blablaba }} isnt a vaild yaml object.
(Or it is but {} is object notation)
Because they are using the toYaml function to turn a valid YAML object into a string for use in the tpl function, I can't represent the template any other way other than using the string.
What I've tried
Comment Trick?
labels: # {{ include `argo-cd.labels` (dict `context` . `component` `configmap` `name` `some-config-map`) | nindent 4 | trim }}
Thought becuse of the nindent it could insert the labels under the comment but it must be striped by toYaml or helm when the values used
Yaml !!map
labels: !!map |
{{ include `argo-cd.labels` (dict `context` . `component` `configmap` `name` `some-config-map`) | nindent 4 | trim }}
and
labels: !!map |
{{ include `argo-cd.labels` (dict `context` . `component` `configmap` `name` `some-config-map`) | fromYaml }}
I don't think yaml has built in conversion but it was worth a shot.
Yaml !!yaml
Tried to play around with !!yaml but I don't understand what it's supposed to do.
Tried this:
labels:
!!yaml: '!' : '!map'
!!value = : {{ include `argo-cd.labels` (dict `context` . `component` `configmap` `name` `some-config-map`) | nindent 4 | trim }}
and
labels:
!!yaml: '!' : '!map'
!!value = : {{ include `argo-cd.labels` (dict `context` . `component` `configmap` `name` `some-config-map`) | fromYaml }}
Question
Is there any valid YAML notation or object I can use to pass the template through toYaml without using a string?
Is there anyway to represent {{ blabla }} in YAML without using a string?
The Correct Way
Obviously doing away with toYaml and using strings will result in the expected behavior
extraObjects:
- |
apiVersion: v1
kind: ConfigMap
metadata:
labels:
{{ include `argo-cd.labels` (dict `context` . `component` `configmap` `name` `some-config-map`) | nindent 4 | trim }}
name: some-config-map
data:
something: something
{{ range .Values.extraObjects }}
---
{{ tpl . $ }}
{{ end }}
Before I open an issue with the Argo-CD Helm team I just want to know if there is any workaround?
I am trying to following but I am getting this error "The conditional check 'item.vrf = 'default'' failed. The error was: template error while templating string: expected token 'end of statement block', got '='. String: {% if item.vrf = 'default' %} True {% else %} False {% endif %}" how to fix it?
proc_vrf: [{''proc'': ''T1'', ''vrf'': ''default''}, {''proc'': ''T2'', ''vrf'': ''vrf_T2''}, {''proc'': ''T3'', ''vrf'': ''default''}, {''proc'': ''T3'', ''vrf'': ''vrf_T3''}]
- name: Shut ospf for default vrf
cisco.nxos.nxos_config:
lines:
- shutdown
parents: router ospf {{ item.proc }}
save_when: modified
when: item.vrf|lower == 'default'
with_items: "{{ proc_vrf }}"
- name: Shut ospf for other vrf
cisco.nxos.nxos_config:
lines:
- shutdown
parents: router ospf {{ item.proc }}; vrf {{ proc_vrf }}
save_when: modified
when: item.vrf|lower != 'default'
with_items: "{{ proc_vrf }}"
There was an operator error. corrected comparison operator, ==
I'm facing the following problem set in writing an Ansible playbook:
A list is given to me:
rooms:
- room_name: bedroom-1
chairs: 1
- room_name: bedroom-2
chairs: 0
- room_name: bathroom
chairs: 0
- room_name: kitchen
chairs: 4
And I need to transform that into a list of dictionaries with these two keys per item:
chair_name: <room_name>-chair-<chair_number>
room_name: <room_name>
With the example above that would give me:
chair_names:
- chair_name: bedroom-1-chair-1
room_name: bedroom-1
- chair_name: kitchen-chair-1
room_name: kitchen
- chair_name: kitchen-chair-2
room_name: kitchen
- chair_name: kitchen-chair-3
room_name: kitchen
- chair_name: kitchen-chair-4
room_name: kitchen
I've been struggling to accomplish this using Ansible. Is there a way to do it?
Use Jinja to create the structure
chair_names_str: |-
{% for room in rooms %}
{% for i in range(1, room.chairs + 1) %}
- chair_name: {{ room.room_name }}-chair-{{ i }}
room_name: {{ room.room_name }}
{% endfor %}
{% endfor %}
chair_names: "{{ chair_names_str|from_yaml }}"
gives
chair_names:
- {chair_name: bedroom-1-chair-1, room_name: bedroom-1}
- {chair_name: kitchen-chair-1, room_name: kitchen}
- {chair_name: kitchen-chair-2, room_name: kitchen}
- {chair_name: kitchen-chair-3, room_name: kitchen}
- {chair_name: kitchen-chair-4, room_name: kitchen}
Q: "Is it possible to achieve the same results using Ansible loops?"
A: Yes. It is. In the first loop create lists of the chairs
- set_fact:
chairs: "{{ chairs|d([]) + [{'chairs': range(1, item|int + 1)|list}] }}"
loop: "{{ rooms|map(attribute='chairs')|list }}"
gives
chairs:
- chairs: [1]
- chairs: []
- chairs: []
- chairs: [1, 2, 3, 4]
In the second loop create the list. The task below gives the same result
- set_fact:
chair_names: "{{ chair_names|d([]) + [_item] }}"
with_subelements:
- "{{ rooms|zip(chairs)|map('combine') }}"
- chairs
vars:
_item:
chair_name: "{{ item.0.room_name }}-chair-{{ item.1 }}"
room_name: "{{ item.0.room_name }}"
You can see that the first option is much simpler.
I am trying to associate users in a aws-auth config map using Helm. I'd like to loop through a nested map in our values file. My attempt to do this is as follows:
mapUsers: |
{{- range $username := .Values.users }}
- groups:
- system:masters
userarn: {{ $username.adminArn }}
username: {{ $username }}
{{- end }}
And the values file is as follows:
users:
username: user.name
userArn: user/Arn
adminArn: user/AdminArn
I'm not certain if this will solve my problem and would like some feedback.
After taking feedback from #Andrew, I was able to get this to work by first changing the structure of my values file to:
users:
testuser:
username: test.user
userArn: testuser/arn
adminArn: testuser/adminArn
I then was able to update my loop to:
{{- range $k := .Values.users }}
- groups:
- system:masters
userarn: {{ .adminArn }}
username: {{ .username }}
{{- end }}
I would like to iterate over a variable in ansible yaml and add key and value in jinja template
variable:
my:
variable:
- name: test
path: /etc/apt
cert: key.crt
my template
{% for key, value in item() %}
{{key}}: {{value}}
{% endfor %}
ansible yaml
- name: test
template:
force: yes
src: test.conf.j2
dest: /tmp/test.conf"
become: yes
with_items:
- "{{ my.variable }}"
How my yaml should look like:
path: /etc/apt
cert: key.crt
You actually have three issues in your task:
When using a loop, may it be loop or all the flavour of with_* you access the element currently looped in with the variable item, so not a function like you used in your task (item())
You are doing a superfluous list of list in
with_items:
- "{{ my.variable }}"
A first step would be to do with_items: "{{ my.variable }}".
An ever better step would be to use the loop replacement of the with_* syntax as suggested in the documentation
We added loop in Ansible 2.5. It is not yet a full replacement for with_<lookup>, but we recommend it for most use cases.
So you will end up with
loop: "{{ my.variable }}"
Then accessing properties of a dictionary in Jinja is done using the syntax
{% for key, value in dict.items() %}
Source: https://jinja.palletsprojects.com/en/2.11.x/templates/#for
So in your case:
{% for key, value in item.items() %}
All together, a working playbook demonstrating this would be:
- hosts: all
gather_facts: no
tasks:
- debug:
msg: |
{% for key, value in item.items() %}
{{key}}: {{value}}
{% endfor %}
loop: "{{ my.variable }}"
vars:
my:
variable:
- name: test
path: /etc/apt
cert: key.crt
That yields the result:
PLAY [all] *******************************************************************************************************
TASK [debug] *****************************************************************************************************
ok: [localhost] => (item={'name': 'test', 'path': '/etc/apt', 'cert': 'key.crt'}) => {
"msg": " name: test\n path: /etc/apt\n cert: key.crt\n"
}
PLAY RECAP *******************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Now you just have to reuse this in your template and loop and you should have what you expect.