Ansible: "unknown type long" when updating Private DNS Zone in Azure - azure

I try to update serial number for a zone in Private DNS in Azure. To do that, I run Ansible code:
- name: Increment DNS serial
azure_rm_dnsrecordset:
resource_group: "{{ my_rg }}"
zone_name: "{{ my_domain }}"
relative_name: "#"
record_type: "SOA"
records:
- serial_number: "{{ new_serial }}"
register: dns_update
until: dns_update is succeeded
Ansible however fails with type mismatch error:
The full traceback is:
fatal: [localhost]: FAILED! => {
"changed": false,
"invocation": {
"module_args": {
...
"record_mode": "purge",
"record_type": "SOA",
"records": [
{
"serial_number": "2"
}
],
"relative_name": "#",
"resource_group": "my-rg",
"state": "present",
...
}
},
"msg": "implementation error: unknown type long requested for serial_number"
}
The error message is a bit confusing: should I understand it as Ansible trying to send 2 as long while Azure API expects string, or vice versa, Azure expects long but Ansible sends string?
Which type conversion should I apply?
Except above issue, is this proper way to update serial?

I bypassed this by switching from azure_rm_dnsrecordset Ansible module to az call, replacing the template as below:
- name: Increment DNS serial
command: >
az network private-dns record-set soa update
--resource-group "{{ my_rg }}"
--zone-name "{{ my_domain }}"
--serial-number "{{ new_serial }}"
This is accepted by Azure at least.
It does not update serial for me however. I can update other SOA paramters like refresh time or minimum TTL, but not the serial number; but this is different issue.

Related

How can I pass a varialbe set by the set_fact module to the Jinja2 template?

