How to set an Ansible tier-specific inventory variable? - ansible

Is it possible to create an Ansible inventory variable that isn't associated with an inventory host or group but the server tier the playbook is run on? I have an Ansible role that installs the libffi-dev package using APT, but I may want to install a different version of that package on each server tier. I've created a variable "libffi-dev_ver" for that purpose. I also have the inventory files "development", "staging", and "production" that correspond to each of my tiers.
My role's main task, which is called from my main site.yml playbook, checks that version variable exists prior to running the role:
# roles/misc_libs/tasks/main.yml
- include: check_vars.yml tags=misc_libs
- include: misc_libs.yml tags=misc_libs
check_vars.yml checks to ensure that the package version variable exists:
# roles/misc_libs_tasks/check_vars.yml
- name: check that required role variables are set
fail: msg="{{ item }} is not defined"
when: not {{ item }}
with_items:
- libffi-dev_ver
The misc_libs role actually uses that variable to install the package:
# roles/misc_libs/tasks/misc_libs.yml
- name: install libffi-dev
apt: >
pkg=libffi-dev={{ libffi-dev_ver }}
update_cache=yes
cache_valid_time=3600
become: True
My development inventory file looks like this:
# development
[webservers]
web01.example.com ansible_ssh_host=<ip_address>
[dev:children]
webservers
[webservers:vars]
libffi-dev_ver="3.1-2+b2"
When I run this command:
ansible-playbook -i development site.yml -l webservers
I get this Ansible error:
fatal: [web01.example.com] => error while evaluating conditional: not libffi-dev_ver
What is the correct way to declare a package versioning variable like this in Ansible? The variable's value depends on the server tier which indicates to me that it goes in an inventory file since inventory files are server tier-specific. But all inventory variables seem to have to be associated with a host or group. I've done that but the role still doesn't see the variable. I could add a task to the role that detects the server tier and uses a "when" conditional to set the variable accordingly but that solution seems ugly because if you're installing multiple packages in a role, you'd need three conditionals for each package version variable. I've looked through the Ansible documentation and read numerous blog posts on setting up multi-tier playbooks but I don't see this particular situation addressed. What's the right way to declare a tier-specific variable like this?

The problem was that the variable 'libffi-dev_ver' I declared is actually a Jinja2 identifier that must adhere to Python 2.x naming rules. The '-' (dash) is an invalid character according to these rules. Once I changed it to an '_' (underscore), I no longer got the error.
Also, the check_vars.yml playbook is actually unnecessary. There is an Ansible configuration variable error_on_undefined_vars which will cause steps containing an undefined variable to fail. Since it's true by default, I don't need to run check_vars.yml as all variables are already being checked.
One place to declare server tier-specific variables seems to be in a file in the group_vars directory that has the same name as the group which is named after that tier in your inventory file. So in my case my 'development' inventory file contains a 'dev' child group. This group contains the web server where I want to install the libffi-dev package. Therefore, I created a file 'group_vars/dev' and declared a variable in that file called 'libffi_dev_ver' which I can reference in my misc_libs.yml playbook.

I don't get what you are attempting to accomplish. Why is:
# roles/misc_libs/tasks/misc_libs.yml
- name: install libffi-dev
apt: >
pkg=libffi-dev={{ libffi-dev_ver }}
update_cache=yes
cache_valid_time=3600
become: True
when: libffi-dev_ver is defined
not enough?

Related

How to have multiple tasks under one role in Ansible?

