Ansible vmware_guest optional disk with Ansible Tower survey - linux

I have a playbook for the creation of a VM from a template in VMware ESXi 6.7. My playbook is below. I want to only configure the second (and possible subsequent) disks if the DISK1_SIZE_GB variable is > 0. This is not working. I've also tried using 'when: DISK1_SIZE_GB is defined' with no luck. I'm using a survey in Ansible Tower, with the 2nd disk configuration being an optional answer. In this case I get an error about 0 being an invalid disk size, or when I check for variable definition I get an error about DISK1_SIZE_GB being undefined. Either way, the 'when' conditional doesn't seem to be working.
If I hardcode the size, as in the first 'disk' entry, it works fine .. same if I enter a valid size from Ansible Tower. I need to NOT configure additional disks unless the size is defined in the Tower survey.
Thanks!
---
- name: Create a VM from a template
hosts: localhost
gather_facts: no
tasks:
- name: Clone a template to a VM
vmware_guest:
hostname: "{{ lookup('env', 'VMWARE_HOST') }}"
username: "{{ lookup('env', 'VMWARE_USER') }}"
password: "{{ lookup('env', 'VMWARE_PASSWORD') }}"
validate_certs: 'false'
name: "{{ HOSTNAME }}"
template: RHEL-Server-7.7
datacenter: Production
folder: Templates
state: poweredon
hardware:
num_cpus: "{{ CPU_NUM }}"
memory_mb: "{{ MEM_MB }}"
disk:
- size_gb: 20
autoselect_datastore: true
- size_gb: "{{ DISK1_SIZE_GB }}"
autoselect_datastore: true
when: DISK1_SIZE_GB > 0
networks:
- name: "{{ NETWORK }}"
type: static
ip: "{{ IP_ADDR }}"
netmask: "{{ NETMASK }}"
gateway: "{{ GATEWAY }}"
dns_servers: "{{ DNS_SERVERS }}"
start_connected: true
wait_for_ip_address: yes

