I am trying to get the inventory_hostname from a hosts group I created.
What I am really trying to achieve is copying a file with a new name to a remote server, while its new name will be other remote servers hostname.
{{ inventory_hostname }} gives IP address and {{ ansible_hostname }} returns hostname as default variables but while doing this, I am stuck:
- name: Copy File
hosts: nagios
become: yes
tasks:
- shell: cp /home/xxx/test.txt /home/xxx/{{ item }}.txt
with_items: "{{ groups['dbs'] }}"
{{ item }} returns the IP address but I want the hostname, not the IP for this one. Any help would be appreciated.
Related
I'm using the following task to update /etc/hosts file on my inventory, but strangely I see a different alias being enetered.
- debug:
msg: "hostname = {{ inventory_hostname }} has ansible_fqdn = {{ ansible_fqdn }} and ansible_ipv4 = {{ ansible_default_ipv4.address }} and ansible_hostname = {{ ansible_hostname }}"
- name: Fix /etc/hosts removing the old hostname
become: true
throttle: 1
lineinfile:
state=present
dest=/etc/hosts
line="{{ ansible_default_ipv4.address }} {{ inventory_hostname }} {{ ansible_hostname }}"
regexp="^{{ ansible_default_ipv4.address }}"
The output of the debug statement is:
TASK [common : debug] ***********************************************************************************************************************************************************************************************
ok: [n2.open-electrons.com] => {
"msg": "hostname = n2.my-host.com has ansible_fqdn = n2.my-host.com and ansible_ipv4 = 192.168.0.203 and ansible_hostname = n2"
}
But when I SSH into the remote machine, I get to see the following in my /etc/hosts:
127.0.1.1 n2.my-host.com n1
127.0.0.1 localhost
Any ideas as to what might have gone wrong? I have another machine in the inventory which is configured as n1.my-host.com, but how come the alias is getting messed up?
My inventory file:
[master]
m1.my-host.com ansible_host=192.168.0.100
[node]
n1.my-host.com ansible_host=192.168.0.102
n2.my-host.com ansible_host=192.168.0.103
[k3s_cluster:children]
master
node
[all:vars]
ansible_python_interpreter=/usr/bin/python3
Any ideas as to what might have gone wrong? ... how come the alias is getting messed up?
Because of the hint within your example code
- name: Fix /etc/hosts removing the old hostname
it is assumed that your are going to change the hostnames of the Remote Nodes.
Therefore the issue might be caused by the fact that the content of ansible_hostname is a gathered fact on the Remote Node dynamically during runtime and by the setup module, whereby the inventory_hostname is a Special variable from the inventroy file on the Control Node.
inventory_hostname The inventory name for the ‘current’ host being iterated over in the play
inventory_hostname_short The short version of inventory_hostname
So to prevent a mismatch of information from different sources you could use the approach of single source of truth.
- name: Fix /etc/hosts removing the old hostname
become: true
lineinfile:
path: /etc/hosts
regexp: "^{{ ansible_default_ipv4.address }}"
line: "{{ ansible_default_ipv4.address }} {{ inventory_hostname }} {{ inventory_hostname_short }}"
state: present
Please notice that on the Remote Node itself can be also different sources for the hostname. Therefore you might need not only to change it on /etc/hosts but also in several different parts
- name: Set hostname to inventory_hostname
shell:
cmd: "nmcli general hostname {{ inventory_hostname }} && nmcli general hostname"
depending on your target system even
hostnamectl
I'm trying to assign hostname to a few hosts, gathering it from inventory.
My inventory is:
[masters]
master.domain.tld
[workers]
worker1.domain.tld
worker2.domain.tld
[all:vars]
ansible_user=root
ansible_ssh_private_key_file=~/.ssh/id_rsa
The code I'm using is:
- name: Set hostname
shell: hostnamectl set-hostname {{ item }}
with_items: "{{ groups['all'] }}"
Unfortunately, code iterate all items (both IPs and hostnames) and assigns to all 3 hosts the last item...
Any help would be much appreciated.
Don't loop: this is going through all your hosts on each target. Use only the natural inventory loop and the inventory_hostname special var.
Moreover, don't use shell when there is a dedicated module
- name: Assign hostname
hosts: all
gather_facts: false
tasks:
- name: Set hostname for target
hostname:
name: "{{ inventory_hostname }}"
I have 3 remote VMs and 1 ansible node.
I am getting the hostname of some VMs by running hostname command on those remote VMs through ansible shell module and registering that output in hostname_output variable.
Then I want to add those VM's IP (collected using gather_facts: True, {{ ansible_default_ipv4.address }} ) with their hostname and append it to a file temp_hostname on localhost, hence I am delegating the task to localhost.
But the issue is, when I see on console, the lineinfile module says that line has been added when the module executed for each node and delegated to localhost, but when I check the file on the localhost, only 1 entry is shown on localhost instead of 3.
---
- name: get hostnames of dynamically created VMs
hosts: all
remote_user: "{{ remote_user }}"
gather_facts: True
tasks:
- name: save hostname in variable, as this command is executed remotely, and we want the value on the ansible node
shell: hostname
register: hostname_output
- name: writing hostname_output in ansible node in file on ansible node
lineinfile:
line: "{{ ansible_default_ipv4.address }} {{ hostname_output.stdout }}"
dest: temp_hostname
state: present
delegate_to: 127.0.0.1
I even tried with copy module as specified in Ansible writing output from multiple task to a single file , but that also gave same result i.e 1 entry only.
---
- name: get hostnames of dynamically created VMs
hosts: all
remote_user: "{{ remote_user }}"
gather_facts: True
tasks:
- name: save hostname in variable, as this command is executed remotely, and we want the value on the ansible node
shell: hostname
register: hostname_output
- name: writing hostname_output in ansible node in file on ansible node
copy:
content: "{{ ansible_default_ipv4.address }} {{ hostname_output.stdout }}"
dest: /volume200gb/sushil/test/code_hostname/temp_hostname
delegate_to: 127.0.0.1
Finally when I used shell module with redirection operator, it worked as I wanted i.e 3 entries in file on localhost.
---
- name: get hostnames of dynamically created VMs
hosts: all
remote_user: "{{ remote_user }}"
gather_facts: True
tasks:
- name: save hostname in variable, as this command is executed remotely, and we want the value on the ansible node
shell: hostname
register: hostname_output
- name: writing hostname_output in ansible node in file on ansible node
shell: echo -e "{{ ansible_default_ipv4.address }} {{ hostname_output.stdout }}" >> temp_hostname
delegate_to: 127.0.0.1
I am calling this ansible-playbook get_hostname.yml using command:
ansible-playbook -i hosts get_hostname.yml --ssh-extra-args="-o StrictHostKeyChecking=no" --extra-vars "remote_user=cloud-user" -vvv
My hosts file is:
10.194.11.86 private_key_file=/root/.ssh/id_rsa
10.194.11.87 private_key_file=/root/.ssh/id_rsa
10.194.11.88 private_key_file=/root/.ssh/id_rsa
I am using ansible 2.1.0.0
I am using default ansible.cfg only, no modications
My question is why lineinfile and copy module didn't work? Did I miss anything or wrote something wrongly
I tried to reproduce your issue and it did not happen for me, I suspect this is a problem with your version of ansible, try with the latest.
That being said, I think you might be able to make it work using serial: 1, it is probably an issue with file locking that I don't see happening in ansible 2.3. I also think that instead of using a shell task to gather the hostname you could use the ansible_hostname variable which is provided as an ansible fact, and you can also avoid gathering ALL facts if all you want is the hostname by adding a task for that specifically. In the end, it would look like this:
---
- name: get hostnames of dynamically created VMs
hosts: all
serial: 1
remote_user: "{{ remote_user }}"
tasks:
- name: Get hostnames
setup:
filter: ansible_hostname
- name: writing hostname_output in ansible node in file on ansible node
lineinfile:
line: "{{ ansible_default_ipv4.address }} {{ ansible_hostname }}"
dest: temp_hostname
state: present
delegate_to: 127.0.0.1
I get inconsistent results using your first code block with lineinfile. Sometimes I get all 3 IPs and hostnames in the destination file and sometimes I only get 2. I'm not sure why this is happening but my guess is that Ansible is trying to save changes to the file at the same time and only one change gets picked up.
The second code block won't work since copy will overwrite the file unless content matches what is already there. The last host that runs will be the only IP/hostname in the destination file.
To work around this, you can loop over your play_hosts (the active hosts in the current play) and reference their variables using hostvars.
- name: writing hostname_output in ansible node in file on ansible node
lineinfile:
line: "{{ hostvars[item]['ansible_default_ipv4'].address }} {{ hostvars[item]['hostname_output'].stdout }}"
dest: temp_hostname
state: present
delegate_to: 127.0.0.1
run_once: True
with_items: "{{ play_hosts }}"
Or you can use a template with the same logic
- name: writing hostname_output in ansible node in file on ansible node
template:
src: IP_hostname.j2
dest: temp_hostname
delegate_to: 127.0.0.1
run_once: True
IP_hostname.j2
{% for host in play_hosts %}
{{ hostvars[host]['ansible_default_ipv4'].address }} {{ hostvars[host]['hostname_output'].stdout }}
{% endfor %}
The problem is here that there is multiple concurrent writes to only one file. That leads to unexpected results:
A solution for that is to use serial: 1 on your play, which forces non-parallel execution among your hosts.
But it can be a performance killer depending on the number of hosts.
I would suggest using another solution: instead of writing to only one file, each host delegation could write on its own file (here using the inventory_hostname value). Therefore, it will have no more concurrent writes.
After that, you can use the module assemble to merge all the file in one. Here is an example (untested):
---
- name: get hostnames of dynamically created VMs
hosts: all
remote_user: "{{ remote_user }}"
gather_facts: True
tasks:
- name: save hostname in variable, as this command is executed remotely, and we want the value on the ansible node
shell: hostname
register: hostname_output
- name: deleting tmp folder
file: path=/tmp/temp_hostname state=absent
delegate_to: 127.0.0.1
run_once: true
- name: create tmp folder
file: path=/tmp/temp_hostname state=directory
delegate_to: 127.0.0.1
run_once: true
- name: writing hostname_output in ansible node in file on ansible node
template: path=tpl.j2 dest=/tmp/temp_hostname/{{ inventory_hostname }}
delegate_to: 127.0.0.1
- name: assemble hostnames
assemble: src=/tmp/temp_hostname/ dest=temp_hostname
delegate_to: '{{ base_rundeck_server }}'
run_once: true
Obviously you have to create the tpl.j2 file.
I need to save two 2 IPs to a variable in a vars_file when launching ec2_instances, which are used later during deployment.
This is how I am saving a single server ip:
- name: Save server public IP to vars file
lineinfile: line="server_public_ip{{':'}} {{ item.public_ip }}"
dest="{{ansible_env.HOME}}/dynamic_ips_{{ec2_environment}}"
with_items: server.instances #server is registered in previous task
The output I have in dynamic_ips file is server_public_ip: xxx.xxx.xx.x
Now I have 2 servers launched and registered as servers.
I need to save this as server_public_ips: xxx.xx.x.xx , xxx.x.xx.x
I tried to declare an empty string and append ips to it, something like this, but I am getting errors.
set_fact:
ips: ""
set_fact:
ips: " {{ ips }} + {{ item.public_ip}} "
with_items: servers.instances #servers is registered in previous task
lineinfile: line="server_public_ips{{':'}} {{ ips }}"
dest="{{ansible_env.HOME}}/dynamic_ips_{{ec2_environment}}"
I think it can be done using lineinfile insertafter and regex.
Finally, I need this to do this in a different server,
- name: Restrict access to outside world
command: iptables INPUT {{ item }} ACCEPT
with_items: {{ server_public_ips }}.split(,) #grant access for each ip
command: iptables INPUT DROP
set_fact:
ips: " {{servers.instances | join(',') }} "
should actually work when servers.instances is a list.
I have a playbook that spins up a new droplet on DigitalOcean using the core module built into Ansible:
- name: Provision droplet on DigitalOcean
local_action: digital_ocean
state=present
ssh_key_ids=1234
name=mydroplet
client_id=ABC
api_key=ABC
size_id=1
region_id=2
image_id=3
wait_timeout=500
register: my_droplet
- debug: msg="ID is {{ my_droplet.droplet.id }}"
- debug: msg="Your droplet has the IP address of {{ my_droplet.droplet.ip_address }}"
I run this using (note the local argument):
ansible-playbook playbooks/create_droplet.yml -c local -i playbooks/hosts
My hosts file initially looks like this:
[production]
TBA
[localhost]
localhost
When the above playbook finishes I can see the debug information in STDOUT:
ok: [localhost] => {
"msg": "Your droplet has the IP address of 255.255.255.255"
}
Is there any way for this playbook to retain the my_droplet.ip_address variable and save the TBA in the hosts file instead of having to manually copy-pasta it there? I ask because I want to add this provisioning playbook to a ruby script that subsequently bootstraps the VPS with another playbook.
I am doing the same, having written a play that creates servers from a dict (approximately 53 servers at a go, dynamically creating a full test environment). To use a static hosts file, I add the following:
- name: Create in-memory inventory
add_host:
name: "{{ item.value.ServerName }}"
groups: "{{ item.value.RoleTag }},tag_Environment_{{ env_name }}"
when: item.value.template is defined
with_dict: server_details
- name: create file
become: yes
template:
src: ansible-hosts.j2
dest: "{{ wm_inventory_file }}"
The ansible-hosts.j2 template is simply:
{% for item in groups %}
[{{item}}]
{% for entry in groups[item] %}
{{ entry }}
{% endfor %}
{% endfor %}
I'm launching instances with ec2, and I originally wanted to do the same thing. I found some examples of using lineinfile and massaging it to the do the same thing as so:
- name: Launching new instances for {{ application }}
local_action: ec2 group={{ security_group }} instance_type={{ instance_type}} image={{ image }} wait=true region={{ region }} keypair={{ keypair }} vpc_subnet_id={{ subnet }} count={{ instance_count }}
register: ec2
- name: Add instance to local host group
local_action: lineinfile dest=ansible_hosts regexp="{{ item.public_ip }}" insertafter="\[{{ application }}\]" line="{{ item.public_ip }} ansible_ssh_private_key_file=~/ec2-keys/{{ keypair }}.pem" state=present
with_items: ec2.instances
But, I have to agree that it's generally not something you want to do. I found it to be quite a pain. I've since switched to using add_host and life is much better. BTW, application would be the value I used for the group name...
Is there any way for this playbook to retain the my_droplet.ip_address
variable and save the TBA in the hosts file instead of having to
manually copy-pasta it there?
You can retain the ip address of your new host by using the add_host module which allows you to dynamically change the in-memory inventory during an ansible-playbook run. This is useful for when you want to provision a new host and then configure it in a single playbook.
For example
local_action: >
add_host
hostname={{ my_droplet.droplet.id }}
groupname=launched
And then later in your playbook:
- name: Configure instance(s)
hosts: launched
tasks:
...
the second part of your questions:
... and save the TBA in the hosts file instead of having to
manually copy-pasta it there?
There is no built-in ansible way to write additions to the inventory file that is on disk. This is generally not something you want to do. In this case you would need to add it or use a dynamic inventory script to discover the host for future configuration runs.
You should use a dynamic inventory script for this. Using name to distinguish instances, you can subsequently refer to your droplets.
Check https://github.com/ansible/ansible/blob/devel/contrib/inventory/digital_ocean.py for an example script.