The Ansible Grain
The Ansible grain is Torque’s native support for orchestrating the execution of Ansible playbooks as part of a Torque blueprint. The referenced playbook can rely on vars or inventory-hosts that are dynamically provided by Torque, and then utilize them to perform configuration management, updates, a health check or any other flow that is executable from Ansible.
Tools and technologies
The following tools and technologies are installed out of the box on our agents in the Kubernetes pods and can be used when designing ansible playbook execution:
- dotnet
- git
- python3
- pip3
- ansible
- openssh-client
Ansible discovery
Auto-discovery of Ansible playbooks requires parsing the file content to differentiate playbooks from other YAML files. The validation process checks if a yaml
file contains the tasks
property, which is a key indicator of an Ansible playbook.
The process is optimized to ensure efficient and targeted scanning.
The structure of a playbook directory should be as follows:
base_folder/
vars/ # any directory name
variables_file # any filename without extension
playbook.yaml
A variables file might have the following structure:
variable1: test
variable2:
- value1
- value2
- value3
variable3:
test: "value1"
test2: "value2"
test3: '{{ name }}'
Below is an example of an auto-generated blueprint based on a discovered playbook and its variables file. This demonstrates how Torque can help you quickly scaffold a working Ansible blueprint:
spec_version: 2
description: Auto-generated Ansible Blueprint
inputs:
agent:
type: agent
variable1:
type: string
default: test
variable2:
type: list
default:
- value1
- value2
- value3
variable3:
type: dict
default:
test: "value1"
test2: "value2"
test3: '{{ name }}'
grains:
ansible_playbook:
kind: ansible
spec:
source:
store: ansible-repo
path: ansible/playbook.yml
agent:
name: '{{ .inputs.agent }}'
inputs:
- variable1: '{{ .inputs.variable1 }}'
- variable2: '{{ .inputs.variable2 }}'
- variable3: '{{ .inputs.variable3 }}'
# The outputs section below is a placeholder. Review and update based on your playbook's actual outputs.
outputs:
- result
Note
The outputs
section in the auto-generated blueprint is a placeholder. You should review and update it to match the actual outputs produced by your playbook. Since outputs depend on the playbook's content, they cannot be determined automatically during discovery.
Note
Always validate and adjust the generated blueprint YAML before running it, especially the outputs
section, to ensure it behaves as expected.
Installing Ansible Requirements
Torque is designed to streamline the installation of your module’s dependencies. It accomplishes this by automatically detecting and installing the requirements specified in a requirements.yaml
or requirements.yml
file. This file should be located in the root directory of your module.
For instance, if your module’s main file is located at ansible/my-module/main.yaml
, the corresponding requirements file should be placed in the same directory, i.e., ansible/my-module/
.
By adhering to this structure, you can ensure that Torque correctly identifies and installs all necessary requirements for your module.
Usage example
spec_version: 2
description: Ansible playbook example with pre-ansible-run scripts
inputs:
agent:
type: agent
grains:
ansible_playbook:
kind: ansible
spec:
source:
store: ansible-repo
path: ansible/playbook.yml
agent:
name: '{{ .inputs.agent }}'
command-arguments: "--vault-password-file ~/.vault_pass.txt"
scripts:
pre-ansible-run:
source:
store: my-script-repo
path: scripts/create_vault_file.sh
arguments: '{{ .params.vault_pass }}'
The Grain spec
source
The source section of an Ansible grain provides Torque with the information on where the Ansible playbook is stored and should be retrieved from. This could be either a direct source URL to an Ansible playbook YAML file, or it can be a reference from a Torque-connected git repository.
Example - direct:
ansible-grain:
kind: ansible
spec:
source:
path: https://github.com/quali/demo/blob/main/assets/ansible/install_apache2_ubuntu.yaml
Example - repository:
ansible-grain:
kind: ansible
spec:
source:
store: assets
path: assets/ansible/install_apache2_webgame_ubuntu.yaml
agent
Please see the grain agent for more details.
inputs
Inputs which are provided to the ansible grain will be used in the ansible command line as "extra-vars". The syntax is similar to any grain inputs.
Let's look at an example. In the example we have a blueprint with 2 grains: a VM and an ansible playbook to configure it.
Blueprint:
spec_version: 2
...
grains:
my_vm:
kind: terraform
spec:
source:
store: assets
path: terraform/vcenter/linux_vm
outputs:
- vm_ip
- vm_link
- vm_name
configure-vm:
depends-on: my_vm
kind: ansible
spec:
source:
store: assets
path: assets/ansible/configure.yaml
inputs:
- nodes: '{{ grains.my_vm.outputs.vm_ip }}'
- username: '{{ .params.vc_ubuntu_user }}'
- password: '{{ .params.vc_ubuntu_password }}'
info
Torque supports playbooks which use ansible roles.
Playbook:
- hosts: {{ nodes }}
tasks:
- name: configure virtual machine
azure_rm_virtualmachine:
username: "{{ username }}"
password: "{{ password }}"
…
Torque will create a JSON file containing the grain inputs under the path: /var/run/ansible/inputs/inputs.json.
{
"nodes": [...],
"username": ...,
"password": ...
}
The playbook will be executed with the extra vars in the following way:
ansible-playbook myplaybook.yaml --extra-vars "@/var/run/ansible/inputs/inputs.json"
inventory-file
Inventory file is a special grain section unique to the ansible grain, that allows you to provide in a YAML structured format, the content of the Ansible inventory file that will be generated for the Ansible playbook to use. For a deep understanding of the format of this file, please see the Ansible official documentation at https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html#inventory-basics-formats-hosts-and-groups. The general standard structure for such a file is as follows:
host_group:
hosts:
host1:
host1_var: host1_var_value
host2:
host2_var: host1_var_value
vars:
group_var: group_var_value
Depending on the Ansible packages used in the playbook, these host and group variables may be used automatically by the package step (in which case, they must be provided in a specific name, for example an “ansible_become: yes/no” group var will determine if the actions in the playbook need to be run as a different user from the host connection info.). Variables not defined on a host will automatically default to the values set in the vars section of the host_group. For example, the below playbook:
---
- gather_facts: no
hosts: all
tasks:
- name: Print Hello World
debug:
msg: "Hello World, my name is {{ person_name}}"
with the below inventory file:
inventory-file:
all:
hosts:
host1:
person_name: John
host2:
vars:
person_name: Doe
will result in this output
TASK [Print Hello World] *******************************************************
ok: [host1] => {
"msg": "Hello World, my name is John"
}
ok: [host2] => {
"msg": "Hello World, my name is Doe"
}
Any section of the “inventory-file:” can contain dynamic values from dependent grains, from the blueprint’s inputs or from Torque’s parameter storage. For more information, see Torque Templating engine. Example: Below is an example of a grains section of a blueprint, containing an Ansible grain:
grains:
hello_world_ansible:
kind: ansible
spec:
source:
store: assets
path: ansible/hello_world.yaml
agent:
name: my-torque-agent
inventory-file:
all:
hosts:
host1:
person_name: John
host2:
vars:
person_name: Doe
outputs
Ansible does not support outputs from playbooks natively so Torque adds this support on top of the ansible capabilities. Why would you need outputs from your ansible grain? Output from the ansible grain can be passed to another grain (ansible or not) or to become one of the blueprint outputs so can be provided to the blueprint consumer (the end user).
For this purpose, we have developed the Torque ansible module "export-torque-outputs".
The module accepts a dictionary of output names and values.
Usage example:
Playbook:
tasks:
- name: task1
configure_vm:
…
register: result1
…
- name: task2
do_something_else:
…
register: result2
- name: Export outputs
torque.collections.export_torque_outputs:
outputs:
output1: “{{ result1 }}”
output2: “{{ result2 }}”
info
The task which runs the "export_torque_outputs" module must be run on localhost.
Blueprint:
grains:
configure-vm:
kind: ansible
spec:
...
outputs:
- output1
- output2
If you wish to install the module locally to test it, please run ansible-galaxy collection install torque.collections
The module resides in the marketplace : https://galaxy.ansible.com/torque/collections
command-arguments
The command-arguments
field allows you to pass additional arguments to the ansible-playbook
command when executing the Ansible playbook. This gives you flexibility to customize the Ansible run as needed.
For example:
grains:
print_hello:
kind: ansible
spec:
source:
store: ansible-repo
path: ansible/playbook.yaml
agent:
name: '{{ .inputs.agent }}'
command-arguments: "--skip-tags deploy --force-handlers"
This will run ansible-playbook
with the --skip-tags deploy --force-handlers
flags appended to the command.
Here's a more complete example showing usage of command-arguments
along with other Ansible grain configurations:
spec_version: 2
description: Run an Ansible playbook with a local connection
inputs:
agent:
type: agent
grains:
print_hello:
kind: ansible
spec:
source:
store: ansible-repo
path: ansible/playbook.yaml
agent:
name: '{{ .inputs.agent }}'
command-arguments: "--skip-tags deploy --version"
env-vars: []
inventory-file:
localhost:
hosts:
127.0.0.1:
ansible_connection: local
For the Blueprint example above, the directory structure is:
playbook.yaml
roles/
hello/
tasks/
main.yaml
playbook.yaml
:
---
- hosts: localhost
roles:
- hello
roles/hello/tasks/main.yaml
:
---
- name: Print hello
debug:
msg: "hello"
This will execute the hello
role, which will print "hello" to the console.
Note that this is a very simple example, and roles can be much more complex, containing multiple tasks, handlers, variables, and other components. Additionally, roles can be shared across multiple playbooks or even distributed as reusable Ansible roles.
In this blueprint, when executing the print_hello
Ansible grain, the ansible-playbook
command will include the --skip-tags deploy --version
flags specified in the command-arguments
field.
The command-arguments
value can include any valid arguments that could be passed to ansible-playbook
. This allows customizing things like tags to run/skip, handling behavior, displaying version info, and more.
scripts
Torque allows you to run scripts before executing the Ansible playbook. This can be useful for various purposes, such as setting up the environment or performing prerequisite tasks.
To run a script before the Ansible playbook execution, you can define it under the scripts
section of the Ansible grain. The pre-ansible-run
key is used to specify the script to be executed.
Use Case: Creating a Vault Password File
One common use case is to create a file containing the Ansible Vault password. This password is required when working with encrypted data in Ansible playbooks. Here's an example of how you can pass a token from the parameter store as the Vault password:
spec_version: 2
description: Ansible playbook example with pre-ansible-run scripts
inputs: ...
grains:
ansible_playbook:
kind: ansible
spec:
source:
store: ansible-repo
path: ansible/playbook.yml
agent: ...
command-arguments: "--vault-password-file ~/.vault_pass.txt"
scripts:
pre-ansible-run:
source:
store: my-script-repo
path: scripts/create_vault_file.sh
arguments: '{{ .params.vault_pass }}'
In this example, the pre-ansible-run
script is defined under the scripts
section. The source
field specifies the location of the script (in this case, a Git repository named my-script-repo
), and the path
field specifies the relative path to the script within the repository.
The arguments
field allows you to pass arguments to the script. In this case, the value of {{ .params.vault_pass }}
is used, which retrieves the Vault password from the parameter store.
The script create_vault_file.sh
should create a file named .vault_pass.txt
in the current working directory and write the Vault password to it. Ansible will then use this file to decrypt the encrypted data in the playbook, as specified by the --vault-password-file
argument in the command-arguments
field.
Script Execution Environment
The pre-Ansible run scripts are executed within the same environment as the Ansible playbook itself. This means that any environment variables or files created by the script will be accessible to the Ansible playbook.
Additional Notes
- The scripts are executed in the order they are defined.
- If a script fails (non-zero exit code), the Ansible playbook execution will be aborted.
- Make sure to include any required dependencies or libraries for the scripts within the script repository or the Ansible repository.
By using pre-Ansible run scripts, you can enhance the flexibility and functionality of your Ansible playbooks within the Torque ecosystem.
on-destroy
The on-destroy
section allows you to define a cleanup or teardown process that will be executed when the grain is destroyed. This is particularly useful for ensuring that resources created during the grain's lifecycle are properly cleaned up, avoiding resource leaks or unintended costs.
The on-destroy
section mirrors the structure of the main Ansible grain configuration, allowing you to specify a separate playbook, inputs, inventory file, and other configurations for the teardown process.
Motivation
- Resource Cleanup: Ensure that resources created during the grain's execution are properly removed.
- Cost Management: Avoid incurring unnecessary costs by cleaning up unused resources.
- Consistency: Maintain a clean and predictable environment by defining explicit teardown steps.
Usage Example
Below is an example of a grain with an on-destroy
section:
inputs:
vm-name:
type: string
agent:
type: agent
grains:
vm:
kind: ansible
spec:
source:
store: ansible-repo
path: vcenter/vm_deploy.yaml
agent:
name: '{{ .inputs.agent }}'
env-vars:
- CUSTOM_TOKEN: '{{ .params.token }}'
inputs:
- name: '{{ .inputs.vm-name }}'
command-arguments: "--tags deploy"
on-destroy:
source:
store: ansible-repo
path: vcenter/vm_destroy.yaml
inputs:
- name: '{{ .inputs.vm-name }}'
command-arguments: "--tags destroy"
inventory-file:
localhost:
hosts:
127.0.0.1:
ansible_connection: local
scripts:
pre-ansible-run:
source:
store: scripts
path: scripts/run.sh
Explanation of the Example
source
: Specifies the playbook to be executed during the destroy phase. In this case, it uses the same playbook as the main grain but with different tags.inputs
: Inputs specific to the destroy phase, such as variables required for cleanup.command-arguments
: Additional arguments passed to theansible-playbook
command. Here, the--tags destroy
argument ensures only the tasks tagged withdestroy
are executed.inventory-file
: Defines the inventory file for the destroy phase, which can differ from the main execution phase.scripts
: Allows running pre-destroy scripts, such as setting up the environment or preparing for teardown.
Notes
- The
on-destroy
section is optional but highly recommended for grains that create external resources. - Ensure that the playbook and inputs used in the
on-destroy
section are designed to handle the cleanup process effectively. - If the
on-destroy
process fails, it may leave resources in an inconsistent state. Proper error handling and validation are crucial.
By using the on-destroy
section, you can ensure that your Ansible grains are not only effective during deployment as a configuration management, but also responsible during teardown, maintaining a clean and efficient environment.