How to change the content of a YAML file (.kubeconfig)? - ansible

I am learning Ansible & dealing with Kubernetes clusters. I would like to have an Ansible task which can change the value in .kube/config in my local Ansible host. For example, the .kube/config content looks like this:
apiVersion: v1
clusters:
- cluster:
server: xxx
name: xxx
contexts:
- context:
cluster: yyy
user: yyy
name: yyy
users:
- name: xxx
user: xxx
I basically would like to have an Ansible task to be able to do the following things:
change those values of xxx, yyy in the file .kube/config on ansible host.
append new content under each section of clusters, context & users if the values do not exist.
Is there a Kubernetes module or plugin I could directly use to achieve it? If not, could someone guide me how to achieve it?
==== I tried this ====
I tried :
- name: Update value to foo
replace:
path: ~/.kube/config
regexp: 'yyy'
regexp: 'foo'
delegate_to: localhost
When running the task, the file content doesn't change at all. Why? (Task has been executed based on logs)

Is there a kubernetes module or plugin I could directly use to achieve it?
Since your input file looks like valid YAML at a first glance, you could simply read it in via include_vars module – Load variables from files, dynamically within a task.
A minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Read kubeconfig
include_vars:
file: kubeconfig
name: kubeconfig
- debug:
msg: "{{ kubeconfig }}"
- name: Write kubeconfig
copy:
content: "{{ kubeconfig | to_nice_yaml }}"
dest: kubeconf # for testing with new file name
resulting into an output of
TASK [debug] ********
ok: [localhost] =>
msg:
apiVersion: v1
clusters:
- cluster:
server: xxx
name: xxx
contexts:
- context:
cluster: yyy
user: yyy
name: yyy
users:
- name: xxx
user: xxx
~/test$ cat kubeconf
apiVersion: v1
clusters:
- cluster:
server: xxx
name: xxx
contexts:
- context:
cluster: yyy
user: yyy
name: yyy
users:
- name: xxx
user: xxx
Then do your data manipulation steps like changing values or append new content. After that write the data structure back into a new file or overwrite the existing one.
By following this approach one can keep the use case simple and by using out-of-box functionality.
Further Q&A
Ansible write variables into YAML file
Ansible: Add dictionary item into a YAML file
Write variable to a file in Ansible
Further Documentation
Since it is not possible to change or update existing variables, but register new ones with the same name, you may also have a look into
update_fact module – Update currently set facts

Related

Include yaml object as child object with Ansible

Let's say I have a playbook that uses two roles like this.
---
- hosts: database
become: yes
roles:
- role: foo_role_one
- role: foo_role_two
And I have a file in /group_vars/database/vars.yml like this.
---
username: bar
The username variable is used by both foo_role_one and foo_role_two, but the values are not identical. I know I could include the variable directly as a child of - role: foo_role_one, but I am attempting to keep my playbooks clean with the variables for groups and and hosts in their own files. To accomplish this I'd like to do some like this.
---
- hosts: database
become: yes
roles:
- role: foo_role_one
'{{ foo_one }}'
- role: foo_role_two
'{{ foo_two }}'
And then in /group_vars/database/vars.yml have the following.
---
foo_one:
username: bar
foo_two:
username: baz
I cannot find a syntax with the yaml spec that will allow me to do this. I thought anchors and references would do it, but it appears that they do not span yaml files.
I think you'll find what you're looking for right in the roles documentation, which shows, for example in which two roles both want variables named dir and app_port, but the values for each role need to be different:
- hosts: webservers
roles:
- common
- role: foo_app_instance
vars:
dir: '/opt/a'
app_port: 5000
tags: typeA
- role: foo_app_instance
vars:
dir: '/opt/b'
app_port: 5001
tags: typeB
I believe that's the same situation you're asking about.

Conditionally add properties to task/module in Ansible Playbook