I have a role to setup NATS cluster,, I've used host_vars to define which node is the master node like below:
is_master: true
Then in the setup-nats.yml task, I used the following to extract the master node's IP address based on the host_var I've set and then used it as a variable for the Jinja2 template, however, the variable doesn't get passed down to the template and I get the `variable 'master_ip' is undefined.
- name: Set master IP
set_fact:
set_master_ip: "{{ ansible_facts['default_ipv4']['address'] }}"
cacheable: yes
when: is_master
- name: debug
debug:
msg: "{{ set_master_ip }}"
run_once: true
- name: generate nats-server.conf for the slave nodes
template:
src: nats-server-slave.conf.j2
dest: /etc/nats-server.conf
owner: nats
group: nats
mode: 0644
when:
- is_master == false
vars:
master_ip: "{{ set_master_ip }}"
notify: nats-server
The variable is used like below in the Jinja2 template:
routes = [
nats-route://ruser:{{ nats_server_password }}#{{ master_ip }}:6222
]
}
Questions:
Is this approach according to the best practices?
What is the correct way of doing the above so the variable is passed down to the template?
Test Output:
I'm using Molecule to test my Ansible and even though in the debug task the IP address is visible, it doesn't get passed down to the template:
TASK [nats : Set master IP] ****************************************************
ok: [target1]
skipping: [target2]
skipping: [target3]
TASK [nats : debug] ************************************************************
ok: [target1] =>
msg: 10.0.2.15
TASK [nats : generate nats-server.conf for the slave nodes] ********************
skipping: [target1]
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.errors.AnsibleUndefinedVariable: {{ set_master_ip }}: 'set_master_ip' is undefined
fatal: [target2]: FAILED! => changed=false
msg: 'AnsibleUndefinedVariable: {{ set_master_ip }}: ''set_master_ip'' is undefined'
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: ansible.errors.AnsibleUndefinedVariable: {{ set_master_ip }}: 'set_master_ip' is undefined
fatal: [target3]: FAILED! => changed=false
msg: 'AnsibleUndefinedVariable: {{ set_master_ip }}: ''set_master_ip'' is undefined'
Any help is appreciated, thanks in advance.
UPDATE: I suspect the issue has something to do with the variable scope being in the host context but cannot find a way to fix it ( I might be wrong though).
Far from being best practice IMO but answering your direct question. Your problem is not passing the variable to your template but the fact it is not assigned to all hosts in your play loop (and hence is undefined on any non master node). The following (untested) addresses that issue keeping the same task structure.
- name: Set master IP for all nodes
ansible.builtin.set_fact:
master_ip: "{{ hostvars | dict2items | map(attribute='value'
| selectattr('is_master', 'defined') | selectattr('is_master')
| map(attribute='ansible_facts.default_ipv4.address') | first }}"
cacheable: yes
run_once: true
- name: Show calculated master IP (making sure it is assigned everywhere)
ansible.builtin.debug:
msg: "{{ master_ip }}"
- name: generate nats-server.conf for the slave nodes
ansible.builtin.template:
src: nats-server-slave.conf.j2
dest: /etc/nats-server.conf
owner: nats
group: nats
mode: 0644
when: not is_master | bool
notify: nats-server
Ideas for enhancement (non exhaustive):
Select your master based on a group membership in the inventory rather than on a host attribute. This makes gathering the ip easier (e.g. master_ip: "{{ hostvars[groups.master | first].ansible_facts.default_ipv4.address }}"
Set the ip as a play var or directly inside the inventory for the node group rather than in a set_fact task.

Ansible - Access output of a shell command with with_items

I wrote a python script which gets executed via my ansible playbook and returns the following output via stdout:
- { username: ansible, admin: yes}
- { username: test, admin: no }
The output then should get saved in the variable "users" and with the "with_items" (or the newer "loop" conditional) I want to iterate through the variable in order to assign the right permissions for each user separately:
- name: execute python script
command: "python3 /tmp/parse_string.py --user_permissions={{ user_permissions }}"
register: output
- name: register
set_fact:
users: "{{ output.stdout }}"
- name: output
debug: msg="{{ users }}"
- name: Add user to group -admin
user:
name={{ item.username }}
groups=admin
append=yes
state=present
when: "item.admin == yes"
with_items: '{{users}}
However when launching the playbook it says that the variable "users" has no attribute "username".
TASK [create_users : output] ***************************************************
ok: [ansible] => {
"msg": "- { username: ansible, admin: yes }\n- { username: test, admin: no }\n- { username: test2, admin: no }"
}
TASK [create_users : Add user to group -admin ***************
fatal: [ansible]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'username'\n\nThe error appears to be in '***': line 29, 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: \n ^ here\n"}
Can anyone help me with this case?
BR
You are setting your users var to a string. It happens that this string is a yaml representation of a datastructure but ansible has no clue about that at this point.
To achieve your requirement, you need to parse that string as yaml and register the result. Luckily, there is a from_yaml filter for that purpose
You just have to modify your set_fact task as the following and everything should work as expected:
- name: register
set_fact:
users: "{{ output.stdout | from_yaml }}"

Ansible changes variable values when set

I have a series of variables set. Call them Hosts and Inthosts. Each has an appropriate value set, as seen in the debug output. When I try to assign the value of inthosts to hosts, it does not actually make them the same, it bolloxes it up changing the double quotes to single quotes and putting a "u" in front of each "item". It there a way to force Ansible to actually do a literal equals in this case without parsing the text? The text should just be treated as a string. In this case the "modified" value is being output to a file, and the change breaks things.
The plan was to use the default hosts, and override it with inthosts if the server in question should be using a different set of servers.
Default Variables Set
filebeat_kafka_hosts: '["x.compute-1.amazonaws.com:9093", "y.compute-1.amazonaws.com:9093"]'
filebeat_kafka_inthosts: '["x.compute-1.amazonaws.com:9093", "y.compute-1.amazonaws.com:9093", "z.compute-1.amazonaws.com:9093"]'
Ansible Code
- debug:
msg: "Hosts {{ filebeat_kafka_hosts }} "
- debug:
msg: "IntHosts {{ filebeat_kafka_inthosts }} "
- set_fact:
filebeat_kafka_hosts="{{ filebeat_kafka_inthosts }}"
- debug:
msg: "Inthosts -> hosts {{ filebeat_kafka_hosts }} "
Output (edited)
"msg": "Hosts [\"x.compute-1.amazonaws.com:9093\", \"y.compute-1.amazonaws.com:9093\"] " |
"msg": "IntHosts [\"x.compute-1.amazonaws.com:9093\", \"y.compute-1.amazonaws.com:9093\", \"z.compute-1.amazonaws.com:9093\"] "
set {"ansible_facts": {"filebeat_kafka_hosts": ["x.compute-1.amazonaws.com:9093", "y.compute-1.amazonaws.com:9093", "z.compute-1.amazonaws.com:9093"]}, "changed": false}
"msg": "Inthosts -> hosts [u'x.compute-1.amazonaws.com:9093', u'y.compute-1.amazonaws.com:9093', u'z.compute-1.amazonaws.com:9093'] "
Ansible is interpreting filebeat_kafka_inthosts and filebeat_kafka_hosts as lists. That gives you the 'u' characters before each item in your debug. The tasks below
- debug:
msg: "{{ item }}"
with_items: "{{ filebeat_kafka_hosts }}"
- debug:
msg: "{{ item }}"
with_items: "{{ filebeat_kafka_inthosts }}"
Would give you
TASK [debug] *******************************************************************
ok: [127.0.0.1] => (item=y.compute-1.amazonaws.com:9093) => {
"item": "y.compute-1.amazonaws.com:9093",
"msg": "y.compute-1.amazonaws.com:9093"
}
ok: [127.0.0.1] => (item=x.compute-1.amazonaws.com:9093) => {
"item": "x.compute-1.amazonaws.com:9093",
"msg": "x.compute-1.amazonaws.com:9093"
}
TASK [debug] *******************************************************************
ok: [127.0.0.1] => (item=x.compute-1.amazonaws.com:9093) => {
"item": "x.compute-1.amazonaws.com:9093",
"msg": "x.compute-1.amazonaws.com:9093"
}
ok: [127.0.0.1] => (item=y.compute-1.amazonaws.com:9093) => {
"item": "y.compute-1.amazonaws.com:9093",
"msg": "y.compute-1.amazonaws.com:9093"
}
ok: [127.0.0.1] => (item=z.compute-1.amazonaws.com:9093) => {
"item": "z.compute-1.amazonaws.com:9093",
"msg": "z.compute-1.amazonaws.com:9093"
}
Since your writing this line to a file, you shouldn't have to worry about it too much. The 'u' character is a side effect of the debug module. Writing the variable to a file would give the same result (although with single quotes instead of double).
- lineinfile:
path: some_file
line: "{{ filebeat_kafka_hosts }}"
some_file
['x.compute-1.amazonaws.com:9093', 'y.compute-1.amazonaws.com:9093', 'z.compute-1.amazonaws.com:9093']
If you really need double quotes, you can use the to_json filter
- lineinfile:
path: some_file
line: "{{ filebeat_kafka_hosts | to_json }}"
some_file
["x.compute-1.amazonaws.com:9093", "y.compute-1.amazonaws.com:9093", "z.compute-1.amazonaws.com:9093"]
Seems that casting the variable as a string also works. Thanks for the advice!

how to create Ansible playbook to obtain OS versions of the remote hosts?

I'm new to ansible. I have a requirement that requires me to pull OS version for of more than 450 linux severs hosted in AWS. AWS does not provide this feature - it rather suggests us to get it from puppet or chef.
I created few simple playbooks which does not run
---
- hosts: testmachine
user: ec2-user
sudo: yes
tasks:
- name: Update all packages to latest
yum: name=* state=latest
task:
- name: obtain OS version
shell: Redhat-release
playbook should output a text file with hostname and OS version. Any insight on this will be highly appreciated.
Use one of the following Jinja2 expressions:
{{ hostvars[inventory_hostname].ansible_distribution }}
{{ hostvars[inventory_hostname].ansible_distribution_major_version }}
{{ hostvars[inventory_hostname].ansible_distribution_version }}
where:
hostvars and ansible_... are built-in and automatically collected by Ansible
ansible_distribution is the host being processed by Ansible
For example, assuming you are running the Ansible role test_role against the host host.example.com running a CentOS 7 distribution:
---
- debug:
msg: "{{ hostvars[inventory_hostname].ansible_distribution }}"
- debug:
msg: "{{ hostvars[inventory_hostname].ansible_distribution_major_version }}"
- debug:
msg: "{{ hostvars[inventory_hostname].ansible_distribution_version }}"
will give you:
TASK [test_role : debug] *******************************************************
ok: [host.example.com] => {
"msg": "CentOS"
}
TASK [test_role : debug] *******************************************************
ok: [host.example.com] => {
"msg": "7"
}
TASK [test_role : debug] *******************************************************
ok: [host.example.com] => {
"msg": "7.5.1804"
}
In a structured way:
- hosts: all
become: no
vars:
output_file: os.csv
tasks:
- block:
# For permisison setup.
- name: get current user
command: whoami
register: whoami
run_once: yes
- name: clean file
copy:
dest: "{{ output_file }}"
content: 'hostname,distribution,version,release'
owner: "{{ whoami.stdout }}"
run_once: yes
- name: fill os information
lineinfile:
path: "{{ output_file }}"
line: "{{ ansible_hostname }},\
{{ ansible_distribution }},\
{{ ansible_distribution_version }},\
{{ ansible_distribution_release }}"
# Tries to prevent concurrent writes.
throttle: 1
delegate_to: localhost
Creates a comma separated file named os.csv in execution folder. You can use any variables you want editing line:.
Ansible already provides a lot of information about the remote host in the "hostvars" variable that is automatically available.
To see information of your host named "my_remote_box_name", e.g. do
- debug: var=hostvars['my_remote_box_name']
Some OS information is in
hostvars['my_remote_box_name']['ansible_lsb']
Which, for one of my ubuntu hosts would be along:
{
"hostvars['my_remote_box_name']['ansible_lsb']": {
"codename": "xenial",
"description": "Ubuntu 16.04.1 LTS",
"id": "Ubuntu",
"major_release": "16",
"release": "16.04"
}
You can just use those variables in your playbooks and templates, using the "{{ variable_name }}" notation.
- debug: msg="My release is {{ansible_lsb.release}}"
output:
"msg": "My release is 16.04"
For a couple of windows instances:
- debug:
msg:
- "ansible_distribution {{ hostvars[inventory_hostname].ansible_distribution }}"
- "major version {{ hostvars[inventory_hostname].ansible_distribution_major_version }}"
- "version {{ hostvars[inventory_hostname].ansible_distribution_version }}"
gives:
ok: [server1] => {
"msg": [
"ansible_distribution Microsoft Windows Server 2008 R2 Standard ",
"major version 6",
"version 6.1.7601.65536"
]
}
ok: [server2] => {
"msg": [
"ansible_distribution Microsoft Windows Server 2016 Standard",
"major version 10",
"version 10.0.14393.0"
]
}
"AWS does not provide this feature " - you can check file /etc/os-release to get details of aws instance.
For example
[ec2-user#ip-xx-xx-xx ~]$ cat /etc/os-release
NAME="Amazon Linux AMI"
VERSION="2016.03"
ID="amzn"
ID_LIKE="rhel fedora"
VERSION_ID="2016.03"
PRETTY_NAME="Amazon Linux AMI 2016.03"
ANSI_COLOR="0;33"
CPE_NAME="cpe:/o:amazon:linux:2016.03:ga"
HOME_URL="http://aws.amazon.com/amazon-linux-ami/"
- name: obtain OS version
shell: Redhat-release
register: result
- name: print OS version
debug: var=result.stdout

Why Ansible didn't see attribute in variable?

I have Ansible role "db" with simple task:
- name: Check repos
apt_repository: repo="{{ item.repo }}" state={{ item.state }}
with_items:
- "{{ apt_repos }}"
In /defaults/mail.yml:
apt_repos:
# Percona
- { state: present, repo: 'deb http://repo.percona.com/apt wheezy main', keyserver: 'keyserver.ubuntu.com', key: '1C4CBDCDCD2EFD2A', needkey: True }
- { state: present, repo: 'deb-src http://repo.percona.com/apt wheezy main', needkey: False }
When i try to run this ansible-playbook:
---
- hosts: test
roles:
- db
i see error:
fatal: [10.10.10.10] => One or more undefined variables: 'unicode object' has no attribute 'repo'
FATAL: all hosts have already failed -- aborting
But i have another role with same task and variable and it work perfectly. What's wrong?
You want to be doing this:
with_items: apt_repos
apt_repos is a list. By referencing it as - "{{ apt_repos }}" the extra - is turning it into a list of lists. You also don't need the quotes or braces in this case - those are pretty much just redundant in this type of situation.

Resources