Introduction
This is a collection of filters, reusable includes and other useful components for use with Ansible.
The latest version can be obtained from github.
The recommended way of using this collection is to include it as a git submodule into your Ansible configuration(s), and adjust plugin search paths in ansible.cfg
.
Example playbooks to demonstrate how to use the extensions provided here are included in the playbooks
directory. To run all of them you can execute ansible-playbook playbooks/all.yml
The general assumption for our deployments is that we do not have valid directory sources to pull information from when setting up a new infrastructure, and that directory sources may be part of our infrastructure. To some extend this can be done with just a plain hosts inventory and shared group variables - as was originally done in our deployments due to shortcomings of Ansible at the time. A better approach nowadays is a custom inventory plugin - this allows easier validation of configuration data as well as augmentation from partial additional directory sources.
The inventory plugin still transforms configuration data into a set of variables, to be consumed by the other roles - so the majority of our components can also be used without our inventory plugin, by manually configuring the variables specified in the role documentation.
Both this role and the inventory plugin also support transforming a more generic configuration syntax into platform specific data structures or actions - making it easy to mix different Linux distributions or even Windows and MacOS systems with minimal platform specific code in your playbooks. Many of the reusable includes in this role are for this purpose. Our basic-host role packages many of those into a configurable variant to bring the basic functionality of any host into the expected state - this includes management access, network configuration, basic package installation as well as hooks into custom adjustments.
Deployment strategy
Recommended Ansible setup
The following example shows the recommended directory structure for the repository containing the Ansible configuration:
├── .gitignore ├── ansible.cfg ├── group_vars │ ├── all.yml │ ├── dotnet_workers.yml │ ├── linux_workers.yml │ ├── macos_workers.yml │ └── windows_workers.yml ├── host_vars │ ├── mac01.yml │ ├── win03.yml │ └── ubuntu01.yml ├── local_roles │ └── README ├── playbooks │ ├── access.yml -> ../roles/data-utilities/playbooks/access.yml │ ├── files │ │ ├── authorized_keys.d │ │ │ ├── user_one │ │ │ └── user_two │ │ ├── cleanup-script.sh │ │ ├── config.json │ │ └── sample.service │ ├── handlers │ │ └── main.yml │ ├── site.yml │ ├── tasks │ │ ├── custom-task1.yml │ │ └── custom-task2.yml │ └── templates │ ├── sample-template1.j2 │ └── sample-template2.j2 ├── roles │ ├── backup │ │ ├── files │ │ └── [..] │ ├── basic-host │ │ ├── files │ │ └── [..] │ ├── data-utilities │ │ ├── bin │ │ └── [..] │ [..] ├── README.org └── site.yaml
.gitignore
*.pyc *.retry /playbooks/host-keys ansible.log
ansible.cfg
This is a suggested minimal ansible.cfg
, though you might want to start from an ansible example for the included comments:
[defaults] inventory = site.yaml gather_timeout = 30 roles_path = local_roles:external_roles:roles inventory_plugins = roles/data-utilities/inventory_plugins filter_plugins = roles/ansible-data-utilities/filter_plugins [inventory] enable_plugins = site_yaml unparsed_is_failed = True [ssh_connection] pipelining = True scp_if_ssh = smart [site_yaml] dynamic_groups = True
The following settings for the default
section often also make sense:
remote_tmp = ~/.ansible/tmp local_tmp = ~/.ansible/tmp forks = 20 gathering = smart
group_vars, host_vars
A list of files containing group variables, which typically at least should contain all.yml
. See Ansible inventory introduction for more details.
While files without file extension work using either .yml
or .yaml
is strongly recommended.
external_roles
A symlink to a directory of checked out roles or unpacked roles. If strict version checking is not required this is an easy approach for simple setups as well as for development. For controlled setups the local_roles
approach described in the next section typically is better.
local_roles
A placeholder directory for temporarily storing or linking roles to for development. It can be created empty with a placeholder README similar to the following:
This directory is for holding roles during development, before importing a new release into the roles subfolder. This directory can either hold subdirectories of direct git clones, or symlinks to role directories. Typically having a clone of the upstream role somewhere, and just linking to it is the easier way: ln -s /path/to/roles/sample-role . or from the ansible root: ln -s /path/to/roles/sample-role local_roles/ Don't forget to remove links/directories when importing a newly released role!
playbooks
This directory contains the infrastructure playbooks and related files.
roles
This directory holds roles versioned together with the Ansible configuration. There are two approaches for managing this:
- synchronising from a git source tree or a release tarball
- adding roles as git submodules
The submodule approach is recommended.
README
A README in the preferred markup format for the project with at least basic information on how to run Ansible and update roles is recommended.
site.yaml
The site configuration. See the inventory plugin documentation for details.
Configuring a host for ansible
New hosts - no matter the OS - should start out in a small configuration, ideally without much differences between deployments. To achieve that (and to reduce the manual labour) infrastructure for unattended installations should be used whenever possible.
For Linux the recommended setup is netbooting with AutoYAST or Kickstart, with management keys pre-loaded for the root user. This allows easy passwordless running of the access playbook directly after installation.
For Windows the recommended setup is a customised ISO with management user pre-generated, SSH pre-installed and SSH keys pre-loaded. Setting up the management user with correct permissions during the initial ansible run is slightly more complicated on Windows - so making sure this user exists during bootstrap simplifies things.
This page collects information related to managing the automated installation systems, as well as scripts to quickly take over a system which could not be bootstrapped automatically.
Generic initial setup via SSH
macOS SSH setup
On macOS images without custom provisioning remote login needs to be enabled. For modern macOS versions this typically also requires granting full disk access privileges:
% sudo launchctl load -w /System/Library/LaunchDaemons/ssh.plist % sudo systemsetup -setremotelogin on
Ansible will use python3, which is not installed per default, and comes with the Command Line Developer Tools. If not installed a first Ansible run may fail with an interactive prompt on the desktop to install those. After finishing the installer the Ansible run should succeed.
Windows SSH setup
The easiest way for managing Windows via Ansible is by just making sure SSH is available on the Windows server. This can be done by a custom scripted Windows installation, or by following this section.
The deploy-ssh powershell script can be used both for installing SSH during an unattended Windows installation (with the SSH installer provided on the media), as well as enable it later on.
It will search for the installer OpenSSH-Win64.zip
in c:\ci
, the current directory and in the current users Download directory, in that order. If not found it will try to download it from the Win32-OpenSSH release page. It should be downloaded and executed in a powershell session with elevated privileges:
> Invoke-WebRequest -Uri https://raw.githubusercontent.com/aardsoft/ansible-data-utilities/master/doc/deploy-ssh.ps1 -OutFile deploy-ssh.ps1 > ./deploy-ssh.ps1
If the execution policy for scripts has not been changed from the default it may be necessary to bypass it for running the script:
> powershell -executionpolicy bypass -File ./deploy-ssh.ps1
On old Windows versions it may be required to force Powershell to use recent TLS mechanisms for the download - set the following if above throws SSL errors:
> [Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"
If using RDP to connect to the server the connection may get terminated while running deploy-ssh.ps1
, though SSH access should be reachable from that point:
$ ssh -o PubkeyAuthentication=no Administrator@windows-system
Depending on the Windows version it may now be possible to also run an access playbook, or manual setup of SSH keys may be required. Running the playbook will handle setting up authorized keys as on other platforms - when manually installing an authorized_keys
file for access setup note that the default configuration uses ProgramData\ssh\administrators_authorized_keys
for any users in the administrators
group. For other users the location is in .ssh\authorized_keys
in the root of their home directory.
Windows custom image
Creating a custom image allows adding or removing components to the regular Windows installation, and pre-load scripts required for taking over a host by Ansible.
The windows-image powershell script can be used to mostly automate image creation. While it will ask for any missing parameters it is recommended to create an answer file. The following example contains all possible variables:
$iso_path="c:\Users\example\Documents\Windows.iso" $new_iso_path="c:\Users\example\Documents\Windows_unattended.iso" $virtio_iso_path="c:\Users\example\Documents\virtio.iso" $image_path="c:\unattended_image" $ci_source_path="c:\ci" $ci_target_dir="\ci" $autounattend_path="c:\Autounattend.xml" $os_index=6 $wait_for_manual=$true $remove_apps=@( "Microsoft.BingWeather" "Microsoft.Getstarted" )
It should be saved as powershell script (with ending .ps1
), and can be passed to the windows-image
script either as first argument, or through the environment variable CI_ISO_PARAMETERS
.
autounattend_path
is the path to a that can be used as Autounattend.xml
for an unattended installation. If this variable is not set, but the CI script directory described below contains Autounattend.xml
this will be used without a need for configuration.
os_index
is the index of the Windows variant to be used for multi variant source images - if unspecified the script will list all available variants before prompting. The specified index will be converted to a single variant install.wim. This setting is only relevant on Windows media containing an install.esd file - for newer media shipping a multi variant install.wim the file will be used directly, and variant selection happens at installation time.
remove_apps
is a list of preinstalled Windows applications that should be removed from the image. The list of applications in mount_path
can be obtained with Get-AppxProvisionedPackage -path mount-path|Format-Table
.
wait_for_manual
makes the script pause before unmounting boot and install images, allowing manual injection of components.
ci_source_path
is the path to a directory containing the files to be injected into the image. A detailed suggestion on how to use this directory is in the section below.
ci_target_dir
is the path to a directory relative to the mountpoint of the install media to place the files. \ci
would place them at the root of the install drive after installation (typically c:\ci
)
virtio_iso_path
is the path to an ISO image containing virtio drivers. If this is specified (and exists) all drivers on this ISO will be injected into the new image. Newer Windows versions require signed drivers - it is recommended to use WHQL signed drivers. One option for this is to use the drivers extracted from rocky linux.
The script expects a Windows ISO file, which can be created by
- downloading a Wndows 11 ISO
- using the Windows 11 media creation tool
- downloading Windows 11 images through the Windows insider program
- download Windows images through uupdump
At the time of writing Arm64 images can only be obtained via the insider program or uupdump. The same four options exists for Windows 10, links should be trivially obtainable by searching the microsoft site.
Windows unattended image
Creating an unattended Windows image requires following the stops for creating a custom Windows image in the section above first.
Next the Windows ADK needs to be installed for creating an answer file. Note that creating Windows 10 or 32-bit images may require using legacy ADK versions. Selecting Deployment Tools
in the features list should be sufficient.
Filters
For generic information on filters please read the Ansible filter documentation.
boolconv
This filter takes most of the inputs possible in Ansible to look like a bool, and converts them to a controlled string representation. Recognised inputs are all data which become one of true, false, yes, no, on, off, 0 or 1 when passed through Ansible variable expansion into Python.
This solves the problem of Ansible auto-converting yes/no to booleans, and eliminates the chance of having malformed values in configuration files due to forgotten quotes in the variable declaration.
A short example playbook demonstrates this problem with a template containing both defaults and sanitised values:
- template: src: boolconv.j2 dest: .out/boolconv vars: var1: yes var2: "yes" var3: true
{{var1}} {{var1|boolconv("yesno")}} {{var2}} {{var2|boolconv("yesno")}} {{var3}} {{var3|boolconv("yesno")}}
The output shows that Ansible converts yes
to a bool, which then becomes True
when converted back to a string.
True yes yes yes True yes
Note that this also can be a problem for configuration files accepting true/false, but expect them to be lower case. To avoid this issue as well this filter allows controlling case of the output as well. Valid arguments are:
- truefalse
- TrueFalse
- TRUEFALSE
- yesno
- YesNo
- YESNO
- onoff
- ONOFF
- int
- enableddisabled
The default if no argument is specified is truefalse
. With a wrong argument as well as inputs not recognisable as one of the values listed above the return value is undefined (typically results in an empty string in Ansible).
ipv6_explode
Explode an IPv6 address, i.e., transforms something like 2a0b:5c81:300:9254::1
into 2a0b:5c81:0300:9254:0000:0000:0000:0001
. This will always return the IP address, but accepts arguments with and without prefix.
Includes for other roles
Generic includes
add_ssl_files
This include handles management of SSL keys and certificates. It can pull keys or certificates from a password store, or generate missing files.
It takes the following variables:
- ssl_size, the size of the SSL key. Defaults to 2048
- ssl_key, the path to the key file
- ssl_certificate, the path to the certificate file
- ssl_key_passdb, a passdb entry containing the SSL key
- ssl_certificate_passdb, a list of passdb entries containing SSL certificates. This allows easy inclusion of intermediaries as well.
If ssl_certificate
is set without a passdb source a self signed certificate will be generated, if it is missing on disk. This is mostly useful when using services like letsencrypt - this allows bringing up the services with temporary certificates, to be replaced as soon as a system is operable enough to request proper certificates.
Example usage:
- include_role: name: data-utilities tasks_from: add_ssl_files vars: ssl_key: /etc/ssl/private/test.key ssl_certificate: /etc/ssl/private/test.pem ssl_size: 4096
When using passdb entries for key and certificates it also is possible to have keys and certificates combined in a single file, specified by ssl_key
:
- include_role: name: data-utilities tasks_from: add_ssl_files vars: ssl_key: /etc/ssl/private/test.pem ssl_key_passdb: test-key ssl_certificate_passdb: - test-cert - intermediary
Using a single file without passdb entries will result in certificate generation being skipped, which quite likely will lead to failures.
configure_gpg_users
This include configures gpg for one or more users, with optional pkcs11-scd support, expecting configuration in a variable named gpg_users
. The basic-host role will automatically set this up if the variable is defined.
Sample configuration for user accounts user1
and user2
:
gpg_users: user1: gnupg-pkcs11-scd: gpg-agent.conf.extra: - allow-emacs-pinentry - debug-level advanced user2: gpg-agent: debug: advanced verbose: gpg.conf: - no-secmem-warning
- dirmngr.conf
A lost of options to use in
dirmngr.conf
. If omitted the following default configuration will be used:keyserver hkps://keys.openpgp.org
- dirmngr.conf.extra
A list of extra options to add to
dirmngr.conf
- gpg.conf
A list of options to use in
gpg.conf
. If omitted the following default configuration will be used:keyserver-options auto-key-retrieve use-agent no-secmem-warning
- gpg.conf.extra
A list of extra options to add to
gpg.conf
- gpg-agent
A list of flags changing the default configuration.
debug
takes the values ofdebug-level
described for gpg-agent. If gnupg-pkcs11-scd is used anything other thannone
here will enable debug logging there as well.verbose
will enable verbose for gpg-agent and gnupkc-pkcs11-scd unless set tofalse
.
- gpg-agent.conf
A list of options to use in
gpg-agent.conf
. If omitted the following default configuration will be used:log-file $HOME/.gnupg/log/gpg-agent.log enable-ssh-support max-cache-ttl 172800 default-cache-ttl 86400 default-cache-ttl-ssh 10800 write-env-file $HOME/.gpg-agent-info
- gpg-agent.conf.extra
A list of extra options to add to
gpg-agent.conf
- gnupg-pkcs11-scd
If present will add
scdaemon-program
pointing to gnupg-pkcs11-scd to gpg-agent configuration, and copy gnupg-pkcs11-scd configuration.This setting can have the subkeys
library
(default: /usr/lib64/p11-kit-proxy.so) andprogram
(default: /usr/bin/gnupg-pkcs11-scd).
configure_gpg
This include configures gpg for a single user, and expects to be run as that user. The include for multiple users just uses this include in a loop. The data structure expected as gpg
is therefore a single user entry of the structure described there.
install_packages
This include handles package installation for the provided packages on the following distribution/package manager combinations:
- SuSE with zypper
- RedHat/CentOS/Fedora with yum
- RedHat/CentOS/Fedora with dnf
- Debian/Ubuntu with apt
It takes the following variables:
- packages, a list of packages to install.
- install_retries, the number of retries if package installation fails. Defaults to
3
.
Example usage:
- include_role: name: data-utilities tasks_from: install_packages vars: install_retries: 10 packages: - nmap
manage_service
This include abstracts commonly used service settings for systemd services and launchd services. The setting names are modeled after systemd services, lowercased with underscores between words.
Additionally state
can be set to one of the values supported by the Ansible systemd module. It defaults to restarted
, though the state change is only triggered if the service file changes on disk. Due to shortcomings of the OS X launchd every value not abesnt
will lead to the service being started according to the service file.
For OS X and Linux the following keys are supported:
name | service name, mandatory |
exec_start | executable and arguments. On OS X this gets split for ProgramArguments |
user | user name, if service should not run as root |
group | group name, if service should not run as root/wheel |
restart | restart settings. For OS X, this maps to KeepAlive: If unspecified or 'always' it is true |
on-failure and on-abort map to Crashed | |
on-success maps to SuccessfulExit | |
standard_error | redirection of standard error. On OS X this should be a path to a file |
stardard_output | redirection of standard out. On OS X this should be a path to a file |
environment | key/value list of environment variables. On OS X, if it does not contain PATH, runtime path + homebrew path is set |
Linux additionally supports:
- exec_start_pre
- exec_start_post
- restart_sec
- description
- wanted_by, defaulting to
multi-user.target
- type
- include_role: name: data-utilities tasks_from: manage_service vars: service: name: test state: absent - include_role: name: data-utilities tasks_from: manage_service vars: service: name: test exec_start: /path/to/binary environment: foo: bar
Includes mainly used by basic-host
This section describes includes used by the basic-host role. The documentation here is mainly useful for the variables used to adjust their behaviour - but also may be useful for sites where the complete configuration applied by basic-host
is not desirable.
check_versions
This include checks if the running Ansible version matches the version range this role has been tested with. Additionally it also exports the variable data_utilities_version
, and sets up some default variables. This also implicitely registers filter paths with Ansible.
The basic-host
role includes this file, so unless another role needs to use a higher data-utilities
version than basic-host
was tested for including this file should not be necessary. When not using basic-host
, or using multiple roles before basic-host
this include should be included early on in the play.
For checking if data utilities is available in the correct version the data_utilities_minver
variable can be set:
- include_role: name: data-utilities tasks_from: check_versions vars: data_utilities_minver: 0.1 tags: - base_config - access_setup
manage_hostname
This include tries to set the system hostname. On Windows this may require a reboot - execution continues once the system is reachable again.
For setting the hostname either the hostname
variable is used, or - if missing - the hostname is generated from the inventory hostname.
manage_motd
This adds or removes files for motd. Variables controlling this should be set in group or host variables.
motd_templates
controls templates for all hosts. motd_templates_<hostname>
controls templates for the specific host only. motd_templates_<groupname>
are added if the host is in that group.
The variables should be a dict, with optional keys filename
and state
. If state
is absent
the file will be removed, added for all other values. filename
controls the filename in the motd directory - if unset the key will be used. The template is always looked up as the key with .j2 appended.
The basic-host
role includes this file for all Linux systems - so as long as this role is used there should be no need to include this file.
motd_templates: motd_default: motd_templates_dummy: motd_dummy: motd_gone: state: absent
This example would add motd_default.j2
to all hosts, and motd_dummy.j2
to a host named or in a group called dummy
, and remove motd_gone
from that host.
manage_nameservices
This include configures name services for a system - this mainly, but not only, covers DNS. Windows currently is not supported.
Nameservers from a list in the variable nameservers
are used, if available. It usually makes sense to set a default in groupvars for all, and override it for other systems/groups.
There also is some legacy support for copying in prefilled resolv.conf templates based on resolv_location
and site_region
settings - this was implemented due to legacy Ansible restriction, and should not be used in new deployments.
manage_time
This include configures timezone and other time related settings. On Windows this also sets NTP servers. For Linux this is handled in a separate ntp-client role.
NTP servers are read from a list in the ntp_servers
variable. This typically should be set in the default groupvars for all, with overrides as needed.
For Windows the timezone is configured in host_timezone_win
, using Microsofts time zone names. For other system standard TZ database names in the host_timezone
variable are used.
For all systems the hardware clock is set to UTC.
setup_passdb
This initialises the default password store. Variables controlling the setup should be set in group or host variables. the basic-host
role includes this file - so as long as this role is used there should be no need to include this file.
Available variables are:
- passdb, defaulting to passwordstore
- passdb_password_length, default 20. This is used when creating passwords from within Ansible.
- passdb_password_create, bool, default
True
. Configures if Ansible is allowed to create missing passwords. - passdb_extra_arg, default = create={{passdb_password_create}} length={{passdb_password_length}}=
- passdb_check_entry, default empty. Allows setting a record to check if passdb is working. This is useful for writing playbooks executable by both full access admins and people without access to some passwords. A sensible value typically is the entry for the default root password.
The variable default_passdb_available
will be set by this include:
True
if nopassdb_check_entry
has been configured. This can lead to errors when executed without correct passdb access permissions.True
ifpassdb_check_entry
is accessible.False
ifpassdb_check_entry
is inaccessible.
Tasks/roles should use this variable to guard sections prompting for credentials, or skip execution completely.
- using 1password as passdb backend
Using 1password requires the 1password CLI to be installed and configured to ask for credentials when queried in an unlocked state - like connecting it to the desktop application.
passdb
then must be set tocommunity.general.onepassword
,passdb_extra_arg
to "".Currently limits to specific vaults or other settings supported by the module can't be configured in this role - see this issue for details.
Passwords should be saved as items of type
Password
. The name of the item is used for the lookup, the content of the fieldpassword
is retrieved. All other fields will be ignored when filled.
Inventory plugin
data format description
group
While optional it is sensible to pre-define groups with host filters to avoid mistakes
sites
For infrastructure spanning multiple physical locations hosts or host groups can be allocated to sites.
networks
This will generate multiple helper variables for easier consumption in roles:
- vlans
- dhcp_networks