I'm developing a role where I want to launch a docker container, among the tasks in my role I have one using the docker_container module to do that:
- name: Launch docker container
docker_container:
name: abc
...
This works fine but now I want to have a variable that will define whether this container needs to be attached to a particular docker network.
If I require it is fine:
- name: Launch docker container
docker_container:
name: abc
networks:
- name: '{{ network_name_var}}'
...
But I want to allow the users to not define it, in which case no networks: ... property should be added.
I have found no easy way of achieving this, is there one?
Semantically I want something like this:
- name: Launch docker container
docker_container:
name: abc
{% if network_name_var is defined %}
networks:
- name: '{{ network_name_var}}'
...
{% endif %}
Here is a possible scenario you can use. The key points:
We keep your single network_name_var that is exposed to your user. I took for granted that this var could be either undefined, or empty.
We define the full network list definition dynamically if the var has a value set. This list stays unset otherwise.
We use the omit place holder to not define any networks in the module if need be.
- name: demo playbook for omit
hosts: localhost
tasks:
- name: set the list of networks for our container
# don't define anywhere else. it should only exist
# if network_name_var is set
set_fact:
my_networks:
- name: '{{ network_name_var }}'
when: network_name_var | default('') | length > 0
- name: make sure container is started
docker_container:
name: abc
networks: "{{ my_networks | default(omit) }}"

How to modularize Ansible playbook with multiple virtual hosts?

(EDIT: the question was thoroughly rewritten based on feedback in comments, aiming to follow a suggestion to use "more code, less talk")
I've accumulated some ansible playbooks for setting up our host(s). I want to start modularizing them. The current situation is roughly like below:
docker-registry-playbook.yml:
- hosts: foo
tasks:
- name: install docker
# ...
- name: copy config files
# ...
- name: start docker registry
# ...etc...
issue-tracker-playbook.yml:
- hosts: foo
tasks:
- name: install issue tracker
# ...etc...
tools-playbook.yml:
- hosts: foo
tasks:
- name: configure nginx virtual hosts
copy:
src: "{{ item }}"
dest: /etc/nginx/sites-available/
# ...
with_items:
- docker.example.com
- issues.example.com
- name: start nginx
# ...
- name: configure letsencrypt
# ...
My first idea was to split it into roles like below:
- hosts: foo
roles: webserver docker_registry issue_tracker
However, in such case, I don't know how to make the webserver role "auto-detect" the host names (virtual hosts) it should expose? I would like to be easily able to move e.g. docker_registry role to a different host (bar), and have the webserver auto-detect the change and update virtual hosts accordingly when I change the new playbook to:
- hosts: foo
roles: webserver docker_registry
- hosts: bar
roles: webserver issue_tracker
I would strongly prefer not having to explicitly list virtual hosts as parameters to webserver both on foo and bar; this would be too much duplication for me. Is it possible to have webserver autodetect names of "virtual hosts" from the other roles configured on the same server?
Or am I approaching the modularization in a wrong way, and the idiomatic approach in Ansible is to split it along some other axis when modularizing?
If not possible to do fully automatically, I'd like it to be done with minimal wiring; I could maybe grudgingly accept something like below, though it already looks much too long and ugly to me (but I still don't know how to do this in Ansible):
- hosts: foo
roles:
- docker_registry
- role: webserver
vars:
vhosts: [ "docker_registry" ]
I'm not sure if I understand your problem correctly. Why is it necessary to have a common variable virtual_hosts in all roles?
... Initially, I thought I could set a variable in each of service1 and service2, named e.g. virtual_hosts: and Ansible would merge the duplicated variables into one...
For example:
- hosts: foo
vars:
virtual_host_service1: docker.example.com
virtual_host_service2: issues.example.com
webserver_vm:
- "{{ virtual_host_service1 }}"
- "{{ virtual_host_service2 }}"
roles:
- webserver
- service1
- service2
You might want to set the variables in the playbook, or in the host_vars, or in many other places according to the precedens

How to use lines of a file as a variables in ansible playbook?