I am setting up Ansible as a newbie. I would like to group a few tasks under an Nginx role.
See my folder structure
The only way I could get this to work is to use include statements in my playbooks... but that means I needed to start changing everything to relative paths since Ansible stopped being able to find things.
My playbook currently:
- name:
Install Nginx, Node, and Verdeccia
hosts:
all
remote_user:
root
tasks:
- include: ../roles/nginx/tasks/install.yml
- include: ../roles/nginx/tasks/create_node_config.yml
vars:
hostname: daz.com
How can I reference the sub task in the playbook to be something like
tasks:
- nginx.install
and still be within best practices.
Your use of roles so far is not in line with the norm or the idea of Ansible. From a purely technical point of view, it is quite possible that you have multiple task bundles in yml files that you include in the playbook.
If you want to include a specific task file from a role, you should better do this via the module include_role with the parameter tasks_from.
However, the workflow with roles usually looks different.
in the folder tasks should always be a file main.yml, this is called automatically, if simply the role is included, no matter over which way.
in this main.yml you can then add further control logic to include your yml files as required.
As it looks to me, for example, you always need to install, but depending on the use case you want to have different configurations.
create in your nginx role a file defaults/main.yml.
---
config_flavor: none
This initializes the config_flavor variable with the string none.
create in your nginx role a file tasks/main.yml.
---
- name: include installation process
include_tasks: install.yml
- name: configure node
include_tasks: create_node_config.yml
when: config_flavor == "node"
- name: configure symfony
include_tasks: create_symfony_config.yml
when: config_flavor == "symfony"
- name: configure wordpress
include_tasks: create_wordpress_config.yml
when: config_flavor == "wordpress"
The main.yml will be included by default when the role is applied.
This is where the installation is done first, then the proper configuration is done.
Which configuration should be done is defined by the variable config_flavor. In the listed example the values are node, symfony and wordpress. In all other cases the installation will be done, but no configuration (so in the default case with none).
include your role in the playbook as follows
---
- name: Install Nginx, Node, and Verdeccia
hosts: all
remote_user: root
vars:
hostname: daz.com
roles:
- role: nginx
config_flavor: node
At this point you can set the value for config_flavor. If you set wordpress instead, the tasks create_wordpress_config.yml will be included automatically, using the logic from tasks/main.yml.
You can find more about roles in the Ansible Docs.
There are many more possibilities and ways, you will get to know them in the course of your learning. Basically I would recommend you to read a lot in the Ansible docs and to use them as a reference book.
Please refer to bellow document. you can use "include_role" with "tasks_from" attribute. you don't need to provide full path of task files, Just file names.
https://docs.ansible.com/ansible/latest/collections/ansible/builtin/include_role_module.html
Example:
- name: Run tasks/other.yaml instead of 'main'
ansible.builtin.include_role:
name: myrole
tasks_from: other

Ansible-galaxy pre_tasks do not seem to execute before the roles

Here is my playbook.yaml
pre_tasks:
- name: Install required ansible-galaxy roles
local_action: shell ansible-galaxy install -r requirements.yaml
roles:
- role: gantsign.golang
vars:
golang_version: "1.16.3"
golang_install_dir: "/opt/go/{{ golang_version }}"
And my requirements.yaml
---
- src: gantsign.golang
However the pre_task never seems to be executed, so the role is not found and provisioning fails.
Any idea why?
ERROR! the role 'gantsign.golang' was not found in /home/pkaramol/ansible
The error appears to be in '/home/pkaramol/ansible/airflow-playbook.yaml': line 33, column 7, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
roles:
- role: gantsign.golang
^ here
It looks as if ansible is trying to find the roles before starting the playbook. For some reason I am pretty sure I had done this process before (i.e. having a pre_task with local_action taking over role installation but I cannot seem to get it working now...)
edit: I have confirmed that this is the case, because when leaving only the pre_task with the local role installation it actually runs
As you can read in that documentation: https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#using-roles-at-the-play-level
When you use the roles option at the play level, Ansible treats the roles as static imports and processes them during playbook parsing. Ansible executes your playbook in this order ...
So The role itself need to be found and load before the playbook start running.
So if you want to use galaxy on pre_tasks, try to dynamically include your role on tasks section of your play: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/include_role_module.html#include-role-module
pre_tasks:
- name: Install required ansible-galaxy roles
local_action: shell ansible-galaxy install -r requirements.yaml
tasks:
- name: include my roles
include_role:
name: gantsign.golang

Unable to set environment variables for use in ansible roles

