I am trying to loop variables into a playbook, but I keep getting the following error
TASK [Add policy package to model device] ********************************************************************************************************************************************************************
failed: [fmg01] (item={'device_name': 'FGT1', 'device_ip': '192.168.0.103', 'group_name': 'Branch_Office', 'policy_package': 'default', 'device_serial': 'FGVM01TM12345678'}) => {"ansible_loop_var": "item", "changed": false, "item": {"device_ip": "192.168.0.103", "device_name": "FGT1", "device_serial": "FGVM01TM12345678", "group_name": "Branch_Office", "policy_package": "default"}, "meta": {"status": {"code": -10, "message": "The data is invalid for selected url"}, "url": "/pm/pkg/adom/root"}, "unreachable": false}
Below is my YML playbook. It works just fine when I use static variables (write out a single IP address and serial number as a string)
---
- name: Add model device to FMG and install Policy Package
hosts: fmg01
# gather_facts: no
connection: httpapi
collections:
- fortinet.fortimanager
vars_files:
- inventory.yml
tasks:
- name: Add model device
fmgr_dvm_cmd_add_device:
loose_validation: true
method: exec
params:
- data:
adom: root
device:
# device action: add_model
mgmt_mode: 'fmg'
#os_ver: 6
#mr: 4
sn: '{{item.device_serial}}'
adm_pass: 'password'
adm_usr: 'admin'
ip: '{{item.device_ip}}'
loop: '{{device_list}}'
- name: Add policy package to model device
fmgr_pm_pkg_adom_obj:
loose_validation: true
method: set
url_params:
adom: root
params:
- data:
name: default
scope member:
- name: '{{item.device_serial}}'
vdom: root
type: pkg
loop: '{{device_list}}'
- name: Install policy package to model device
fmgr_securityconsole_install_package:
loose_validation: true
method: exec
params:
- data:
adom: root
pkg: default
scope:
- name: '{{item.device_serial}}'
vdom: root
loop: '{{device_list}}'
Here is my inventory.yml file. I tested this ok with the first task. It passed in the IP address in ok. I tested the other tasks with the device_serial parameter, but that did not work.
#feeds into playbook
#add_device / add_device_group
device_list:
- device_name: FGT1
device_ip: '192.168.0.103'
group_name: Branch_Office
policy_package: default
device_serial: 'FGVM01TM12345678'
Problem turned out to be an indention problem with the params portion
- name: Add policy package to model device
fmgr_pm_pkg_adom_obj:
loose_validation: true
method: set
url_params:
adom: root
params:
- data:
name: default
scope member:
- name: '{{item.device_serial}}'
vdom: root
type: pkg
loop: '{{device_list}}'
Related
I have ansible playbook where I want purge all queues in rabbitmq
- name: Get queues
uri:
url: http://localhost:15672/api/queues
method: GET
return_content: yes
status_code: 200,404
body_format: json
register: result
- name: Just the Names
debug: msg="{{ result.json | json_query(jmesquery)}}"
vars:
jmesquery: "[*].name"
register: queue_name
Return list queues
TASK [demo : Just the Names] ***
ok: [test] => {
"msg": [
"test1",
"test2"
]
}
How to do a loop for queues
- name: delete queues
uri:
url: http://localhost:15672/api/queues/aaa/{{ queue_name }}/contents"
method: DELETE
You could try:
- name: delete queues
uri:
url: "http://localhost:15672/api/queues/aaa/{{ item }}/contents"
method: DELETE
loop: "{{ result.json | json_query(jmesquery)}}"
vars:
jmesquery: "[*].name"
I'm creating playbook to install fluentbit on windows hosts. Everything is working properly but i'm getting error when creating service, it doesn’t fail the install as then everything is already in place but I would like to figure out how I could leverage conditionals. Could you help me with this? :)
My adhoc test-play where I've tried to parse results from ansible.windows.win_service_info module is as follows:
---
- name: Check Windows service status
hosts: win
gather_facts: True
tasks:
- name: Check if a service is installed
win_service:
name: fluent-bit
register: service_info
- debug: msg="{{service_info}}"
- name: Get info for a single service
ansible.windows.win_service_info:
name: fluent-bit
register: service_info
- debug: msg="{{ service_info }}"
- name: Get info for a fluent-bit service
ansible.windows.win_service_info:
name: logging
register: service_exists
- debug: msg="{{ service_exists }}"
- name: Send message if service exists
debug:
msg: "Service is installed"
when: service_exists.state is not defined or service_exists.name is not defined
- name: Send message if service exists
debug:
msg: "Service is NOT installed"
when: service_exists.state is not running
I just don’t get it how I could parse output so that I could skip task when fluent-bit -service exists = True like here:
TASK [debug] *****************************************************************************************
ok: [win-server-1] => {
"msg": {
"can_pause_and_continue": false,
"changed": false,
"depended_by": [],
"dependencies": [],
"description": "",
"desktop_interact": false,
"display_name": "fluent-bit",
**"exists": true,**
"failed": false,
"name": "fluent-bit",
"path": "C:\\fluent-bit\\bin\\fluent-bit.exe -c C:\\fluent-bit\\conf\\fluent-bit.conf",
"start_mode": "manual",
"state": "stopped",
"username": "LocalSystem"
}
}
Cheers :)
So, got it working as I wanted with service_info.exists != True, now it will skip the task if service is already present.
I have the following variables in my var file:
repo_type:
hosted:
data:
- name: hosted_repo1
online: true
storage:
blobstarage: default
write_policy: allow_once
- name: hosted_repo2
online: true
storage:
blobstarage: default
write_policy: allow_once
proxy:
data:
- name: proxy_repo1
online: true
storage:
blobstarage: default
write_policy: allow_once
- name: proxy_repo2
online: true
storage:
blobstarage: default
write_policy: allow_once
group:
data:
- name: group_repo1
online: true
storage:
blobstarage: default
write_policy: allow_once
- name: group_repo2
online: true
storage:
blobstarage: default
write_policy: allow_once
I want to configure a task to to loop over (hosted,proxy and group) and body over data dict.
Here is the task:
- name: Create pypi hosted Repos
uri:
url: "{{ nexus_api_scheme }}://{{ nexus_api_hostname }}:{{ nexus_api_port }}\
{{ nexus_api_context_path }}{{ nexus_rest_api_endpoint }}/repositories/pypi/{{ item.key}}"
user: "{{ nexus_api_user }}"
password: "{{ nexus_default_admin_password }}"
headers:
accept: "application/json"
Content-Type: "application/json"
body_format: json
method: POST
force_basic_auth: yes
validate_certs: "{{ nexus_api_validate_certs }}"
body: "{{ item }}"
status_code: 201
no_log: no
with_dict: "{{ repo_type}}"
I have tried with_items, with_dict and with_nested but nothing helped.
The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'data'
Any help would be appreciated!
If your goal is to loop over the contents of the data keys as a flat list, you could do it like this:
- debug:
msg: "repo {{ item.name }} write_policy {{ item.storage.write_policy }}"
loop_control:
label: "{{ item.name }}"
loop: "{{ repo_type | json_query('*.data[]') }}"
That uses a JMESPath expression to get the data key from each
top-level dictionary, and then flatten the resulting nested list. In
other words, it transforms you original structure into:
- name: hosted_repo1
online: true
storage:
blobstarage: default
write_policy: allow_once
- name: hosted_repo2
online: true
storage:
blobstarage: default
write_policy: allow_once
- name: proxy_repo1
online: true
storage:
blobstarage: default
write_policy: allow_once
- name: proxy_repo2
online: true
storage:
blobstarage: default
write_policy: allow_once
- name: group_repo1
online: true
storage:
blobstarage: default
write_policy: allow_once
- name: group_repo2
online: true
storage:
blobstarage: default
write_policy: allow_once
When run using your example data, this produces as output:
TASK [debug] *********************************************************************************************************
ok: [localhost] => (item=hosted_repo1) => {
"msg": "repo hosted_repo1 write_policy allow_once"
}
ok: [localhost] => (item=hosted_repo2) => {
"msg": "repo hosted_repo2 write_policy allow_once"
}
ok: [localhost] => (item=proxy_repo1) => {
"msg": "repo proxy_repo1 write_policy allow_once"
}
ok: [localhost] => (item=proxy_repo2) => {
"msg": "repo proxy_repo2 write_policy allow_once"
}
ok: [localhost] => (item=group_repo1) => {
"msg": "repo group_repo1 write_policy allow_once"
}
ok: [localhost] => (item=group_repo2) => {
"msg": "repo group_repo2 write_policy allow_once"
}
If you're trying to do something else, please update your question so
that it clearly shows the values you expect for each iteration of your
loop.
As reported by #larsk, you actually didn't manage to explain clearly how you are trying to loop over your data and what your api call is actually expecting.
But you are in luck this time since I have messed around with Nexus quite a bit (and I think I actually recognize those variable names and overall task layout)
The Nexus Repository POST /v1/repositories/pypi/[hosted|proxy|group] API endpoints are expecting one call for each of your repos in data. To implement your requirement you need to loop over the keys in repo_type to select the appropriate endpoint and then loop again over each element in data to send the repo definition to create.
This is actually possible combining a dict2items and subelements filters in your loop as in the below playbook (not directly tested).
The basics of the transformation are as follow:
transform your dict to a key/value list using dict2items e.g. (shortened example)
- key: hosted
value:
data:
- <repo definition 1>
- <repo definition 2>
[...]
use the subelements filter to combine each top element with each element in value.data e.g.:
- # <- This is the entry for first repo i.e. `item` in your loop
- key: hosted # <- this is the start of the corresponding top element i.e. `item.0` in your loop
value:
data:
- <repo definition 1>
- <repo definition 2>
- <repo definition 1> # <- this is the sub-element i.e. `item.1` in your loop
- # <- this is the entry for second repo
- key: hosted
value:
data:
- <repo definition 1>
- <repo definition 2>
- <repo definition 2>
[...]
Following one of your comments and from my experience, I added to my example an explicit json serialization of the repo definition using the to_json filter.
Putting it all together this gives:
- name: Create pypi hosted Repos
uri:
url: "{{ nexus_api_scheme }}://{{ nexus_api_hostname }}:{{ nexus_api_port }}\
{{ nexus_api_context_path }}{{ nexus_rest_api_endpoint }}/repositories/pypi/{{ item.0.key }}"
user: "{{ nexus_api_user }}"
password: "{{ nexus_default_admin_password }}"
headers:
accept: "application/json"
Content-Type: "application/json"
body_format: json
method: POST
force_basic_auth: yes
validate_certs: "{{ nexus_api_validate_certs }}"
body: "{{ item.1 | to_json }}"
status_code: 201
no_log: no
loop: "{{ repo_type | dict2items | subelements('value.data') }}"
C:\CYGWIN64\ETC\ANSIBLE\ANSIBLE-ACI-CONFIG
├───environments
│ ├───houston
│ └───munich
├───group_vars
├───plays
├───plugins
│ └───filter
│ └───__pycache__
└───roles
├───aci-fabric-onboarding
│ └───tasks
variable file:
oob_nodes:
- { node_id: "101", obb_address: "10.10.10.10", obb_cidr: "27" , obb_gateway: "10.10.10.1" }
- { node_id: "102", obb_address: "10.10.10.11", obb_cidr: "27" , obb_gateway: "10.10.10.1" }
- { node_id: "201", obb_address: "10.10.10.12", obb_cidr: "27" , obb_gateway: "10.10.10.1" }
play
========
- name: Setup ACI Fabric
hosts: "{{ target }}"
gather_facts: no
any_errors_fatal: true
tasks:
- include_vars:
file: "{{ ACI_SSoT_path }}/fabricsetup.yml"
- include_vars:
file: "{{ ACI_SSoT_path }}/oob.yml"
# Intent Statement
- include_role:
name: aci-fabric-onboarding
roles
==============
# Adding OBB address
- name: Add OBB address
delegate_to: localhost
aci_rest:
host: "{{ aci_ip }}"
username: ansible
private_key: ansible.key
certificate_name: ansible
use_ssl: yes
validate_certs: false
path: /api/node/mo/uni/tn-mgmt/mgmtp-default/oob-default/rsooBStNode-[topology/pod-1/node-"{{item.node_id}}"].json
method: post
content:
{
"mgmtRsOoBStNode":{
"attributes":{
"tDn":"topology/pod-1/node-101",
"addr":"25.96.131.61/27",
"gw":"25.96.131.33",
"status":"created"
},
"children":[
]
}
}
with_items: "{{ oob_nodes }}"
error:
TASK [aci-fabric-onboarding : Add OBB address] *****************************************************************************************************************************************************
task path: /etc/ansible/Ansible-Aci-config/roles/aci-fabric-onboarding/tasks/apply-oob-config.yml:4
fatal: [25.96.131.30]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: 'item' is undefined\n\nThe error appears to be in '/etc/ansible/Ansible-Aci-config/roles/aci-fabric-onboarding/tasks/apply-oob-config.yml': line 4, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n# Adding OBB address\n- name: Add OBB address\n ^ here\n"
}
That looks like an indention error to me. You have with_items with the same indention as aci_rest:
# Adding OBB address
- name: Add OBB address
delegate_to: localhost
aci_rest:
host: "{{ aci_ip }}"
username: ansible
private_key: ansible.key
certificate_name: ansible
use_ssl: yes
validate_certs: false
path: /api/node/mo/uni/tn-mgmt/mgmtp-default/oob-default/rsooBStNode-[topology/pod-1/node-"{{ item.node_id }}"].json
method: post
content:
{
"mgmtRsOoBStNode":{
"attributes":{
"tDn":"topology/pod-1/node-101",
"addr":"25.96.131.61/27",
"gw":"25.96.131.33",
"status":"created"
},
"children":[
]
}
}
with_items: "{{ oob_nodes }}"
Have a look at the documentation as well.
Here is the Ansible source code where with_items is linked to a dictionary type variable named "grafana_datasource_body". I always get an error message "ith_items expects a list or a set". Here is the source code and related output.
Ansible source code :
- name: Configure datasource creation http body
set_fact:
grafana_datasource_body:
name: "{{Ass_grafana_Datasource_Name}}"
type: "{{Ass_grafana_Datasource_Type}}"
isDefault: "{{Ass_grafana_Datasource_IsDefault}}"
access: "{{Ass_grafana_Datasource_Access}}"
basicAuth: "{{Ass_grafana_Datasource_BasicAuth}}"
url: "{{InfluxDB_Server}}:{{Ass_grafana_Datasource_Http_Port}}"
database: "{{Ass_grafana_Datasource_Db}}"
username: "{{Ass_grafana_Datasource_DbUser}}"
password: "{{Ass_grafana_Datasource_DbPassword}}"
when: Ass_grafana_Datasource_Name not in grafana_datasources_output.json|map(attribute='name')|list
- debug: msg="Datasource creation http body = {{grafana_datasource_body}}"
when: Ass_grafana_Datasource_Name not in grafana_datasources_output.json|map(attribute='name')|list
# Create non existing datasources
- name: datasource > Create datasource
register: grafana_datasources_create_output
failed_when: "'Datasource added' not in grafana_datasources_create_output|to_json"
uri:
url: "http://127.0.0.1:{{Ass_grafana_Listen_Http_Port}}/api/datasources"
method: POST
HEADER_Content-Type: application/json
body: '{{ item|to_json }}'
force_basic_auth: true
user: "{{Ass_grafana_Api_User}}"
password: "{{Ass_grafana_Api_Password}}"
status_code: 200
with_items: "{{grafana_datasource_body}}"
when: item.name not in grafana_datasources_output.json|map(attribute='name')|list
Output
TASK: [./grafana/grafana_Role | Configure datasource creation http body] ******
<10.0.20.7> ESTABLISH CONNECTION FOR USER: centos
ok: [10.0.20.7] => {"ansible_facts": {"grafana_datasource_body": {"access": "proxy", "basicAuth": "false", "database": "webcom-int", "isDefault": "true", "name": "MonTonton", "password": "webcom", "type": "influxdb", "url": "http://localhost:8086", "username": "webcom"}}}
TASK: [./grafana/grafana_Role | debug msg="Datasource creation http body = {{grafana_datasource_body}}"] ***
<10.0.20.7> ESTABLISH CONNECTION FOR USER: centos
ok: [10.0.20.7] => {
"msg": "Datasource creation http body = {'username': u'webcom', 'name': u'MonTonton', 'database': u'webcom-int', 'url': u'http://localhost:8086', 'basicAuth': u'false', 'access': u'proxy', 'password': u'webcom', 'type': u'influxdb', 'isDefault': u'true'}"
}
TASK: [./grafana/grafana_Role | datasource > Create datasource] ***************
fatal: [10.0.20.7] => with_items expects a list or a set
FATAL: all hosts have already failed -- aborting
Any real reason to use with_items loop here?
Replace
with_items: "{{grafana_datasource_body}}"
with
with_items: "{{ [grafana_datasource_body] }}"
this will work.
But you can use body: '{{ grafana_datasource_body|to_json }}' and don't use with_items.