I am looking for a way that uses lines of file as variable in ansible playbook.
I have a playbook which uses number of variables approximate 15-20 variables. So it is very difficult for me to pass these 15 variables during runtime.
For the same I will create one file like:
**** variables.txt *****
Tomcat8
192.168.0.67
8080
8081
8082
8084
Playbook Sample:
---
- hosts: tomcat_server
vars:
tomcat_instances:
- name: foo
user: tomcatfoo
group: tomcatfoo
path: /srv/tomcatfoo
home: /home/tomcatfoo
service_name: foo#tomcat
service_file: foo#.service
port_ajp: 18009
port_connector: 18080
port_redirect: 18443
port_shutdown: 18005
So if there is any way where I can call the line number to pass the value of the variable in playbook it will be very helpful.
Thanks in advance!
I recommend using a structured way of managing variables like:
tomcat:
p_port: 8080
s_port: 8081
ip : 192.168.0.1
Then read the variables like
- name: Read all variables
block:
- name: Get All Variables
stat:
path: "{{item}}"
with_fileglob:
- "/myansiblehome/vars/common/tomcat1.yml"
- "/myansiblehome/vars/common/tomcat2.yml"
register: _variables_stat
- name: Include Variables when found
include_vars : "{{item.stat.path}}"
when : item.stat.exists
with_items : "{{_variables_stat.results}}"
no_log : true
delegate_to: localhost
become: false
After that, use like:
- name: My Tomcat install
mymodule:
myaction1: "{{ tomcat.p_port }}"
myaction2: "{{ tomcat.s_port }}"
myaction3: "{{ tomcat.ip }}"
Hope it helps
There are a few ways of doing it, but I would set up a Tomcat role and set the vars in tomcat_role/default/main.yml. If you ever need to change your Tomcat config it's done neatly from one place.
tomcat_role/default/main.yml example:
---
tomcat_user: tomcat
tomcat_group: tomcat
tomcat_path: "/path/to/tomcat"
When I want to call the Tomcat config I would declare it in my play like this:
- name: Set up Tomcat
hosts: your_host_here
become: yes
roles:
- tomcat_role
I can then call the vars from that role in my play like this:
user: {{tomcat_user}}
path: {{tomcat_path}}
Hope this helps.

How do I save an ansible variable into a temporary file that is automatically removed at the end of playbook execution?

In order to perform some operations locally (not on the remote machine), I need to put the content of an ansible variable inside a temporary file.
Please note that I am looking for a solution that takes care of generating the temporary file to a location where it can be written (no hardcoded names) and also that takes care of the removal of the file as we do not want to leave things behind.
You should be able to use the tempfile module, followed by either the copy or template modules. Like so:
- hosts: localhost
tasks:
# Create a file named ansible.{random}.config
- tempfile:
state: file
suffix: config
register: temp_config
# Render template content to it
- template:
src: templates/configfile.j2
dest: "{{ temp_config.path }}"
vars:
username: admin
Or if you're running it in a role:
- tempfile:
state: file
suffix: config
register: temp_config
- copy:
content: "{{ lookup('template', 'configfile.j2') }}"
dest: "{{ temp_config.path }}"
vars:
username: admin
Then just pass temp_config.path to whatever module you need to pass the file to.
It's not a great solution, but the alternative is writing a custom module to do it in one step.
Rather than do it with a file, why not just use the environment? This wan you can easily work with the variable and it will be alive through the ansible session and you can easily retrieve it in any steps or outside of them.
Although using the shell/application environment is probably, if you specifically want to use a file to store variable data you could do something like this
- hosts: server1
tasks:
- shell: cat /etc/file.txt
register: some_data
- local_action: copy dest=/tmp/foo.txt content="{{some_data.stdout}}"
- hosts: localhost
tasks:
- set_fact: some_data="{{ lookup('file', '/tmp/foo.txt') }}"
- debug: var=some_data
As for your requirement to give the file a unique name and clean it up at the end of the play. I'll leave that implementation to you

Resources