We're running ansible 2.9.27 on Red Hat Linux 7.9. In my playbook, I have the following code:
- name: DEBUG THIS THING1
debug:
msg: "FOUND HOME: {{ found_service_accounts['svclinux']['home'] }}"
- name: DEBUG THIS THING2
debug:
msg: "FOUND: {{ found_service_accounts['svclinux']['home'] | selectattr('path', 'defined') | list }}"
The output looks like this:
TASK [service_accounts : DEBUG THIS THING1] ***************************************************************************
ok: [myhost.example.com] => {
"msg": "FOUND HOME: {u'owner': u'svclinux', u'path': u'/home/svclinux', u'group': u'svc-linux', u'permissions': u'755'}"
}
TASK [service_accounts : DEBUG THIS THING2] ***************************************************************************
ok: [myhost.example.com] => {
"msg": "FOUND: []"
}
I can't figure out why Ansible isn't printing the path attribute of my home. It just prints the empty list. Can you see my obvious mistake?
Thanks.
Note: found_service_accounts is a dict that was set as follows. The homes.results...stdout_lines[n] are simple strings gleaned from a shell command:
- name: set found_service_accounts
set_fact:
found_service_accounts: |
{% set j2fsa = { } %}
{%- for user in service_account_names -%}
{% set _ = j2fsa.update(
{ user : { 'home' : { 'path' : homes.results[loop.index0].stdout_lines[0],
'owner' : homes.results[loop.index0].stdout_lines[1],
'group' : homes.results[loop.index0].stdout_lines[2],
'permissions' : homes.results[loop.index0].stdout_lines[3]
},
'ssh_subdirectory' : {
'owner' : ssh_subdir.results[loop.index0].stdout_lines[0],
'permissions' : ssh_subdir.results[loop.index0].stdout_lines[1]
}
}
})
%}
{%- endfor %}
{{ j2fsa }}
and service_account_names is just a list like: [ 'svclinux' ]
found_service_accounts['svclinux']['home'] is a dictionary, not a list. You should directly access the value of path:
- name: DEBUG THIS THING2
debug:
msg: "FOUND: {{ found_service_accounts['svclinux']['home']['path'] }}"
What happens in your original attempt is that iterating over a dictionary returns just the dictionary keys (["owner", "path", "group", "permissions"]), and strings do not have an attribute named path.
Related
I'm using Ansible playbook to get information about the server's hardware internals through iDrac controller. It is performed by 3rd party module, which uses API to connect to the device.
I get server's internals info (controllers, disks, CPU information, etc.) by running the task. And I would like to register some variables from such output (the output is just shortened by dots).
I kept the main structure of output, to make it clear:
ok: [rac1] => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"invocation": {
"module_args": {
"ca_path": null,
"idrac_ip": "192.168.168.100",
"idrac_password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER",
"idrac_port": 443,
...
}
},
"msg": "Successfully fetched the system inventory details.",
"system_info": {
"BIOS": [
{
"BIOSReleaseDate": "09/14/2022",
"FQDD": "BIOS.Setup.1-1",
…
}
],
"CPU": [
{
"CPUFamily": "Intel(R) Xeon(TM)",
"Characteristics": "64-bit capable",
"CurrentClockSpeed": "2.1 GHz",
…
},
{
"CPUFamily": "Intel(R) Xeon(TM)",
"Characteristics": "64-bit capable",
…
}
],
"Controller": [
{
"Bus": "67",
"CacheSize": "8192.0 MB",
"DeviceDescription": "RAID Controller in SL 3",
"FQDD": "RAID.SL.3-1",
"Key": "RAID.SL.3-1",
…
},
I need to get only couple values from output (PCI slot num where RAID controller is located):
"DeviceDescription": "RAID Controller in SL 3",
"Key": "RAID.SL.3-1"
But I have no clue, which example from documentation can I use to register value to variable.
Considering this is a third party module. The task execution is very slow, so it is not so easy for me to play with it as much as possible.
Could somebody suggest me please, which direction should I dig? I'm not a big expert in Ansible yet.
My role's tasks are following below.
I tried to get nested values using debug task(just to figure out key which I need to register), like this, but no luck:
### Get inventory key:value pairs and trying to save certain value to variable ###:
- name: Get Inventory
dellemc.openmanage.idrac_system_info:
idrac_ip: "{{ idrac_ip }}"
idrac_user: "{{ idrac_user }}"
idrac_password: "{{ idrac_password }}"
validate_certs: False
register: ansible_facts[system_info][Controller][FQDD].result
### Trying to show my saved variable in this task ###
- name: print registered value
debug:
var: RAID slot is at "{{ result }}"
verbosity: 4
I get this message after launching playbook:
"msg": "Unsupported parameters for (dellemc.openmanage.idrac_system_info) module: register. Supported parameters include: idrac_ip, timeout, idrac_user, ca_path, idrac_port, validate_certs, idrac_password (idrac_pwd)."
Since you are providing already valid output, how do have generated that? How was it "printed"?
A minimal example playbook
---
- hosts: rac1
become: false
gather_facts: false
vars:
result:
system_info: {
"BIOS": [
{
"BIOSReleaseDate": "09/14/2022",
"FQDD": "BIOS.Setup.1-1"
}
],
"CPU": [
{
"CPUFamily": "Intel(R) Xeon(TM)",
"Characteristics": "64-bit capable",
"CurrentClockSpeed": "2.1 GHz"
},
{
"CPUFamily": "Intel(R) Xeon(TM)",
"Characteristics": "64-bit capable"
}
],
"Controller": [
{
"Bus": "67",
"CacheSize": "8192.0 MB",
"DeviceDescription": "RAID Controller in SL 3",
"FQDD": "RAID.SL.3-1",
"Key": "RAID.SL.3-1"
}
]
}
tasks:
- name: Show Facts
debug:
msg: "{{ result.system_info.Controller }}"
will result already into the expected output of
TASK [Show Facts] *****************************
ok: [rac1] =>
msg:
- Bus: '67'
CacheSize: 8192.0 MB
DeviceDescription: RAID Controller in SL 3
FQDD: RAID.SL.3-1
Key: RAID.SL.3-1
Regarding
which example from documentation can I use to register value to variable.
you may read about Registering variables. For registering results, even for 3rd-party or Custom Modules the structure will be
- name: Task
module_name:
module_parameter: values
register: variable_name
That's why you get an syntax error
Unsupported parameters for (dellemc.openmanage.idrac_system_info) module: register.
about the incorrect indention. Therefore try first
- name: Get Inventory
dellemc.openmanage.idrac_system_info:
idrac_ip: "{{ idrac_ip }}"
idrac_user: "{{ idrac_user }}"
idrac_password: "{{ idrac_password }}"
validate_certs: False
register: inventory
- name: Show Inventory
debug:
msg: "{{ inventory }}"
to get familiar with the result set and data structure.
Further documentation which might help are Return Values and idrac_system_info module – Get the PowerEdge Server System Inventory.
I am stuck with a looping situation using jinja template with Ansible.
My jinja template:
{% for int in interfaces | difference(existing_conf) %}
interface {{ int }}
{% for ip in dhcp_servers | difference(existing_conf) %}
ip dhcp relay address {{ ip }}
{% endfor %}
{% endfor %}
TASK [view the existing config] *******************************************************************************************************************************************************************************************************************************************************************************
ok: [nxos-1] => {
"existing_conf": [
"Vlan1",
"10.1.1.2",
"10.1.1.3",
"10.1.1.4",
"Ethernet1/49",
"10.1.1.2",
"Ethernet1/50",
"10.1.1.2"
]
}
TASK [Needed config] *****************************************************************************************************************************************************************************************************************************************************************
ok: [nxos-1]
TASK [View the needed config] ************************************************************************************************************************************************************************************************************************************************************************************
ok: [nxos-1] => {
"needed_conf": [
"Vlan1",
"10.1.1.2",
"10.1.1.3",
"10.1.1.4",
"Ethernet1/49",
"10.1.1.2",
"10.1.1.3",
"10.1.1.4",
"Ethernet1/50",
"10.1.1.2",
"10.1.1.3",
"10.1.1.4"
]
}
My variables:
interfaces=["Vlan1", "Ethernet1/49", "Ethernet1/50"]
dhcp_servers=["10.1.1.2", "10.1.1.3", "10.1.1.4"]
With my template, I am getting an empty config.
Config I want:
interface Ethernet1/49
ip dhcp relay address 10.1.1.3
ip dhcp relay address 10.1.1.4
interface Ethernet1/50
ip dhcp relay address 10.1.1.3
ip dhcp relay address 10.1.1.4
Note: I only want to add the config which is not in the running-config of switch.
Q: "... I am getting an empty config."
A: There are no items to iterate over. The difference
- debug:
msg: "{{ interfaces|difference(existing_conf) }}"
gives an empty list
msg: []
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.
I tried to combine the below dictionaries to one but struggling to pass the condition when one of the variable is not defined.
ok: [vm1.nodekite.com]] => {
"containeruplist": {
"service": "service-test-app",
"platform-service": "service-dev-app"
}
}
ok: [vm1.nodekite.com] => {
"containerexitedlist": {
"nginx": "www-service"
}
}
Here is my code.
- set_fact:
final_list: "{{ final_list|default({}) | combine( {item.key:item.value} ) }}"
with_dict:
- "{{ containeruplist }}"
- "{{ containerexitedlist }}"
when: (containeruplist is defined) or (containerexitedlist is defined)
Here is the output
ok: [vm1.nodekite.com] => {
"final_list": {
"service": "service-test-app",
"platform-service": "service-dev-app"
"nginx": "www-service"
}
}
but how to pass all the 3 conditionals when one of the variable is not defined.
containeruplist is populated or containerexitedlist is undefined
containeruplist is undefined or containerexitedlist is populated
containeruplist is populated or containerexitedlist is populated
I tried with when: (containeruplist is defined) or (containerexitedlist is defined) which still throws {"msg": "'containerexitedlist' is undefined"}
I know how to process jinja2 templates files and let them create files. I also know how to POST to webservices using the url module.
For now I use some code like this, which successfully posts hardcoded JSON to my remote service:
tasks:
- name: GSA app definition
uri:
url: "http://localhost:8764/api/apps?relatedObjects=false"
method: POST
force_basic_auth: yes
user: "{{ admin_name }}"
password: "{{ admin_pass }}"
body_format: json
body: "{\"name\":\"My new app\", \"description\":\"A really great new app\" }"
follow_redirects: all
status_code: 200
timeout: 15
register: app_gsa_cfg
But the JSON is static, how can I process a jinja2 template and POST its content ? I would prefer not having to create temporary files on disk and POST them, what I am looking for is a direct connection or perhaps an approach that puts the template processing result into a string.
For starters a jinja2 template could look like this, later I will add variables too:
{#
This file creates the basic GSA app in Fusion. See https://doc.lucidworks.com/fusion-server/4.2/reference-guides/api/apps-api.html#create-a-new-app for details
#}
{
"name": "GSA",
"description": "Contains all configuration specific to the migrated GSA legacy searches"
}
(I know that this has little advantage over a static json included into the playbook. But is is easier to edit and offers me the opportunity to have (jinja style) comments in Json, which is normally not possible)
In my case, what I do is the following:
I have an API, so I do the following:
- name: Change API Status
uri:
url: "{{ enpoint }}/v1/requests/{{ whatever }}"
method: PATCH
user: "{{ tokenid }}"
password: x
headers:
X-4me-Account: "myaccount"
body: '{ "status":"{{ reqstatus }}" }'
body_format: json
status_code:
- 201
- 200
force_basic_auth: true
validate_certs: false
return_content: true
Then your reqstatus var will change.
Even you can add your whole text as yaml, import into a variable and convert with filters {{ some_variable | to_json }}
Note: Have a look to the formatting without escaping quotes. That will help.
It makes no sense creating a file with jinja2 if you are not going to copy it remotely. Ansible supports jinja natively but its strength is the possibility to have plugins for better maintainability. There is no difference between template (or win_template) modules unless (as said) you copy the file somewhere. Look this example:
---
- name: Adhoc Jinja
hosts: localhost
connection: local
gather_facts: false
vars:
mytemplate:
- name: "GSA"
description: "Contains all configuration specific to the migrated GSA legacy searches"
- name: "Another Name"
description: "Contains Another Var"
tasks:
- name: Read Vars Loop
debug:
msg: "{{ item | to_json }}"
with_items: "{{ mytemplate }}"
- name: Include Vars
include_vars: adhocjinja2.yml
- name: Read Vars Loop
debug:
msg: "{{ item | to_json }}"
with_items: "{{ mytemplate }}"
And adhocjinja2.yml:
mytemplate:
- name: "GSA2"
description: "Contains all configuration specific to the migrated GSA legacy searches"
- name: "Another Name 2"
description: "Contains Another Var"
The output is:
TASK [Read Vars Loop] **************************************************************************************
ok: [localhost] => (item={'name': 'GSA', 'description': 'Contains all configuration specific to the migrated GSA legacy searches'}) => {
"msg": "{\"name\": \"GSA\", \"description\": \"Contains all configuration specific to the migrated GSA legacy searches\"}"
}
ok: [localhost] => (item={'name': 'Another Name', 'description': 'Contains Another Var'}) => {
"msg": "{\"name\": \"Another Name\", \"description\": \"Contains Another Var\"}"
}
TASK [Include Vars] ****************************************************************************************
ok: [localhost]
TASK [Read Vars Loop] **************************************************************************************
ok: [localhost] => (item={'name': 'GSA2', 'description': 'Contains all configuration specific to the migrated GSA legacy searches'}) => {
"msg": "{\"name\": \"GSA2\", \"description\": \"Contains all configuration specific to the migrated GSA legacy searches\"}"
}
ok: [localhost] => (item={'name': 'Another Name 2', 'description': 'Contains Another Var'}) => {
"msg": "{\"name\": \"Another Name 2\", \"description\": \"Contains Another Var\"}"
}
You can manage your variables as you want and create your json on the fly as Ansible has jinja and json it its heart.