AFAIK this can't be accomplished in a single task. You were on the right track with when: DISK1_SIZE_GB is defined if disk: was a task and not a parameter though. Below is how I would approach this.
Create two survey questions:
DISK1_SIZE_GB - integer - required answer - enforce a non-zero
minimum value such as 20 (since you're deploying RHEL)
DISK2_SIZE_GB - integer - optional answer - no minimum or maximum
value
Create disk 1 in your existing vmware_guest task:
disk:
- size_gb: "{{ DISK1_SIZE_GB }}"
autoselect_datastore: true
Create a new vmware_guest_disk task which runs immediately afterwards and conditionally adds the second disk:
- name: Add second hard disk if necessary
vmware_guest_disk:
hostname: "{{ lookup('env', 'VMWARE_HOST') }}"
username: "{{ lookup('env', 'VMWARE_USER') }}"
password: "{{ lookup('env', 'VMWARE_PASSWORD') }}"
validate_certs: 'false'
name: "{{ HOSTNAME }}"
datacenter: Production
folder: Templates
state: poweredon
disk:
- size_gb: "{{ DISK2_SIZE_GB }}"
autoselect_datastore: true
when: DISK2_SIZE_GB is defined

Related

Multiple with_items in an Ansible module block

I want to create multiple logical volumes with a variable file but it return a sintax error found character that cannot start any token, I have tried in different ways but still doesn't work
main.yml
---
- name: playbook for create volume groups
hosts: localhost
become: true
tasks:
- include_vars: vars.yml
- name: Create a logical volume
lvol:
vg: vg03
lv: "{{ item.var1 }}"
size: "{{ item.var2 }}"
with_items:
- { var1: "{{ var_lv_name }}", var2: "{{ var_lv_size }}" }
vars.yml
var_lv_name:
- lv05
- lv06
var_lv_size:
- 1g
- 1g
Use with_together. Test it first. For example,
- debug:
msg: "Create lv: {{ item.0 }} size: {{ item.1 }}"
with_together:
- "{{ var_lv_name }}"
- "{{ var_lv_size }}"
gives (abridged)
msg: 'Create lv: lv05 size: 1g'
msg: 'Create lv: lv06 size: 1g'
Optionally, put the declaration below into the file vars.yml
var_lv: "{{ var_lv_name|zip(var_lv_size) }}"
This creates the list
var_lv:
- [lv05, 1g]
- [lv06, 1g]
Use it in the code. The simplified task below gives the same results
- debug:
msg: "Create lv: {{ item.0 }} size: {{ item.1 }}"
loop: "{{ var_lv }}"
The previous answer it's totally correct but In my humble opinion we should be getting into the new way to do the things with loop and filters.
Here's my answer:
---
- name: playbook for create volume groups
hosts: localhost
gather_facts: no
become: true
vars_files: vars.yml
tasks:
- name: Create a logical volume
lvol:
vg: vg03
lv: "{{ item[0] }}"
size: "{{ item[1] }}"
loop: "{{ var_lv_name | zip(var_lv_size) | list }}"
In this answer you're using the new way to use loops with keyword loop and using filters like zip and turning the result into a list type for iteration in the loop.

Extend Volume Group using Ansible

I have trying to extend the VG via ansible passing the pvname by variable, however I really don't understand why is not working.
Below you can see my code.
Variable file:
new_disk:
- diskname: /dev/sdc
pvname: /dev/sdb1, dev/sdc1
vgname: datavg
lvm_settings:
- lv_name: datalv
lv_size: +100%FREE
fs_name: ansible_fs_test
lvpath: /dev/mapper/datavg-datalv
filesystem_type: ext4
tasks file:
include_vars: "{{ vm_name }}.yml"
- name: First disk partition settings
block:
- name: Create a new primary partition
community.general.parted:
device: "{{ item.diskname }}"
number: 1
state: present
with_items: "{{ new_disk }}"
register: partition_status
rescue:
- name: Debug messages to check the error
debug:
msg: "{{ partition_status }}"
- name: Extending the Volume Group
community.general.lvg:
vg: "{{ vgname }}"
pvs: "{{ pvname }}"
pvresize: yes
Below, you can see the error message:
TASK [resize_fs_linux : Extending the Volume Group] **********************************************************************************************************************************************************fatal: [10.1.33.225]: FAILED! => {"changed": false, "msg": "Device /home/icc-admin/ dev/sdc1 not found."}
Do you know have any idea why is not working?
I really appreciate your help and time
Best Regards,
For it works that way:
Variable file
diskname:
- /dev/sdb
- /dev/sdc
disks_settings:
- vgname: datavg
pvname:
- /dev/sdb1
- /dev/sdc1
lvm_settings:
- vgname: datavg
lv_name: datalv
lv_size: +100%FREE
fs_name: ansible_fs_test
lvpath: /dev/mapper/datavg-datalv
filesystem_type: ext4
Tasks file:
---
# tasks file for resize_fs_linux
- include_vars: "{{ vm_name }}.yml"
- name: First disk partition settings
block:
- name: Create a new primary partition
community.general.parted:
device: "{{ item }}"
number: 1
state: present
with_items: "{{ diskname }}"
register: partition_status
run_once: true
rescue:
- name: Debug messages to check the error
debug:
msg: "{{ partition_status }}"
- name: Extending the Volume Group
community.general.lvg:
vg: "{{ item.vgname }}"
pvs: "{{ item.pvname }}"
pvresize: yes
with_items: "{{ disks_settings }}"
- name: Increasing the filesystems
community.general.lvol:
vg: "{{ vgname }}"
lv: "{{ item.lv_name }}"
size: "{{ item.lv_size }}"
resizefs: true
with_items: "{{ lvm_settings }}"

Ansible loops and Azure resource dependencies

I'm using Ansible to provision resources to Azure. I'd like to have one task for each type of resource I want to deploy to Azure which loops through a list of dictionaries, so I can just add more dicts in case I want more resources provisioned. I'd like to define each resource variable only once.
The problem that arises with this is dependencies to other resources. Resource groups need to be provisioned before virtual networks, virtual networks before subnets and so on. Yet the information of the top level resources is still needed when provisioning the bottom level ones.
Here's the first attempt, with all of the required top level resource vars defined in the bottom level resource vars as well:
- hosts: localhost
connection: local
vars:
resourcegroups:
- name: "eh_test_rg01"
location: "westeurope"
- name: "eh_test_rg02"
location: "eastus"
virtualnetworks:
- name: "eh_test_vn01"
cidr: 10.15.0.0/22
resource_group: "eh_test_rg01"
- name: "eh_test_vn02"
cidr: 10.15.4.0/22
resource_group: "eh_test_rg02"
DMZ_subnets:
- name: "eh_test_dmzsn01"
cidr: 10.15.1.0/24
vnet: "eh_test_vn01"
location: "westeurope"
resource_group: "eh_test_rg01"
- name: "eh_test_dmzsn02"
cidr: 10.15.5.0/24
vnet: "eh_test_vn02"
location: "eastus"
resource_group: "eh_test_rg02"
app_subnets:
- name: "eh_test_appsn01"
cidr: 10.15.2.0/24
vnet: "eh_test_vn01"
location: "westeurope"
resource_group: "eh_test_rg01"
- name: "eh_test_appsn02"
cidr: 10.15.6.0/24
vnet: "eh_test_vn02"
location: "eastus"
resource_group: "eh_test_rg02"
gateway_subnets:
- name: "GatewaySubnet"
cidr: 10.15.0.0/24
vnet: "eh_test_vn01"
resource_group: "eh_test_rg01"
location: "westeurope"
- name: "GatewaySubnet"
cidr: 10.15.4.0/24
vnet: "eh_test_vn02"
resource_group: "eh_test_rg02"
location: "eastus"
tasks:
- name: Create resource Group
azure_rm_resourcegroup:
name: "{{ item.name }}"
location: "{{ item.location }}"
with_items:
- "{{ resourcegroups }}"
tags: resourcegroups
- name: Create vnet
azure_rm_virtualnetwork:
name: "{{ item.name }}"
resource_group: "{{ item.resource_group }}"
address_prefixes_cidr: "{{ item.cidr }}"
with_items:
- "{{ virtualnetworks }}"
tags: vnets
- name: Create subnets
azure_rm_subnet:
name: "{{ item.name }}"
resource_group: "{{ item.resource_group }}"
address_prefix: "{{ item.cidr }}"
virtual_network: "{{ item.vnet }}"
with_items:
- "{{ DMZ_subnets }}"
- "{{ app_subnets }}"
- "{{ gateway_subnets }}"
tags: subnets
As can be seen from above example, by the subnets dicts there's already 2 vars I have defined before. The deeper we go into the hierarchy, the more excess dict entries will come into play.
I tried to build the relationships into the variable structure, but ran into issues looping though the new variable structure. With_subelements worked fine for looping two lists of dictionaries, but it can't handle 3 or more.
- hosts: localhost
connection: local
vars:
resourcegroups:
- name: "eh_test_rg01"
location: westeurope
virtualnetworks:
- name: "eh_test_vn01"
cidr: 10.15.0.0/22
subnets:
- name: GatewaySubnet
cidr: 10.15.0.0/24
- name: eh_test_dmzsn01
cidr: 10.15.1.0/24
- name: eh_test_appsn01
cidr: 10.15.2.0/24
- name: "eh_test_rg02"
location: westeurope
virtualnetworks:
- name: "eh_test_vn02"
cidr: 10.15.4.0/22
subnets:
- name: GatewaySubnet
cidr: 10.15.4.0/24
- name: eh_test_dmzsn02
cidr: 10.15.5.0/24
- name: eh_test_appsn02
cidr: 10.15.6.0/24
tasks:
- name: Create resource Group
azure_rm_resourcegroup:
name: "{{ item.name }}"
location: "{{ item.location }}"
with_items:
- "{{ resourcegroups }}"
tags: resourcegroups
- name: Create vnet
azure_rm_virtualnetwork:
name: "{{ item.1.name }}"
resource_group: "{{ item.0.name }}"
address_prefixes_cidr: "{{ item.1.cidr }}"
with_subelements:
- "{{ resourcegroups }}"
- virtualnetworks
tags: vnets
# Blows up at this point, with_subelements does not support more lists than 2
- name: Create subnets
azure_rm_subnet:
name: "{{ item.2.name }}"
resource_group: "{{ item.0.name }}"
address_prefix: "{{ item.2.cidr }}"
virtual_network: "{{ item.1.vnet }}"
with_subelements:
- "{{ resourcegroups }}"
- virtualnetworks
- subnets
tags: subnets
What would be the best way to approach this problem? Do I need to define the vars differently, make some kind of helper tasks to create variable structures before running the task itself, use different loops or..?
As far as I know, I can't make references to other dict values which are contained in a list of dictionaries using YAML.
I would go totally other route and use arm templates, that a much better way of provisioning things on Azure, you can use native Ansible tasks for that as well:
http://docs.ansible.com/ansible/latest/azure_rm_deployment_module.html

Ansible loops for assigning sequential integers as hostnames

I'm new to Ansible. I have the following playbook that changes the hostname for a remote server:
---
- hosts: dbservers
remote_user: testuser1
become: yes
become_method: sudo
vars:
LOCAL_HOSTNAME: 'db01'
LOCAL_DOMAIN_NAME: 'ansibletest.com'
tasks:
# Checks and removed the existing occurences of <IP hostname FQDN> from /etc/hosts
- name: Remove occurences of the existing IP
lineinfile: dest=/etc/hosts
regexp='{{ hostvars[item].ansible_default_ipv4.address }}'
state=absent
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
# Adds the IP in the format <IP hostname FQDN> to /etc/hosts
- name: Add the IP and hostname to the hosts file
lineinfile: dest=/etc/hosts
regexp='.*{{ item }}$'
line="{{ hostvars[item].ansible_default_ipv4.address }} {{ LOCAL_HOSTNAME }} {{ LOCAL_HOSTNAME }}.{{ LOCAL_DOMAIN_NAME }}"
state=present
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
- name: Remove HOSTNAME occurences from /etc/sysconfig/network
lineinfile: dest=/etc/sysconfig/network
regexp='^HOSTNAME'
state=absent
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
- name: Add new HOSTNAME to /etc/sysconfig/network
lineinfile: dest=/etc/sysconfig/network
regexp='^HOSTNAME='
line="HOSTNAME={{ LOCAL_HOSTNAME }}.{{ LOCAL_DOMAIN_NAME }}"
state=present
when: hostvars[item].ansible_default_ipv4.address is defined
with_items: "{{ groups['dbservers'] }}"
- name: Set up the hostname
hostname: name={{ LOCAL_HOSTNAME }}.{{ LOCAL_DOMAIN_NAME }}
In this example, LOCAL_HOSTNAME is already assigned a value of db01. And in this scenario, the dbservers group has only one server:
[dbservers]
192.168.1.93
However, I also have 2 other servers that are designated to be webservers:
[webservers]
192.168.1.95
192.168.1.96
[dbservers]
192.168.1.93
The aim is to name them as web01.domain, web02.domain and so on.
As per the docs it seems that this could be achieved by using with_sequence.
My question is, is it possible (in Ansible) to use 2 variables in loops? Something along the lines of the pseudo-code below:
i=1
for host in webservers:
open host(/etc/hosts):
add "IP<space>HOSTNAME{i}<space>"<space>"HOSTNAME{i}.FQDN"
i++
Could this be achieved using playbooks or am I approaching the issue in an wrong way?
Generate indexed hostname first, define it as hostfact and use it later to fill other servers' hosts files.
- hosts: webservers
gather_facts: no
tasks:
- set_fact:
indexed_hostname: "{{ 'web{0:02d}'.format(play_hosts.index(inventory_hostname)+1) }}"
- hosts: dbservers
gather_facts: no
tasks:
- debug:
msg: "{{ hostvars[item].indexed_hostname }}"
with_items: "{{ groups['webservers'] }}"
Also there is such thing as with_indexed_items.

Using ansible launch configuration module ec2_lc and securitygroup names versus id

I want to accomplish the following in aws ec2:
Create security groups using ansible module ec2_group.
Create a launch configuration using ansible module ec2_lc and attach a security group created earlier.
Now, i want to use the security group names instead of id's because i want to be able to recreate the whole infrastructure with ansible if needed.
Recreating security groups will cause the id of the group to be different.
But the ec2_lc module only accepts security group id's.
Is there any way i can map a security group id to a name?
I am defining security groups like this:
- name: create ec2 group
ec2_group:
name: "{{ item.name }}"
description: "{{ item.description }}"
vpc_id: "{{ item.vpc_id }}"
region: "{{ item.region }}"
state: present
rules: "{{ item.rules }}"
rules_egress: "{{ item.rules_egress }}"
register: sg
The launch configuration code looks like this:
- name: Create Launch Configuration
ec2_lc:
region: "{{ item.region }}"
name: "{{ item.name }}"
image_id: "{{ item.image_id }}"
key_name: "{{ item.key_name }}"
security_groups: "{{ item.security_groups }}" # how can i refer to specific group_id based on a group name?
instance_type: "{{ item.instance_type }}"
user_data: "{{ item.ec2_user_data }}"
instance_profile_name: "{{ item.instance_profile_name }}"
assign_public_ip: "{{ item.assign_public_ip }}"
Use the ec2_group-facts to query the security groups by name:
- ec2_group_facts:
filters:
group-name:
- "{{ sg.name }}"
register: ec2sgs
- debug:
msg: "{{ ec2sgs.security_groups | map(attribute='group_id')| list }}"
With some tribute to this question, you can try this:
- name: Create Launch Configuration
ec2_lc:
...
security_groups: "{{ sg.results | selectattr('item.name','equalto',item) | join('',attribute='group_id') }}"
...
You can write a filter, that can make an aws api call for you dynamically.
For instance I have something like this in my vars/main.yml
public_sg_id: "{{ 'Public' |get_sg(public_vpc_id, aws_region) }}"
Here is the code for get_sg filter.
import boto.ec2
from ansible import errors
def get_sg(name, vpc_id, region):
connect = boto.ec2.connect_to_region(region)
filter_by = {
"tag-key": "Name",
"tag-value": name,
"vpc-id": vpc_id
}
sg_groups = connect.get_all_security_groups(filters=filter_by)
if len(sg_groups) == 1:
return sg_groups[0].id
elif len(sg_groups) > 1:
raise errors.AnsibleFilterError(
"Too many results for {0}: {1}".format(
name, ",".join(sg_groups)
)
)
else:
raise errors.AnsibleFilterError(
"Security Group {0} was not found".format(name)
)

Resources