I have playbook running fine when I have environment variables and tasks defined in one single playbook without roles.
But when I structure my project into roles, I see that running tasks is not finding the environment variables that are set from the original playbook.
Any hint how to set env variables so they are available for all roles inside a playbook?
Do I need to specify the environment variables in tasks/main.yaml file?, if yes how should do this exactly?
cat playbook.yaml
-
name: Deploy Team Services Playbook
hosts: all
environment:
PATH: "{{ ansible_env.PATH }}:/usr/local/bin"
KUBECONFIG: "{{ ansible_env.HOME }}/.kube/config/{{ ansible_env.USER }}.kubeconfig"
roles:
- prereq1_setup
- prereq2_k8s
prereq1_setup\tasks\main.yaml
- name: "Validate kubeconfig set?"
shell: echo {{ ansible_env.KUBECONFIG }}
failed_when: "'KUBECONFIG' not in ansible_env"
Above works if I don't use roles and directly add tasks below. currently, am getting error as
output:
|TASK [prereq1_setup : Validate kubeconfig set?] *****************************************************
fatal: [target1]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'KUBECONFIG'\n\nThe error appears to be in '/Users/testu/ansible/ansible-team/team_deploy/roles/prereq1_setup/tasks/main.yaml': line 57, column 9, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: \"Validate kubeconfig set?\"\n ^ here\n"}
Any hint how to set env variables so they are available for all roles inside a playbook?
The mechanism you are using is correct, and that environment variable is being correctly set, but it is set in the environment, and not in the ansible facts. Those facts are gathered before the playbook boots up, and thus your environment: happens after fact gathering, which explains why ansible_env does not contain it
You have a few paths forward, depending on what you prefer:
Explicitly re-gather facts inside the playbook (or even change your playbook to gather_facts: no and invoke setup: manually)
Stop looking for the environment in ansible_env, with the trust that it is actually there, and just use the commands which need the environment variable
Explicitly declare a separate fact to make that variable available to both the environment: and to the ansible tasks
If you want the first one, it would look like:
-
name: Deploy Team Services Playbook
hosts: all
gather_facts: no
environment:
whatever: goes here
pre_tasks:
- setup:
roles:
- and so forth
You can confirm the second via:
- name: ensure $KUBECONFIG is set
shell: echo $KUBECONFIG
And the third would look like:
- hosts: all
environment:
alpha: beta
vars:
alpha: beta
roles:
- # now {{ alpha }} is available to ansible and as $alpha in `commands:`

Error while using vars_prompt in ansible playbook [duplicate]

