Consider the following from a Powershell session on a WS2019 instance, ...
PS> get-netadapter -Name "*" -physical
Name InterfaceDescription ifIndex Status ...
---- -------------------- -------------- ...
Ethernet0 2 vmxnet3 Ethernet Adapter 3 up ...
I want to use the above Name value as a conditional in an Ansible Play, so i task ...
- name: Get current NIC name (re-entrant flag)
ansible.windows.win_powershell:
script: |
Get-NetAdapter -Name "*" -physical
register: NIC
... and then use NIC.Name in a later conditional; but it fails as undefined. So, i debug NIC to learn structure and get this huge enumeration that includes the following (i deleted some irrelevant rows for purpose of this post) ...
"NIC: {'changed': True, 'result': {}, 'host_out': '', 'host_err': '', 'output': [{'PowerOnHours':None, 'Virtual': False, 'ReceiveLinkSpeed': 10000000000, 'ElementName': None, ...
\Device\\\\{2C89B206-627B-45C0-B9F9-AFAEDA168DF6}', 'Name': 'Ethernet0 2', 'PSComputerName': None, ...
'PortNumber': 0, 'OtherPortType': None}], 'error': [], 'warning': [], 'verbose': [], 'debug': [], 'information': [], 'failed': False}",
In attempting to find a working hierarchy/syntax, i debugged for the following with the results as commented ...
- name: Debug essentials
debug:
msg:
- "NIC: {{ NIC }}"
#has no attribute - "NIC Name: {{ NIC.Name }}"
- "NIC output: {{ NIC.output }}"
#wtf; this task won't run with this line: - "NIC output name: {{ NIC.output.name }}"
#has no attribute - "NIC output Name: {{ NIC.output.Name }}"
#has no attribute - "NIC output PowerOnHours: {{ NIC.output.PowerOnHours }}"
#has no attribute - "NIC output CIM: {{ NIC.output.CimInstanceProperties }}"
- "Survey Network Name: {{ VMNetwork }}"
That is, only NIC and NIC.output statements of the above were valid. Can anyone help me to learn ...
NIC<missing syntax>Name
... such that i can use it in a later play as a conditional argument.
Summary: I intuitively tried ...
NIC.Name
... but it resolves as undefined attribute. I assessed NIC output via debug and then tried ...
NIC.output.Name
... but got the same undefined results.
I researched PowerShell usage of brackets and braces while considering the Name: Ethernet0 2. My resolve was to try …
NIC.output[0].Name
... and this worked. Fortunately, there is only 1 physical NIC on these newly instantiated VMs so it was easy to deduce 0 as the ordinal offset. If there were more physical NICs, i'd have to learn some more WMI stuff :( ...
Related
I´m doing the first steps in Ansible this week and I break on include_tasks for looping ofer a role.
The needed task is to create Letsencrypt certificates for a bunch of domains, thanks to T-Systems-MMS, there is already a collection to do this via APIs of letsencrypt and AutoDNS (see https://github.com/T-Systems-MMS/ansible-collection-acme/blob/master/docs/dns-challenge/autodns.md).
Filling this playbook with my settings, it is working fine for one domain. My try to loop over is (hopefully there was no mistake while anonymising the code):
playbook_getsslcert_main.yml:
---
- hosts: localhost
connection: local
vars:
ansible_python_interpreter: auto
tasks:
- name: Get SSL certificate
include_tasks: playbook_getsslcert_task.yml
loop:
- sub1.domain1.com
- sub2.domain1.com
playbook_getsslcert_task.yml:
---
- name: Doing letsencrypt ACME with AutoDNS
collections:
- t_systems_mms.acme
roles:
- acme
vars:
nbb_emailadress: my.email#example.com
nbb_autodnsuser: login.user#other.com
acme_domain:
certificate_name: "{{ item }}"
zone: "domain1.com"
email_address: "{{ nbb_emailadress }}"
subject_alt_name:
- "{{ item }}"
acme_challenge_provider: autodns
acme_use_live_directory: true
acme_conf_dir: /etc/letsencrypt
acme_account_email: "{{ nbb_emailadress }}"
acme_dns_user: "{{ nbb_autodnsuser }}"
acme_dns_password: "supersecret"
The error I get is
fatal: [localhost]: FAILED! => {"reason": "conflicting action statements: hosts, roles\n\nThe error appears to be in 'playbook_getsslcert_task.yml': line 2, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n---\n- name: Doing letsencrypt ACME with AutoDNS\n ^ here\n"}
My collegues and me are experienced Linux guys, we tested a lot; also we checked the YAML with formatcheckers and so on, did different styles for looping, tried an example tasks.ym just for writing a message, checked file formats (for linefeeds, correct HEX values,...) and so on.
But Ansible doesnt like the playbook.
Thanks for all your suggestions.
Edit:
Ubuntu 18.04 LTS, Python 3.6.9, Ansible 2.9.27
Thanks to #Zeitounator (sorry for overlooing your first link), a suitable and working solution have been found:
---
- hosts: all
connection: local
vars:
ansible_python_interpreter: auto
tasks:
- name: "Doing letsencrypt ACME with AutoDNS for {{ nbb_domain }}"
collections:
- t_systems_mms.acme
include_role:
name: acme
vars:
nbb_emailadress: my.email#example.com
nbb_autodnsuser: login.user#other.com
acme_domain:
certificate_name: "{{ nbb_domain }}"
zone: "domain1.com"
email_address: "{{ nbb_emailadress }}"
subject_alt_name:
- "{{ nbb_domain }}"
acme_challenge_provider: autodns
acme_use_live_directory: true
acme_conf_dir: /etc/letsencrypt
acme_account_email: "{{ nbb_emailadress }}"
acme_dns_user: "{{ nbb_autodnsuser }}"
acme_dns_password: "supersecret"
loop:
- sub1.domain1.com
- sub2.domain1.com
loop_control:
loop_var: nbb_domain
I'm rather new to ansible and would like to deploy prometheus-grok-exporter (via ansible-grok-exporter role) with a specific configuration for all my nodes that run the cacti application.
My inventory is like this:
cacti_first ansible_host=192.168.50.50
cacti_second ansible_host=192.168.50.51
[group__cacti]
cacti_first
cacti_second
Inside group_vars/group__cacti I want to add something like this:
---
prometheus_grok_services_template:
- name: cacti_metrics
config_version: 3
input:
type: file
paths:
{% for cacti_dir in cacti_path %}
- "{{cacti_dir}}/log/cacti.log"
{% endfor %}
readall: false
extaConfigContinuesFromHere: true
And I have host config like this:
host_vars/cacti_first:
---
cacti_path:
- /usr/share/cacti
prometheus_grok_services:
- prometheus_grok_services_template
host_vars/cacti_second:
---
cacti_path:
- /usr/share/cacti
- /usr/share/cacti2
prometheus_grok_services:
- prometheus_grok_services_template
Inside the playbook I do a loop for prometheus_grok_services and use the yaml data to provision the service.
Now - this works as long as I don't try to use a loop inside group_vars/group__cacti. ansible-inventory reports that:
$ ansible-inventory -i hosts --list cacti_second
ERROR! Syntax Error while loading YAML.
found character that cannot start any token
The error appears to be in '/home/bastion/ansible-playbooks/group_vars/group__cacti': line 8, column 10, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
paths:
{% for cacti_dir in cacti_path %}
^ here
So, I'd like to ask - is it allowed to do jinja loops to build yaml for group vars? Is it a syntax error on my end? How am I supposed to template it?
I'd like to avoid moving the block to host vars (which I know works), mostly because it's a large piece of code (about 2KB of yaml config) and it's not as elegant as using group vars.
Thanks!
Fix the group_vars. For example
shell> cat group_vars/group__cacti
---
prometheus_grok_services_template:
- name: cacti_metrics
config_version: 3
input:
type: file
paths: "{{ paths_str|from_yaml }}"
readall: false
extaConfigContinuesFromHere: true
paths_str: |
{% for cacti_dir in cacti_path %}
- {{ cacti_dir }}/log/cacti.log
{% endfor %}
Then, the playbook
- hosts: group__cacti
gather_facts: false
tasks:
- debug:
msg: "{{ lookup('vars', item) }}"
loop: "{{ prometheus_grok_services }}"
gives
ok: [cacti_first] => (item=prometheus_grok_services_template) =>
msg:
- config_version: 3
input:
extaConfigContinuesFromHere: true
paths:
- /usr/share/cacti/log/cacti.log
readall: false
type: file
name: cacti_metrics
ok: [cacti_second] => (item=prometheus_grok_services_template) =>
msg:
- config_version: 3
input:
extaConfigContinuesFromHere: true
paths:
- /usr/share/cacti/log/cacti.log
- /usr/share/cacti2/log/cacti.log
readall: false
type: file
name: cacti_metrics
You can't use this kind of for loop in a variables file or a playbook - it only works in template files. To acheive what you're after, you can use product filters, as described https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#products
In your example, you would have:
---
prometheus_grok_services_template:
- name: cacti_metrics
config_version: 3
input:
type: file
paths: "{{ cacti_path | product(['/log/cacti.log']) | map('join') | list }}"
readall: false
extaConfigContinuesFromHere: true
I have an array of JSON like below:
[
{
"item": {
"item": "SERVERNAME"
}
},
{
"item": {
"item": "SERVERNAME2"
}
}
]
On a previous API call I was using
with_items: "{{ cluster_server.results }}"
And was able to grab each server name via {{ item.item.item }}
BUT on this API call I only need the first result so using
with_items: "{{ cluster_server.results | first}}"
yet it returns this error:
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'item'\n\nThe error appears to be in '/home/mycomp/Documents/ansible/build-auth.yml': line 177, column 6, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - copy:\n ^ here\n"}
What am I doing wrong here? Fairly new to Ansible.
You are overwriting item, which is your loop variable. Use another name for the loop variable:
- debug:
msg: "{{ server.item.item }}"
with_items:
- "{{ cluster_server.results | first }}"
loop_control:
loop_var: server
I have a variables file that includes important info about our databases; the server they are on, the db version, the DB_HOME directory, etc. In the variables file, I would like to dynamically create lists that capture the unique values of those properties, so they can be easily iterated through in a task.
I have equivalent functionality by creating the list on the fly in a task's loop option, but that means repeating that loop syntax (violates DRY principle) and I would like less sophisticated Ansible colleagues to be able to use a pre-defined list.
example of the variables file databases.yml:
databases:
- name: test_db1
server: ora_901
listener: LISTENER_XYZ
version: '11.2.0.4'
oracle_home: '/app/oracle/product/11.2.0.4/db_home'
- name: test_db2
server: ora_902
listener: LISTENER_ABC
version: '11.2.0.4'
oracle_home: '/app/oracle/product/11.2.0.4/db_home'
## This didn't work... was hoping I could build this list dynamically
listeners:
- name: "{{ item }}"
loop: "{{ databases | map(attribute = 'listener') | list | unique }}"
servers:
- name: "{{ item }}"
loop: "{{ databases | map(attribute = 'server') | list | unique }}"
I would then use this loop through either the 'listeners' or 'servers' lists directly with some tasks.
When I tried a task that referenced the listeners variable, it failed. Referencing databases works and all items are returned, so I know it's getting some data from the vars_file...
- vars_files:
- vars/databases.yml
tasks:
- debug:
msg: "{{ databases }}"
- debug:
msg: "{{ listeners }}"
TASK [debug] **************************************************************************
ok: [FQDN] => {
"msg": [
{
"listener": "LISTENER_XYZ",
"name": "test_db1",
"oracle_home": "/app/oracle/product/11.2.0.4/db_home",
"server": "ora_901",
"version": "11.2.0.4"
},
{
"listener": "LISTENER_ABC",
"name": "test_db2",
"oracle_home": "/app/oracle/product/11.2.0.4/dbhome_1",
"server": "ora_902",
"version": "11.2.0.4"
},
fatal: [FQDN]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'item' is undefined\n\nThe error appears to have been in '/home/xxx/test_vars.yml': line 21, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
I would really like to keep these dynamic definitions in the same place as the server definitions, and don't see why it wouldn't be possible, I'm just sure I'm using the wrong mechanism.
Check this out from Ansible doc// Just to give you an idea, about "loop_control".
You can nest two looping tasks using include_tasks. However, by default Ansible sets the loop variable item for each loop. This means the inner, nested loop will overwrite the value of item from the outer loop. You can specify the name of the variable for each loop using loop_var with loop_control:
# main.yml
- include_tasks: inner.yml
loop:
- 1
- 2
- 3
loop_control:
loop_var: outer_item
# inner.yml
- debug:
msg: "outer item={{ outer_item }} inner item={{ item }}"
loop:
- a
- b
- c
I have a set of variables which define FQDNs.
domains:
- erp: erp.mycompany.com
- crm: crm.mycompany.com
- git: git.mycompany.com
Indeed, I both need to loop over them and access them namely (in a template file). So accessing them like domains.erpworks like a charm. But I can't get ansible to loop over these.
Obviously, if I do:
- name: Print domains
debug:
msg: test {{ item }}
with_items:
- "{{ domains }}"
It prints both the key and the value… And if I do:
- name: Print domains
debug:
msg: test {{ domains[{{ item }}] }}
with_items:
- "{{ domain }}"
But that doesn't work. I also tried the hashes form as mentionned in the docs, but didn't get any luck either…
Finally, I had to use a dict.
It didn't work the first time because unlike with_items, which has the items going each on their own line, with_dict is a one liner without - before the element to loop through.
domains:
erp:
address: erp.mycompany.com
crm:
address: crm.mycompany.com
git:
address: git.mycompany.com
# used by letsencrypt
webserverType: apache2
withCerts: true
tasks:
- name: Print phone records
debug:
msg: "{{ item.value.address }}"
with_dict: "{{ domains }}"
# I can still access a given domain by its name when needed like so:
{{ domains.erp.address }}
Looks like you figured out your issue. Your original attempt uses a list of dictionaries that do not contain the same keys, making it difficult to access the values uniformly across each list item.
Your second solution creates a dictionary where the keys refer to other dictionaries.
Another solution than what you posted if you still wanted to use a list:
- hosts: localhost
vars:
domains:
- name: erp
address: erp.mycompany.com
- name: crm
address: crm.mycompany.com
- name: git
address: git.mycompany.com
tasks:
- name: Print phone records
debug:
msg: "{{ item.address }}"
with_items: "{{ domains }}"
To me this approach is simpler but your second approach works as well.