Ansible shows an error:
ERROR! no action detected in task. This often indicates a misspelled module name, or incorrect module path.
What is wrong?
The exact transcript is:
ERROR! no action detected in task. This often indicates a misspelled module name, or incorrect module path.
The error appears to have been in 'playbook.yml': line 10, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
---
- name: My task name
^ here
Reason #1
You are using an older version of Ansible which did not have the module you try to run.
How to check it?
Open the list of modules module documentation and find the documentation page for your module.
Read the header at the top of the page - it usually shows the Ansible version in which the module was introduced. For example:
New in version 2.2.
Ensure you are running the specified version of Ansible or later. Run:
ansible-playbook --version
And check the output. It should show something like:
ansible-playbook 2.4.1.0
Reason #2
You tried to write a role and put a playbook in my_role/tasks/main.yml.
The tasks/main.yml file should contain only a list of tasks. If you specified:
---
- name: Configure servers
hosts: my_hosts
tasks:
- name: My first task
my_module:
parameter1: value1
Ansible tries to find an action module named hosts and an action module named tasks. It doesn't, so it throws an error.
Solution: specify only a list of tasks in the tasks/main.yml file:
---
- name: My first task
my_module:
parameter1: value1
Reason #3
The action module name is misspelled.
This is pretty obvious, but overlooked. If you use incorrect module name, for example users instead of user, Ansible will report "no action detected in task".
Ansible was designed as a highly extensible system. It does not have a limited set of modules which you can run and it cannot check "in advance" the spelling of each action module.
In fact you can write and then specify your own module named qLQn1BHxzirz and Ansible has to respect that. As it is an interpreted language, it "discovers" the error only when trying to execute the task.
Reason #4
You are trying to execute a module not distributed with Ansible.
The action module name is correct, but it is not a standard module distributed with Ansible.
If you are using a module provided by a third party - a vendor of software/hardware or another module shared publicly, you must first download the module and place it in appropriate directory.
You can place it either in modules subdirectory of the playbook or in a common path.
Ansible looks ANSIBLE_LIBRARY or the --module-path command line argument.
To check what paths are valid, run:
ansible-playbook --version
and check the value of:
configured module search path =
Ansible version 2.4 and later should provide a list of paths.
Reason #5
You really don't have any action inside the task.
The task must have some action module defined. The following example is not valid:
- name: My task
become: true
I can't really improve upon #techraf answer https://stackoverflow.com/a/47159200/619760.
I wanted to add reason #6 my special case
Reason #6
Incorrectly using roles: to import/include roles as a subtask.
This does not work, you can not include roles in this way as subtasks in a play.
---
- hosts: somehosts
tasks:
- name: include somerole
roles:
- somerole
Use include_role
According to the documentation
you can now use roles inline with any other tasks using import_role or include_role:
- hosts: webservers
tasks:
- debug:
msg: "before we run our role"
- import_role:
name: example
- include_role:
name: example
- debug:
msg: "after we ran our role"
Put the roles at the right place inline with hosts
Include the roles at the top
---
- hosts: somehosts
roles:
- somerole
tasks:
- name: some static task
import_role:
name: somerole
hosts: some host
- include_role:
name: example
You need to understand the difference between import/include static/dynamic
I got this error when I referenced the debug task as ansible.builtin.debug
Causes a syntax failure in CI (but worked locally):
- name: "Echo the jenkins job template"
ansible.builtin.debug:
var: template_xml
verbosity: 1
Works locally and in CI:
- name: "Echo the jenkins job template"
debug:
var: template_xml
verbosity: 1
I believe - but have not confirmed - that the differences in local vs CI was ansible versions.
Local : 2.10
CI : 2.7
Explanation of the error :
No tasks to execute means it can not do the action that was described in your playbook
Root cause:
the installed version of Ansible doesn't support it
How to check :
ansible --version
Solution:
upgrade Ansible to a version which supports the feature you are trying to use
How to upgrade Ansible:
https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html#selecting-an-ansible-version-to-install
Quick instruction for Ubuntu :
sudo apt update
sudo apt install software-properties-common
sudo apt-add-repository --yes --update ppa:ansible/ansible
sudo apt install ansible
P.S: followed this path and upgraded from version 2.0.2 to 2.9
After upgrade, same playbook worked like a charm
For me the problem occurred with "systemd" module. Turned out that my ansible --version was 2.0.0.2 and module was first introduced in version 2.2. Updating my ansible to latest version fixed the problem.
playbook.yaml
- name: "Enable and start docker service and ensure it's not masked"
systemd:
name: docker
state: started
enabled: yes
masked: no
Error
ERROR! no action detected in task
etc..
etc..
etc..
- name: "Enable and start docker service and ensure it's not masked"
^ here
In my case this was fix:
ansible-galaxy collection install ansible.posix

include playbook with a variable name, which is defined on another host

I have some trouble getting my playbook to include another. I use a playbook to roll out a clean VM. After that, I'd like to run another playbook, to configure the VM in a certain way. I've added the new host to the inventory and have ssh access to it.
Our team has set up a project per servertype. I've retrieved the right path to the project in an early stage (running against localhost) and used set_fact to put it in "servertype_project".
I expect this to work (at the playbook-level, running against the new VM):
- name: "Run servertype playbook ({{ project }}) on VM"
vars:
project: "{{ hostvars['localhost']['servertype_project'] }}"
include: "{{ project }}/ansible/playbook.yml"
But it fails the syntax check with this message:
ERROR! {{ hostvars['localhost']['servertype_project'] }}: 'hostvars' is undefined
I can retrieve the right string if I refer to {{ hostvars['localhost']['servertype_project'] }} from within a task, but not from the level in which I can include another playbook.
Since the value is determined at runtime, I think set_fact is the correct way to store the variable, but that one is host-specific. Is there any way I can pass it along to this host as well? Or did I miss some global-var-like-option?
You are not missing anything. This is expected. Facts are bound to hosts and hostvars is not accessible in playbook scope.
There is no way to define a variable that would be accessible in the include declaration, except for passing it as an extra-vars argument in CLI. But if you use an in-memory inventory (which is a reasonable assumption), then calling another ansible-playbook instance is out of question.
I think you should rethink your flow and instead of including a dynamically-defined play, have statically-defined play(s) running against your in-memory inventory and include the roles conditionally.

Resources