Search This Blog

Saturday, 20 February 2021

Ansible Vault

 Ansible Vault encrypts variables and files so you can protect sensitive content such as passwords or keys rather than leaving it visible as plaintext in playbooks or roles. To use Ansible Vault you need one or more passwords to encrypt and decrypt content. If you store your vault passwords in a third-party tool such as a secret manager, you need a script to access them. Use the passwords with the ansible-vault command-line tool to create and view encrypted variables, create encrypted files, encrypt existing files, or edit, re-key, or decrypt files. You can then place encrypted content under source control and share it more safely.

Warning

Encryption with Ansible Vault ONLY protects ‘data at rest’. Once the content is decrypted (‘data in use’), play and plugin authors are responsible for avoiding any secret disclosure, see no_log for details on hiding output and Steps to secure your editor for security considerations on editors you use with Ansible Vault.

You can use encrypted variables and files in ad-hoc commands and playbooks by supplying the passwords you used to encrypt them. You can modify your ansible.cfg file to specify the location of a password file or to always prompt for the password.

Managing vault passwords

Managing your encrypted content is easier if you develop a strategy for managing your vault passwords. A vault password can be any string you choose. There is no special command to create a vault password. However, you need to keep track of your vault passwords. Each time you encrypt a variable or file with Ansible Vault, you must provide a password. When you use an encrypted variable or file in a command or playbook, you must provide the same password that was used to encrypt it. To develop a strategy for managing vault passwords, start with two questions:
  • Do you want to encrypt all your content with the same password, or use different passwords for different needs?
  • Where do you want to store your password or passwords?

Choosing between a single password and multiple passwords

If you have a small team or few sensitive values, you can use a single password for everything you encrypt with Ansible Vault. Store your vault password securely in a file or a secret manager as described below.

If you have a larger team or many sensitive values, you can use multiple passwords. For example, you can use different passwords for different users or different levels of access. Depending on your needs, you might want a different password for each encrypted file, for each directory, or for each environment. For example, you might have a playbook that includes two vars files, one for the dev environment and one for the production environment, encrypted with two different passwords. When you run the playbook, select the correct vault password for the environment you are targeting, using a vault ID.

Managing multiple passwords with vault IDs

If you use multiple vault passwords, you can differentiate one password from another with vault IDs. You use the vault ID in three ways:
  • Pass it with --vault-id to the ansible-vault command when you create encrypted content
  • Include it wherever you store the password for that vault ID (see Storing and accessing vault passwords)
  • Pass it with --vault-id to the ansible-playbook command when you run a playbook that uses content you encrypted with that vault ID
When you pass a vault ID as an option to the ansible-vault command, you add a label (a hint or nickname) to the encrypted content. This label documents which password you used to encrypt it. The encrypted variable or file includes the vault ID label in plain text in the header. The vault ID is the last element before the encrypted content. For example:

my_encrytped_var: !vault |
          $ANSIBLE_VAULT;1.2;AES256;dev
          30613233633461343837653833666333643061636561303338373661313838333565653635353162
          3263363434623733343538653462613064333634333464660a663633623939393439316636633863
          61636237636537333938306331383339353265363239643939666639386530626330633337633833
          6664656334373166630a363736393262666465663432613932613036303963343263623137386239
          6330

In addition to the label, you must provide a source for the related password. The source can be a prompt, a file, or a script, depending on how you are storing your vault passwords. The pattern looks like this:

--vault-id label@source

If your playbook uses multiple encrypted variables or files that you encrypted with different passwords, you must pass the vault IDs when you run that playbook. You can use --vault-id by itself, with --vault-password-file, or with --ask-vault-pass. The pattern is the same as when you create encrypted content: include the label and the source for the matching password.


Limitations of vault IDs

Ansible does not enforce using the same password every time you use a particular vault ID label. You can encrypt different variables or files with the same vault ID label but different passwords. This usually happens when you type the password at a prompt and make a mistake. It is possible to use different passwords with the same vault ID label on purpose. For example, you could use each label as a reference to a class of passwords, rather than a single password. In this scenario, you must always know which specific password or file to use in context. However, you are more likely to encrypt two files with the same vault ID label and different passwords by mistake. If you encrypt two files with the same label but different passwords by accident, you can rekey one file to fix the issue.

Enforcing vault ID matching

By default the vault ID label is only a hint to remind you which password you used to encrypt a variable or file. Ansible does not check that the vault ID in the header of the encrypted content matches the vault ID you provide when you use the content. Ansible decrypts all files and variables called by your command or playbook that are encrypted with the password you provide. To check the encrypted content and decrypt it only when the vault ID it contains matches the one you provide with --vault-id, set the config option DEFAULT_VAULT_ID_MATCH. When you set DEFAULT_VAULT_ID_MATCH, each password is only used to decrypt data that was encrypted with the same label. This is efficient, predictable, and can reduce errors when different values are encrypted with different passwords.

Note

Even with the DEFAULT_VAULT_ID_MATCH setting enabled, Ansible does not enforce using the same password every time you use a particular vault ID label.

Storing and accessing vault passwords

You can memorize your vault password, or manually copy vault passwords from any source and paste them at a command-line prompt, but most users store them securely and access them as needed from within Ansible. You have two options for storing vault passwords that work from within Ansible: in files, or in a third-party tool such as the system keyring or a secret manager. If you store your passwords in a third-party tool, you need a vault password client script to retrieve them from within Ansible.

Storing passwords in files

To store a vault password in a file, enter the password as a string on a single line in the file. Make sure the permissions on the file are appropriate. Do not add password files to source control.

Storing passwords in third-party tools with vault password client scripts

You can store your vault passwords on the system keyring, in a database, or in a secret manager and retrieve them from within Ansible using a vault password client script. Enter the password as a string on a single line. If your password has a vault ID, store it in a way that works with your password storage tool.

To create a vault password client script:

  • Create a file with a name ending in -client.py
  • Make the file executable
  • Within the script itself:
  • Print the passwords to standard output
  • Accept a --vault-id option
  • If the script prompts for data (for example, a database password), send the prompts to standard error
When you run a playbook that uses vault passwords stored in a third-party tool, specify the script as the source within the --vault-id flag. For example:

ansible-playbook --vault-id dev@contrib/vault/vault-keyring-client.py

Ansible executes the client script with a --vault-id option so the script knows which vault ID label you specified. For example a script loading passwords from a secret manager can use the vault ID label to pick either the ‘dev’ or ‘prod’ password. The example command above results in the following execution of the client script:

contrib/vault/vault-keyring-client.py --vault-id dev

For an example of a client script that loads passwords from the system keyring, see contrib/vault/vault-keyring-client.py.

Encrypting content with Ansible Vault

Once you have a strategy for managing and storing vault passwords, you can start encrypting content. You can encrypt two types of content with Ansible Vault: variables and files. Encrypted content always includes the !vault tag, which tells Ansible and YAML that the content needs to be decrypted, and a | character, which allows multi-line strings. Encrypted content created with --vault-id also contains the vault ID label. For more details about the encryption process and the format of content encrypted with Ansible Vault. This table shows the main differences between encrypted variables and encrypted files:


Encrypted variables

Encrypted files

How much is encrypted?

Variables within a plaintext file

The entire file

When is it decrypted?

On demand, only when needed

Whenever loaded or referenced 1

What can be encrypted?

Only variables

Any structured data file


Ansible cannot know if it needs content from an encrypted file unless it decrypts the file, so it decrypts all encrypted files referenced in your playbooks and roles.

Encrypting individual variables with Ansible Vault

You can encrypt single values inside a YAML file using the ansible-vault encrypt_string command. For one way to keep your vaulted variables safely visible, see Keep vaulted variables safely visible.

Advantages and disadvantages of encrypting variables

With variable-level encryption, your files are still easily legible. You can mix plaintext and encrypted variables, even inline in a play or role. However, password rotation is not as simple as with file-level encryption. You cannot rekey encrypted variables. Also, variable-level encryption only works on variables. If you want to encrypt tasks or other content, you must encrypt the entire file.

Creating encrypted variables

The ansible-vault encrypt_string command encrypts and formats any string you type (or copy or generate) into a format that can be included in a playbook, role, or variables file. To create a basic encrypted variable, pass three options to the ansible-vault encrypt_string command:
  • a source for the vault password (prompt, file, or script, with or without a vault ID)
  • the string to encrypt
  • the string name (the name of the variable)

The pattern looks like this:

ansible-vault encrypt_string <password_source> '<string_to_encrypt>' --name '<string_name_of_variable>'

For example, to encrypt the string ‘foobar’ using the only password stored in ‘a_password_file’ and name the variable ‘the_secret’:

ansible-vault encrypt_string --vault-password-file a_password_file 'foobar' --name 'the_secret'
The command above creates this content:

the_secret: !vault |
      $ANSIBLE_VAULT;1.1;AES256
      62313365396662343061393464336163383764373764613633653634306231386433626436623361
      6134333665353966363534333632666535333761666131620a663537646436643839616531643561
      63396265333966386166373632626539326166353965363262633030333630313338646335303630
      3438626666666137650a353638643435666633633964366338633066623234616432373231333331
      6564

To encrypt the string ‘foooodev’, add the vault ID label ‘dev’ with the ‘dev’ vault password stored in ‘a_password_file’, and call the encrypted variable ‘the_dev_secret’:

ansible-vault encrypt_string --vault-id dev@a_password_file 'foooodev' --name 'the_dev_secret'
The command above creates this content:

the_dev_secret: !vault |
          $ANSIBLE_VAULT;1.2;AES256;dev
          30613233633461343837653833666333643061636561303338373661313838333565653635353162
          3263363434623733343538653462613064333634333464660a663633623939393439316636633863
          61636237636537333938306331383339353265363239643939666639386530626330633337633833
          6664656334373166630a363736393262666465663432613932613036303963343263623137386239
          6330

To encrypt the string ‘letmein’ read from stdin, add the vault ID ‘test’ using the ‘test’ vault password stored in a_password_file, and name the variable ‘test_db_password’:

echo -n 'letmein' | ansible-vault encrypt_string --vault-id test@a_password_file --stdin-name 'test_db_password'

Warning

Typing secret content directly at the command line (without a prompt) leaves the secret string in your shell history. Do not do this outside of testing.

The command above creates this output:

Reading plaintext input from stdin. (ctrl-d to end input, twice if your content does not already have a new line)

db_password: !vault |
          $ANSIBLE_VAULT;1.2;AES256;dev
          61323931353866666336306139373937316366366138656131323863373866376666353364373761
          3539633234313836346435323766306164626134376564330a373530313635343535343133316133
          36643666306434616266376434363239346433643238336464643566386135356334303736353136
          6565633133366366360a326566323363363936613664616364623437336130623133343530333739
          3039

To be prompted for a string to encrypt, encrypt it with the ‘dev’ vault password from ‘a_password_file’, name the variable ‘new_user_password’ and give it the vault ID label ‘dev’:

ansible-vault encrypt_string --vault-id dev@a_password_file --stdin-name 'new_user_password'
The command above triggers this prompt:

Reading plaintext input from stdin. (ctrl-d to end input, twice if your content does not already have a new line)

Type the string to encrypt (for example, ‘hunter2’), hit ctrl-d, and wait.

Warning

Do not press Enter after supplying the string to encrypt. That will add a newline to the encrypted value.

The sequence above creates this output:

new_user_password: !vault |
          $ANSIBLE_VAULT;1.2;AES256;dev
          37636561366636643464376336303466613062633537323632306566653533383833366462366662
          6565353063303065303831323539656138653863353230620a653638643639333133306331336365
          62373737623337616130386137373461306535383538373162316263386165376131623631323434
          3866363862363335620a376466656164383032633338306162326639643635663936623939666238
          3161

You can add the output from any of the examples above to any playbook, variables file, or role for future use. Encrypted variables are larger than plain-text variables, but they protect your sensitive content while leaving the rest of the playbook, variables file, or role in plain text so you can easily read it.

Viewing encrypted variables

You can view the original value of an encrypted variable using the debug module. You must pass the password that was used to encrypt the variable. For example, if you stored the variable created by the last example above in a file called ‘vars.yml’, you could view the unencrypted value of that variable like this:

ansible localhost -m ansible.builtin.debug -a var="new_user_password" -e "@vars.yml" --vault-id dev@a_password_file

localhost | SUCCESS => {
    "new_user_password": "hunter2"
}

Encrypting files with Ansible Vault


Ansible Vault can encrypt any structured data file used by Ansible, including:

  • group variables files from inventory
  • host variables files from inventory
  • variables files passed to ansible-playbook with -e @file.yml or -e @file.json
  • variables files loaded by include_vars or vars_files
  • variables files in roles
  • defaults files in roles
  • tasks files
  • handlers files
  • binary files or other arbitrary files
The full file is encrypted in the vault.

Note

Ansible Vault uses an editor to create or modify encrypted files. See Steps to secure your editor for some guidance on securing the editor.

Advantages and disadvantages of encrypting files

File-level encryption is easy to use. Password rotation for encrypted files is straightforward with the rekey command. Encrypting files can hide not only sensitive values, but the names of the variables you use. However, with file-level encryption the contents of files are no longer easy to access and read. This may be a problem with encrypted tasks files. When encrypting a variables file, see Keep vaulted variables safely visible for one way to keep references to these variables in a non-encrypted file. Ansible always decrypts the entire encrypted file when it is when loaded or referenced, because Ansible cannot know if it needs the content unless it decrypts it.

Creating encrypted files

To create a new encrypted data file called ‘foo.yml’ with the ‘test’ vault password from ‘multi_password_file’:

ansible-vault create --vault-id test@multi_password_file foo.yml

The tool launches an editor (whatever editor you have defined with $EDITOR, default editor is vi). Add the content. When you close the the editor session, the file is saved as encrypted data. The file header reflects the vault ID used to create it:

``$ANSIBLE_VAULT;1.2;AES256;test``

To create a new encrypted data file with the vault ID ‘my_new_password’ assigned to it and be prompted for the password:

ansible-vault create --vault-id my_new_password@prompt foo.yml

Again, add content to the file in the editor and save. Be sure to store the new password you created at the prompt, so you can find it when you want to decrypt that file.

Encrypting existing files

To encrypt an existing file, use the ansible-vault encrypt command. This command can operate on multiple files at once. For example:

ansible-vault encrypt foo.yml bar.yml baz.yml

To encrypt existing files with the ‘project’ ID and be prompted for the password:

ansible-vault encrypt --vault-id project@prompt foo.yml bar.yml baz.yml

Viewing encrypted files

To view the contents of an encrypted file without editing it, you can use the ansible-vault view command:

ansible-vault view foo.yml bar.yml baz.yml

Editing encrypted files

To edit an encrypted file in place, use the ansible-vault edit command. This command decrypts the file to a temporary file, allows you to edit the content, then saves and re-encrypts the content and removes the temporary file when you close the editor. For example:

ansible-vault edit foo.yml

To edit a file encrypted with the vault2 password file and assigned the vault ID pass2:

ansible-vault edit --vault-id pass2@vault2 foo.yml

Changing the password and/or vault ID on encrypted files

To change the password on an encrypted file or files, use the rekey command:

ansible-vault rekey foo.yml bar.yml baz.yml

This command can rekey multiple data files at once and will ask for the original password and also the new password. To set a different ID for the rekeyed files, pass the new ID to --new-vault-id. For example, to rekey a list of files encrypted with the ‘preprod1’ vault ID from the ‘ppold’ file to the ‘preprod2’ vault ID and be prompted for the new password:

ansible-vault rekey --vault-id preprod1@ppold --new-vault-id preprod2@prompt foo.yml bar.yml baz.yml

Decrypting encrypted files

If you have an encrypted file that you no longer want to keep encrypted, you can permanently decrypt it by running the ansible-vault decrypt command. This command will save the file unencrypted to the disk, so be sure you do not want to edit it instead.

ansible-vault decrypt foo.yml bar.yml baz.yml

Steps to secure your editor

Ansible Vault relies on your configured editor, which can be a source of disclosures. Most editors have ways to prevent loss of data, but these normally rely on extra plain text files that can have a clear text copy of your secrets. Consult your editor documentation to configure the editor to avoid disclosing secure data. The following sections provide some guidance on common editors but should not be taken as a complete guide to securing your editor.

vim

You can set the following vim options in command mode to avoid cases of disclosure. There may be more settings you need to modify to ensure security, especially when using plugins, so consult the vim documentation.

1. Disable swapfiles that act like an autosave in case of crash or interruption.

set noswapfile

2. Disable creation of backup files.

set nobackup
set nowritebackup

3. Disable the viminfo file from copying data from your current session.

set viminfo=

4. Disable copying to the system clipboard.

set clipboard=

You can optionally add these settings in .vimrc for all files, or just specific paths or extensions. 

Emacs

You can set the following Emacs options to avoid cases of disclosure. There may be more settings you need to modify to ensure security, especially when using plugins, so consult the Emacs documentation.

1. Do not copy data to the system clipboard.

(setq x-select-enable-clipboard nil)

2. Disable creation of backup files.

(setq make-backup-files nil)

3. Disable autosave files.

(setq auto-save-default nil)


Using encrypted variables and files


When you run a task or playbook that uses encrypted variables or files, you must provide the passwords to decrypt the variables or files. You can do this at the command line or in the playbook itself.

Passing a single password

If all the encrypted variables and files your task or playbook needs use a single password, you can use the --ask-vault-pass or --vault-password-file cli options.

To prompt for the password:

ansible-playbook --ask-vault-pass site.yml

To retrieve the password from the /path/to/my/vault-password-file file:

ansible-playbook --vault-password-file /path/to/my/vault-password-file site.yml

To get the password from the vault password client script my-vault-password-client.py:

ansible-playbook --vault-password-file my-vault-password-client.py

Passing vault IDs

You can also use the --vault-id option to pass a single password with its vault label. This approach is clearer when multiple vaults are used within a single inventory.

To prompt for the password for the ‘dev’ vault ID:

ansible-playbook --vault-id dev@prompt site.yml

To retrieve the password for the ‘dev’ vault ID from the dev-password file:

ansible-playbook --vault-id dev@dev-password site.yml

To get the password for the ‘dev’ vault ID from the vault password client script my-vault-password-client.py:

ansible-playbook --vault-id dev@my-vault-password-client.py

Passing multiple vault passwords

If your task or playbook requires multiple encrypted variables or files that you encrypted with different vault IDs, you must use the --vault-id option, passing multiple --vault-id options to specify the vault IDs (‘dev’, ‘prod’, ‘cloud’, ‘db’) and sources for the passwords (prompt, file, script). . For example, to use a ‘dev’ password read from a file and to be prompted for the ‘prod’ password:

ansible-playbook --vault-id dev@dev-password --vault-id prod@prompt site.yml

By default the vault ID labels (dev, prod and so on) are only hints. Ansible attempts to decrypt vault content with each password. The password with the same label as the encrypted data will be tried first, after that each vault secret will be tried in the order they were provided on the command line.

Where the encrypted data has no label, or the label does not match any of the provided labels, the passwords will be tried in the order they are specified. In the example above, the ‘dev’ password will be tried first, then the ‘prod’ password for cases where Ansible doesn’t know which vault ID is used to encrypt something.

Using --vault-id without a vault ID

The --vault-id option can also be used without specifying a vault-id. This behavior is equivalent to --ask-vault-pass or --vault-password-file so is rarely used.

For example, to use a password file dev-password:

ansible-playbook --vault-id dev-password site.yml

To prompt for the password:

ansible-playbook --vault-id @prompt site.yml

To get the password from an executable script my-vault-password-client.py:

ansible-playbook --vault-id my-vault-password-client.py

Configuring defaults for using encrypted content

Setting a default vault ID

If you use one vault ID more frequently than any other, you can set the config option DEFAULT_VAULT_IDENTITY_LIST to specify a default vault ID and password source. Ansible will use the default vault ID and source any time you do not specify --vault-id. You can set multiple values for this option. Setting multiple values is equivalent to passing multiple --vault-id cli options.

Setting a default password source

If you use one vault password file more frequently than any other, you can set the DEFAULT_VAULT_PASSWORD_FILE config option or the ANSIBLE_VAULT_PASSWORD_FILE environment variable to specify that file. For example, if you set ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass.txt, Ansible will automatically search for the password in that file. This is useful if, for example, you use Ansible from a continuous integration system such as Jenkins.

When are encrypted files made visible?

In general, content you encrypt with Ansible Vault remains encrypted after execution. However, there is one exception. If you pass an encrypted file as the src argument to the copy, template, unarchive, script or assemble module, the file will not be encrypted on the target host (assuming you supply the correct vault password when you run the play). This behavior is intended and useful. You can encrypt a configuration file or template to avoid sharing the details of your configuration, but when you copy that configuration to servers in your environment, you want it to be decrypted so local users and processes can access it.

Speeding up Ansible Vault

If you have many encrypted files, decrypting them at startup may cause a perceptible delay. To speed this up, install the cryptography package:

pip install cryptography

Format of files encrypted with Ansible Vault

Ansible Vault creates UTF-8 encoded txt files. The file format includes a newline terminated header. For example:

$ANSIBLE_VAULT;1.1;AES256

or:

$ANSIBLE_VAULT;1.2;AES256;vault-id-label

The header contains up to four elements, separated by semi-colons (;).

  • The format ID ($ANSIBLE_VAULT). Currently $ANSIBLE_VAULT is the only valid format ID. The format ID identifies content that is encrypted with Ansible Vault (via vault.is_encrypted_file()).
  • The vault format version (1.X). All supported versions of Ansible will currently default to ‘1.1’ or ‘1.2’ if a labeled vault ID is supplied. The ‘1.0’ format is supported for reading only (and will be converted automatically to the ‘1.1’ format on write). The format version is currently used as an exact string compare only (version numbers are not currently ‘compared’).
  • The cipher algorithm used to encrypt the data (AES256). Currently AES256 is the only supported cipher algorithm. Vault format 1.0 used ‘AES’, but current code always uses ‘AES256’.
  • The vault ID label used to encrypt the data (optional, vault-id-label) For example, if you encrypt a file with --vault-id dev@prompt, the vault-id-label is dev.

Note: In the future, the header could change. Fields after the format ID and format version depend on the format version, and future vault format versions may add more cipher algorithm options and/or additional fields.

The rest of the content of the file is the ‘vaulttext’. The vaulttext is a text armored version of the encrypted ciphertext. Each line is 80 characters wide, except for the last line which may be shorter.

Ansible Vault payload format 1.1 - 1.2

The vaulttext is a concatenation of the ciphertext and a SHA256 digest with the result ‘hexlifyied’.

‘hexlify’ refers to the hexlify() method of the Python Standard Library’s binascii module.

hexlify()’ed result of:

  • hexlify()’ed string of the salt, followed by a newline (0x0a)
  • hexlify()’ed string of the crypted HMAC, followed by a newline. The HMAC is:
  • a RFC2104 style HMAC
inputs are:
  • The AES256 encrypted ciphertext
  • A PBKDF2 key. This key, the cipher key, and the cipher IV are generated from:
  • the salt, in bytes
  • 10000 iterations
  • SHA256() algorithm
  • the first 32 bytes are the cipher key
  • the second 32 bytes are the HMAC key
  • remaining 16 bytes are the cipher IV

  • hexlify()’ed string of the ciphertext. The ciphertext is:
  • AES256 encrypted data. The data is encrypted using:
  • AES-CTR stream cipher
  • cipher key
  • IV
  • a 128 bit counter block seeded from an integer IV
  • the plaintext
  • the original plaintext
  • padding up to the AES256 blocksize. (The data used for padding is based on RFC5652)

Ansible Templating (Jinja2) - Lookups

 Lookups

Lookup plugins retrieve data from outside sources such as files, databases, key/value stores, APIs, and other services. Like all templating, lookups execute and are evaluated on the Ansible control machine. Ansible makes the data returned by a lookup plugin available using the standard templating system. Before Ansible 2.5, lookups were mostly used indirectly in with_<lookup> constructs for looping. Starting with Ansible 2.5, lookups are used more explicitly as part of Jinja2 expressions fed into the loop keyword.

Using lookups in variables

You can populate variables using lookups. Ansible evaluates the value each time it is executed in a task (or template):

vars:

  motd_value: "{{ lookup('file', '/etc/motd') }}"

tasks:

  - debug:

      msg: "motd value is {{ motd_value }}"

For more details and a list of lookup plugins in ansible-base, see Working With Plugins. You may also find lookup plugins in collections. You can review a list of lookup plugins installed on your control machine with the command ansible-doc -l -t lookup.

Ansible Templating (Jinja2) - Tests

Tests

Tests in Jinja are a way of evaluating template expressions and returning True or False. Jinja ships with many of these. See builtin tests in the official Jinja template documentation.

The main difference between tests and filters are that Jinja tests are used for comparisons, whereas filters are used for data manipulation, and have different applications in jinja. Tests can also be used in list processing filters, like map() and select() to choose items in the list.

Like all templating, tests always execute on the Ansible controller, not on the target of a task, as they test local data.

In addition to those Jinja2 tests, Ansible supplies a few more and users can easily create their own.

Test syntax

Test syntax varies from filter syntax (variable | filter). Historically Ansible has registered tests as both jinja tests and jinja filters, allowing for them to be referenced using filter syntax.

As of Ansible 2.5, using a jinja test as a filter will generate a warning.

The syntax for using a jinja test is as follows:

variable is test_name

Such as:

result is failed

Testing strings

To match strings against a substring or a regular expression, use the match, search or regex tests:

vars:
  url: "http://example.com/users/foo/resources/bar"

tasks:
    - debug:
        msg: "matched pattern 1"
      when: url is match("http://example.com/users/.*/resources/")

    - debug:
        msg: "matched pattern 2"
      when: url is search("/users/.*/resources/.*")

    - debug:
        msg: "matched pattern 3"
      when: url is search("/users/")

    - debug:
        msg: "matched pattern 4"
      when: url is regex("example.com/\w+/foo")

match succeeds if it finds the pattern at the beginning of the string, while search succeeds if it finds the pattern anywhere within string. By default, regex works like search, but regex can be configured to perform other tests as well, by passing the match_type keyword argument. In particular, match_type determines the re method that gets used to perform the search. The full list can be found in the relevant Python documentation here.

All of the string tests also take optional ignorecase and multiline arguments. These correspond to re.I and re.M from Python’s re library, respectively.

Vault

You can test whether a variable is an inline single vault encrypted value using the vault_encrypted test.

vars:
  variable: !vault |
    $ANSIBLE_VAULT;1.2;AES256;dev
    61323931353866666336306139373937316366366138656131323863373866376666353364373761
    3539633234313836346435323766306164626134376564330a373530313635343535343133316133
    36643666306434616266376434363239346433643238336464643566386135356334303736353136
    6565633133366366360a326566323363363936613664616364623437336130623133343530333739
    3039

tasks:
  - debug:
      msg: '{{ (variable is vault_encrypted) | ternary("Vault encrypted", "Not vault encrypted") }}'

Testing truthiness

As of Ansible 2.10, you can now perform Python like truthy and falsy checks.

- debug:
    msg: "Truthy"
  when: value is truthy
  vars:
    value: "some string"

- debug:
    msg: "Falsy"
  when: value is falsy
  vars:
    value: ""

Additionally, the truthy and falsy tests accept an optional parameter called convert_bool that will attempt to convert boolean indicators to actual booleans.

- debug:
    msg: "Truthy"
  when: value is truthy(convert_bool=True)
  vars:
    value: "yes"

- debug:
    msg: "Falsy"
  when: value is falsy(convert_bool=True)
  vars:
    value: "off"

Comparing versions

Note

In 2.5 version_compare was renamed to version

To compare a version number, such as checking if the ansible_facts['distribution_version'] version is greater than or equal to ‘12.04’, you can use the version test.

The version test can also be used to evaluate the ansible_facts['distribution_version']:

{{ ansible_facts['distribution_version'] is version('12.04', '>=') }}

If ansible_facts['distribution_version'] is greater than or equal to 12.04, this test returns True, otherwise False.

The version test accepts the following operators:

<, lt, <=, le, >, gt, >=, ge, ==, =, eq, !=, <>, ne

This test also accepts a 3rd parameter, strict which defines if strict version parsing as defined by distutils.version.StrictVersion should be used. The default is False (using distutils.version.LooseVersion), True enables strict version parsing:

{{ sample_version_var is version('1.0', operator='lt', strict=True) }}
When using version in a playbook or role, don’t use {{ }} as described in the FAQ:

vars:
    my_version: 1.2.3

tasks:
    - debug:
        msg: "my_version is higher than 1.0.0"
      when: my_version is version('1.0.0', '>')

Set theory tests

Note

In 2.5 issubset and issuperset were renamed to subset and superset

To see if a list includes or is included by another list, you can use ‘subset’ and ‘superset’:

vars:
    a: [1,2,3,4,5]
    b: [2,3]
tasks:
    - debug:
        msg: "A includes B"
      when: a is superset(b)

    - debug:
        msg: "B is included in A"
      when: b is subset(a)

Testing if a list contains a value

New in version 2.8.

Ansible includes a contains test which operates similarly, but in reverse of the Jinja2 provided in test. The contains test is designed to work with the select, reject, selectattr, and rejectattr filters:

vars:
  lacp_groups:
    - master: lacp0
      network: 10.65.100.0/24
      gateway: 10.65.100.1
      dns4:
        - 10.65.100.10
        - 10.65.100.11
      interfaces:
        - em1
        - em2

    - master: lacp1
      network: 10.65.120.0/24
      gateway: 10.65.120.1
      dns4:
        - 10.65.100.10
        - 10.65.100.11
      interfaces:
          - em3
          - em4

tasks:
  - debug:
      msg: "{{ (lacp_groups|selectattr('interfaces', 'contains', 'em1')|first).master }}"

New in version 2.4.

Testing if a list value is True

You can use any and all to check if any or all elements in a list are true or not:

vars:
  mylist:
      - 1
      - "{{ 3 == 3 }}"
      - True
  myotherlist:
      - False
      - True
tasks:

  - debug:
      msg: "all are true!"
    when: mylist is all

  - debug:
      msg: "at least one is true"
    when: myotherlist is any

Testing paths

Note

In 2.5 the following tests were renamed to remove the is_ prefix

The following tests can provide information about a path on the controller:

- debug:
    msg: "path is a directory"
  when: mypath is directory

- debug:
    msg: "path is a file"
  when: mypath is file

- debug:
    msg: "path is a symlink"
  when: mypath is link

- debug:
    msg: "path already exists"
  when: mypath is exists

- debug:
    msg: "path is {{ (mypath is abs)|ternary('absolute','relative')}}"

- debug:
    msg: "path is the same file as path2"
  when: mypath is same_file(path2)

- debug:
    msg: "path is a mount"
  when: mypath is mount

Testing size formats

The human_readable and human_to_bytes functions let you test your playbooks to make sure you are using the right size format in your tasks, and that you provide Byte format to computers and human-readable format to people.

Human readable

Asserts whether the given string is human readable or not.

For example:

- name: "Human Readable"
  assert:
    that:
      - '"1.00 Bytes" == 1|human_readable'
      - '"1.00 bits" == 1|human_readable(isbits=True)'
      - '"10.00 KB" == 10240|human_readable'
      - '"97.66 MB" == 102400000|human_readable'
      - '"0.10 GB" == 102400000|human_readable(unit="G")'
      - '"0.10 Gb" == 102400000|human_readable(isbits=True, unit="G")'
This would result in:

{ "changed": false, "msg": "All assertions passed" }

Human to bytes

Returns the given string in the Bytes format.

For example:

- name: "Human to Bytes"
  assert:
    that:
      - "{{'0'|human_to_bytes}}        == 0"
      - "{{'0.1'|human_to_bytes}}      == 0"
      - "{{'0.9'|human_to_bytes}}      == 1"
      - "{{'1'|human_to_bytes}}        == 1"
      - "{{'10.00 KB'|human_to_bytes}} == 10240"
      - "{{   '11 MB'|human_to_bytes}} == 11534336"
      - "{{  '1.1 GB'|human_to_bytes}} == 1181116006"
      - "{{'10.00 Kb'|human_to_bytes(isbits=True)}} == 10240"

This would result in:

{ "changed": false, "msg": "All assertions passed" }

Testing task results

The following tasks are illustrative of the tests meant to check the status of tasks:

tasks:

  - shell: /usr/bin/foo
    register: result
    ignore_errors: True

  - debug:
      msg: "it failed"
    when: result is failed

  # in most cases you'll want a handler, but if you want to do something right now, this is nice
  - debug:
      msg: "it changed"
    when: result is changed

  - debug:
      msg: "it succeeded in Ansible >= 2.1"
    when: result is succeeded

  - debug:
      msg: "it succeeded"
    when: result is success

  - debug:
      msg: "it was skipped"
    when: result is skipped

Note

From 2.1, you can also use success, failure, change, and skip so that the grammar matches, for those who need to be strict about it.

Ansible Templating (Jinja2) - Using filters to manipulate data

 Ansible uses Jinja2 templating to enable dynamic expressions and access to variables. Ansible includes a lot of specialized filters and tests for templating. You can use all the standard filters and tests included in Jinja2 as well. Ansible also offers a new plugin type: Lookup Plugins.

All templating happens on the Ansible controller before the task is sent and executed on the target machine. This approach minimizes the package requirements on the target (jinja2 is only required on the controller). It also limits the amount of data Ansible passes to the target machine. Ansible parses templates on the controller and passes only the information needed for each task to the target machine, instead of passing all the data on the controller and parsing it on the target.

Get the current time

The now() Jinja2 function retrieves a Python datetime object or a string representation for the current time.
The now() function supports 2 arguments:

utc
Specify True to get the current time in UTC. Defaults to False.

fmt
Accepts a strftime string that returns a formatted date time string.

Using filters to manipulate data

Filters let you transform JSON data into YAML data, split a URL to extract the hostname, get the SHA1 hash of a string, add or multiply integers, and much more. You can use the Ansible-specific filters documented here to manipulate your data, or use any of the standard filters shipped with Jinja2 - see the list of built-in filters in the official Jinja2 template documentation. You can also use Python methods to transform data. You can create custom Ansible filters as plugins, though we generally welcome new filters into the ansible-base repo so everyone can use them.

Because templating happens on the Ansible controller, not on the target host, filters execute on the controller and transform data locally.

Handling undefined variables


Filters can help you manage missing or undefined variables by providing defaults or making some variables optional. If you configure Ansible to ignore most undefined variables, you can mark some variables as requiring values with the mandatory filter.

Providing default values

You can provide default values for variables directly in your templates using the Jinja2 ‘default’ filter. This is often a better approach than failing if a variable is not defined:

{{ some_variable | default(5) }}
In the above example, if the variable ‘some_variable’ is not defined, Ansible uses the default value 5, rather than raising an “undefined variable” error and failing. If you are working within a role, you can also add a defaults/main.yml to define the default values for variables in your role.

Beginning in version 2.8, attempting to access an attribute of an Undefined value in Jinja will return another Undefined value, rather than throwing an error immediately. This means that you can now simply use a default with a value in a nested data structure (in other words, {{ foo.bar.baz | default('DEFAULT') }}) when you do not know if the intermediate values are defined.

If you want to use the default value when variables evaluate to false or an empty string you have to set the second parameter to true:

{{ lookup('env', 'MY_USER') | default('admin', true) }}

Making variables optional

By default Ansible requires values for all variables in a templated expression. However, you can make specific variables optional. For example, you might want to use a system default for some items and control the value for others. To make a variable optional, set the default value to the special variable omit:

- name: Touch files with an optional mode
  ansible.builtin.file:
    dest: "{{ item.path }}"
    state: touch
    mode: "{{ item.mode | default(omit) }}"
  loop:
    - path: /tmp/foo
    - path: /tmp/bar
    - path: /tmp/baz
      mode: "0444"

In this example, the default mode for the files /tmp/foo and /tmp/bar is determined by the umask of the system. Ansible does not send a value for mode. Only the third file, /tmp/baz, receives the mode=0444 option.

Note

If you are “chaining” additional filters after the default(omit) filter, you should instead do something like this: "{{ foo | default(None) | some_filter or omit }}". In this example, the default None (Python null) value will cause the later filters to fail, which will trigger the or omit portion of the logic. Using omit in this manner is very specific to the later filters you are chaining though, so be prepared for some trial and error if you do this.

Defining mandatory values

If you configure Ansible to ignore undefined variables, you may want to define some values as mandatory. By default, Ansible fails if a variable in your playbook or command is undefined. You can configure Ansible to allow undefined variables by setting DEFAULT_UNDEFINED_VAR_BEHAVIOR to false. In that case, you may want to require some variables to be defined. You can do this with:

{{ variable | mandatory }}
The variable value will be used as is, but the template evaluation will raise an error if it is undefined.

Defining different values for true/false/null (ternary)


You can create a test, then define one value to use when the test returns true and another when the test returns false (new in version 1.9):

{{ (status == 'needs_restart') | ternary('restart', 'continue') }}
In addition, you can define a one value to use on true, one value on false and a third value on null (new in version 2.8):

{{ enabled | ternary('no shutdown', 'shutdown', omit) }}

Managing data types

You might need to know, change, or set the data type on a variable. For example, a registered variable might contain a dictionary when your next task needs a list, or a user prompt might return a string when your playbook needs a boolean value. Use the type_debug, dict2items, and items2dict filters to manage data types. You can also use the data type itself to cast a value as a specific data type.

Discovering the data type

If you are unsure of the underlying Python type of a variable, you can use the type_debug filter to display it. This is useful in debugging when you need a particular type of variable:

{{ myvar | type_debug }}

Transforming dictionaries into lists

Use the dict2items filter to transform a dictionary into a list of items suitable for looping:

{{ dict | dict2items }}
Dictionary data (before applying the dict2items filter):

tags:
  Application: payment
  Environment: dev
List data (after applying the dict2items filter):

- key: Application
  value: payment
- key: Environment
  value: dev

The dict2items filter is the reverse of the items2dict filter.

If you want to configure the names of the keys, the dict2items filter accepts 2 keyword arguments. Pass the key_name and value_name arguments to configure the names of the keys in the list output:

{{ files | dict2items(key_name='file', value_name='path') }}
Dictionary data (before applying the dict2items filter):

files:
  users: /etc/passwd
  groups: /etc/group
List data (after applying the dict2items filter):

- file: users
  path: /etc/passwd
- file: groups
  path: /etc/group

Transforming lists into dictionaries

Use the items2dict filter to transform a list into a dictionary, mapping the content into key: value pairs:

{{ tags | items2dict }}
List data (before applying the items2dict filter):

tags:
  - key: Application
    value: payment
  - key: Environment
    value: dev
Dictionary data (after applying the items2dict filter):

Application: payment
Environment: dev
The items2dict filter is the reverse of the dict2items filter.

Not all lists use key to designate keys and value to designate values. For example:

fruits:
  - fruit: apple
    color: red
  - fruit: pear
    color: yellow
  - fruit: grapefruit
    color: yellow

In this example, you must pass the key_name and value_name arguments to configure the transformation. For example:

{{ tags | items2dict(key_name='fruit', value_name='color') }}
If you do not pass these arguments, or do not pass the correct values for your list, you will see KeyError: key or KeyError: my_typo.

Forcing the data type

You can cast values as certain types. For example, if you expect the input “True” from a vars_prompt and you want Ansible to recognize it as a boolean value instead of a string:

- debug:
  msg: test
  when: some_string_value | bool
If you want to perform a mathematical comparison on a fact and you want Ansible to recognize it as an integer instead of a string:

- shell: echo "only on Red Hat 6, derivatives, and later"
  when: ansible_facts['os_family'] == "RedHat" and ansible_facts['lsb']['major_release'] | int >= 6
New in version 1.6.

Formatting data: YAML and JSON

You can switch a data structure in a template from or to JSON or YAML format, with options for formatting, indenting, and loading data. The basic filters are occasionally useful for debugging:

{{ some_variable | to_json }}
{{ some_variable | to_yaml }}
For human readable output, you can use:

{{ some_variable | to_nice_json }}
{{ some_variable | to_nice_yaml }}
You can change the indentation of either format:

{{ some_variable | to_nice_json(indent=2) }}
{{ some_variable | to_nice_yaml(indent=8) }}
The to_yaml and to_nice_yaml filters use the PyYAML library which has a default 80 symbol string length limit. That causes unexpected line break after 80th symbol (if there is a space after 80th symbol) To avoid such behavior and generate long lines, use the width option. You must use a hardcoded number to define the width, instead of a construction like float("inf"), because the filter does not support proxying Python functions. For example:

{{ some_variable | to_yaml(indent=8, width=1337) }}
{{ some_variable | to_nice_yaml(indent=8, width=1337) }}

If you are reading in some already formatted data:

{{ some_variable | from_json }}
{{ some_variable | from_yaml }}
for example:

tasks:
  - name: Register JSON output as a variable
    ansible.builtin.shell: cat /some/path/to/file.json
    register: result

  - name: Set a variable
    ansible.builtin.set_fact:
      myvar: "{{ result.stdout | from_json }}"

Filter to_json and Unicode support

By default to_json and to_nice_json will convert data received to ASCII, so:

{{ 'München'| to_json }}
will return:

'M\u00fcnchen'

To keep Unicode characters, pass the parameter ensure_ascii=False to the filter:

{{ 'München'| to_json(ensure_ascii=False) }}

'München'

To parse multi-document YAML strings, the from_yaml_all filter is provided. The from_yaml_all filter will return a generator of parsed YAML documents.

for example:

tasks:
  - name: Register a file content as a variable
    ansible.builtin.shell: cat /some/path/to/multidoc-file.yaml
    register: result

  - name: Print the transformed variable
    ansible.builtin.debug:
      msg: '{{ item }}'
    loop: '{{ result.stdout | from_yaml_all | list }}'

Combining and selecting data

You can combine data from multiple sources and types, and select values from large data structures, giving you precise control over complex data.

Combining items from multiple lists: zip and zip_longest

To get a list combining the elements of other lists use zip:

- name: Give me list combo of two lists
  ansible.builtin.debug:
   msg: "{{ [1,2,3,4,5] | zip(['a','b','c','d','e','f']) | list }}"

- name: Give me shortest combo of two lists
  ansible.builtin.debug:
    msg: "{{ [1,2,3] | zip(['a','b','c','d','e','f']) | list }}"

To always exhaust all lists use zip_longest:

- name: Give me longest combo of three lists , fill with X
  ansible.builtin.debug:
    msg: "{{ [1,2,3] | zip_longest(['a','b','c','d','e','f'], [21, 22, 23], fillvalue='X') | list }}"

Similarly to the output of the items2dict filter mentioned above, these filters can be used to construct a dict:

{{ dict(keys_list | zip(values_list)) }}

List data (before applying the zip filter):

keys_list:
  - one
  - two
values_list:
  - apple
  - orange
Dictonary data (after applying the zip filter):

one: apple
two: orange

Combining objects and subelements

The subelements filter produces a product of an object and the subelement values of that object, similar to the subelements lookup. This lets you specify individual subelements to use in a template. For example, this expression:

{{ users | subelements('groups', skip_missing=True) }}
Data before applying the subelements filter:

users:
- name: alice
  authorized:
  - /tmp/alice/onekey.pub
  - /tmp/alice/twokey.pub
  groups:
  - wheel
  - docker
- name: bob
  authorized:
  - /tmp/bob/id_rsa.pub
  groups:
  - docker

Data after applying the subelements filter:

-
  - name: alice
    groups:
    - wheel
    - docker
    authorized:
    - /tmp/alice/onekey.pub
    - /tmp/alice/twokey.pub
  - wheel
-
  - name: alice
    groups:
    - wheel
    - docker
    authorized:
    - /tmp/alice/onekey.pub
    - /tmp/alice/twokey.pub
  - docker
-
  - name: bob
    authorized:
    - /tmp/bob/id_rsa.pub
    groups:
    - docker
  - docker

You can use the transformed data with loop to iterate over the same subelement for multiple objects:

- name: Set authorized ssh key, extracting just that data from 'users'
  ansible.posix.authorized_key:
    user: "{{ item.0.name }}"
    key: "{{ lookup('file', item.1) }}"
  loop: "{{ users | subelements('authorized') }}"

Combining hashes/dictionaries

The combine filter allows hashes to be merged. For example, the following would override keys in one hash:

{{ {'a':1, 'b':2} | combine({'b':3}) }}

The resulting hash would be:

{'a':1, 'b':3}

The filter can also take multiple arguments to merge:

{{ a | combine(b, c, d) }}
{{ [a, b, c, d] | combine }}

In this case, keys in d would override those in c, which would override those in b, and so on.

The filter also accepts two optional parameters: recursive and list_merge.

recursive

Is a boolean, default to False. Should the combine recursively merge nested hashes. Note: It does not depend on the value of the hash_behaviour setting in ansible.cfg.

list_merge

Is a string, its possible values are replace (default), keep, append, prepend, append_rp or prepend_rp. It modifies the behaviour of combine when the hashes to merge contain arrays/lists.

default:
  a:
    x: default
    y: default
  b: default
  c: default
patch:
  a:
    y: patch
    z: patch
  b: patch

If recursive=False (the default), nested hash aren’t merged:

{{ default | combine(patch) }}
This would result in:

a:
  y: patch
  z: patch
b: patch
c: default

If recursive=True, recurse into nested hash and merge their keys:

{{ default | combine(patch, recursive=True) }}

This would result in:

a:
  x: default
  y: patch
  z: patch
b: patch
c: default

If list_merge='replace' (the default), arrays from the right hash will “replace” the ones in the left hash:

default:
  a:
    - default
patch:
  a:
    - patch
{{ default | combine(patch) }}

This would result in:

a:
  - patch

If list_merge='keep', arrays from the left hash will be kept:

{{ default | combine(patch, list_merge='keep') }}

This would result in:

a:
  - default
If list_merge='append', arrays from the right hash will be appended to the ones in the left hash:

{{ default | combine(patch, list_merge='append') }}

This would result in:

a:
  - default
  - patch
If list_merge='prepend', arrays from the right hash will be prepended to the ones in the left hash:

{{ default | combine(patch, list_merge='prepend') }}

This would result in:

a:
  - patch
  - default

If list_merge='append_rp', arrays from the right hash will be appended to the ones in the left hash. Elements of arrays in the left hash that are also in the corresponding array of the right hash will be removed (“rp” stands for “remove present”). Duplicate elements that aren’t in both hashes are kept:

default:
  a:
    - 1
    - 1
    - 2
    - 3
patch:
  a:
    - 3
    - 4
    - 5
    - 5
{{ default | combine(patch, list_merge='append_rp') }}
This would result in:

a:
  - 1
  - 1
  - 2
  - 3
  - 4
  - 5
  - 5

If list_merge='prepend_rp', the behavior is similar to the one for append_rp, but elements of arrays in the right hash are prepended:

{{ default | combine(patch, list_merge='prepend_rp') }}
This would result in:

a:
  - 3
  - 4
  - 5
  - 5
  - 1
  - 1
  - 2

recursive and list_merge can be used together:

default:
  a:
    a':
      x: default_value
      y: default_value
      list:
        - default_value
  b:
    - 1
    - 1
    - 2
    - 3

patch:
  a:
    a':
      y: patch_value
      z: patch_value
      list:
        - patch_value
  b:
    - 3
    - 4
    - 4
    - key: value
{{ default | combine(patch, recursive=True, list_merge='append_rp') }}

This would result in:

a:
  a':
    x: default_value
    y: patch_value
    z: patch_value
    list:
      - default_value
      - patch_value
b:
  - 1
  - 1
  - 2
  - 3
  - 4
  - 4
  - key: value

Selecting values from arrays or hashtables

The extract filter is used to map from a list of indices to a list of values from a container (hash or array):

{{ [0,2] | map('extract', ['x','y','z']) | list }}
{{ ['x','y'] | map('extract', {'x': 42, 'y': 31}) | list }}
The results of the above expressions would be:

['x', 'z']
[42, 31]
The filter can take another argument:

{{ groups['x'] | map('extract', hostvars, 'ec2_ip_address') | list }}
This takes the list of hosts in group ‘x’, looks them up in hostvars, and then looks up the ec2_ip_address of the result. The final result is a list of IP addresses for the hosts in group ‘x’.

The third argument to the filter can also be a list, for a recursive lookup inside the container:

{{ ['a'] | map('extract', b, ['x','y']) | list }}
This would return a list containing the value of b[‘a’][‘x’][‘y’].


Combining lists


This set of filters returns a list of combined lists.

permutations

To get permutations of a list:

- name: Give me largest permutations (order matters)
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5] | permutations | list }}"

- name: Give me permutations of sets of three
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5] | permutations(3) | list }}"

combinations

Combinations always require a set size:

- name: Give me combinations for sets of two
  ansible.builtin.debug:
    msg: "{{ [1,2,3,4,5] | combinations(2) | list }}"
Also see the Combining items from multiple lists: zip and zip_longest

products

The product filter returns the cartesian product of the input iterables. This is roughly equivalent to nested for-loops in a generator expression.

For example:

- name: Generate multiple hostnames
  ansible.builtin.debug:
    msg: "{{ ['foo', 'bar'] | product(['com']) | map('join', '.') | join(',') }}"
This would result in:

{ "msg": "foo.com,bar.com" }

Selecting JSON data: JSON queries


To select a single element or a data subset from a complex data structure in JSON format (for example, Ansible facts), use the json_query filter. The json_query filter lets you query a complex JSON structure and iterate over it using a loop structure.

Note

This filter has migrated to the community.general collection. Follow the installation instructions to install that collection.

Note

This filter is built upon jmespath, and you can use the same syntax. For examples, see jmespath examples.

Consider this data structure:

{
    "domain_definition": {
        "domain": {
            "cluster": [
                {
                    "name": "cluster1"
                },
                {
                    "name": "cluster2"
                }
            ],
            "server": [
                {
                    "name": "server11",
                    "cluster": "cluster1",
                    "port": "8080"
                },
                {
                    "name": "server12",
                    "cluster": "cluster1",
                    "port": "8090"
                },
                {
                    "name": "server21",
                    "cluster": "cluster2",
                    "port": "9080"
                },
                {
                    "name": "server22",
                    "cluster": "cluster2",
                    "port": "9090"
                }
            ],
            "library": [
                {
                    "name": "lib1",
                    "target": "cluster1"
                },
                {
                    "name": "lib2",
                    "target": "cluster2"
                }
            ]
        }
    }
}

To extract all clusters from this structure, you can use the following query:

- name: Display all cluster names
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query('domain.cluster[*].name') }}"

To extract all server names:

- name: Display all server names
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query('domain.server[*].name') }}"

To extract ports from cluster1:

- ansible.builtin.name: Display all ports from cluster1
  debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
  vars:
    server_name_cluster1_query: "domain.server[?cluster=='cluster1'].port"
Note

You can use a variable to make the query more readable.

To print out the ports from cluster1 in a comma separated string:

- name: Display all ports from cluster1 as a string
  ansible.builtin.debug:
    msg: "{{ domain_definition | community.general.json_query('domain.server[?cluster==`cluster1`].port') | join(', ') }}"

Note

In the example above, quoting literals using backticks avoids escaping quotes and maintains readability.

You can use YAML single quote escaping:

- name: Display all ports from cluster1
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query('domain.server[?cluster==''cluster1''].port') }}"

Note

Escaping single quotes within single quotes in YAML is done by doubling the single quote.

To get a hash map with all ports and names of a cluster:

- name: Display all server ports and names from cluster1
  ansible.builtin.debug:
    var: item
  loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
  vars:
    server_name_cluster1_query: "domain.server[?cluster=='cluster2'].{name: name, port: port}"


Randomizing data


When you need a randomly generated value, use one of these filters.

Random MAC addresses

This filter can be used to generate a random MAC address from a string prefix.


To get a random MAC address from a string prefix starting with ‘52:54:00’:

"{{ '52:54:00' | community.general.random_mac }}"
# => '52:54:00:ef:1c:03'

Note that if anything is wrong with the prefix string, the filter will issue an error.

As of Ansible version 2.9, you can also initialize the random number generator from a seed to create random-but-idempotent MAC addresses:

"{{ '52:54:00' | community.general.random_mac(seed=inventory_hostname) }}"

Random items or numbers

The random filter in Ansible is an extension of the default Jinja2 random filter, and can be used to return a random item from a sequence of items or to generate a random number based on a range.

To get a random item from a list:

"{{ ['a','b','c'] | random }}"
# => 'c'

To get a random number between 0 and a specified number:

"{{ 60 | random }} * * * * root /script/from/cron"
# => '21 * * * * root /script/from/cron'

To get a random number from 0 to 100 but in steps of 10:

{{ 101 | random(step=10) }}
# => 70

To get a random number from 1 to 100 but in steps of 10:

{{ 101 | random(1, 10) }}
# => 31
{{ 101 | random(start=1, step=10) }}
# => 51

You can initialize the random number generator from a seed to create random-but-idempotent numbers:

"{{ 60 | random(seed=inventory_hostname) }} * * * * root /script/from/cron"

Shuffling a list

The shuffle filter randomizes an existing list, giving a different order every invocation.

To get a random list from an existing list:

{{ ['a','b','c'] | shuffle }}
# => ['c','a','b']
{{ ['a','b','c'] | shuffle }}
# => ['b','c','a']

You can initialize the shuffle generator from a seed to generate a random-but-idempotent order:

{{ ['a','b','c'] | shuffle(seed=inventory_hostname) }}
# => ['b','a','c']

The shuffle filter returns a list whenever possible. If you use it with a non ‘listable’ item, the filter does nothing.

Managing list variables

You can search for the minimum or maximum value in a list, or flatten a multi-level list.

To get the minimum value from list of numbers:

{{ list1 | min }}

To get the maximum value from a list of numbers:

{{ [3, 4, 2] | max }}

Flatten a list (same thing the flatten lookup does):

{{ [3, [4, 2] ] | flatten }}

Flatten only the first level of a list (akin to the items lookup):

{{ [3, [4, [2]] ] | flatten(levels=1) }}

Selecting from sets or lists (set theory)

You can select or combine items from sets or lists.

To get a unique set from a list:

# list1: [1, 2, 5, 1, 3, 4, 10]
{{ list1 | unique }}
# => [1, 2, 5, 3, 4, 10]

To get a union of two lists:

# list1: [1, 2, 5, 1, 3, 4, 10]
# list2: [1, 2, 3, 4, 5, 11, 99]
{{ list1 | union(list2) }}
# => [1, 2, 5, 1, 3, 4, 10, 11, 99]

To get the intersection of 2 lists (unique list of all items in both):

# list1: [1, 2, 5, 3, 4, 10]
# list2: [1, 2, 3, 4, 5, 11, 99]
{{ list1 | intersect(list2) }}
# => [1, 2, 5, 3, 4]

To get the difference of 2 lists (items in 1 that don’t exist in 2):

# list1: [1, 2, 5, 1, 3, 4, 10]
# list2: [1, 2, 3, 4, 5, 11, 99]
{{ list1 | difference(list2) }}
# => [10]

To get the symmetric difference of 2 lists (items exclusive to each list):

# list1: [1, 2, 5, 1, 3, 4, 10]
# list2: [1, 2, 3, 4, 5, 11, 99]
{{ list1 | symmetric_difference(list2) }}
# => [10, 11, 99]

Calculating numbers (math)

You can calculate logs, powers, and roots of numbers with Ansible filters. Jinja2 provides other mathematical functions like abs() and round().

Get the logarithm (default is e):

{{ myvar | log }}
Get the base 10 logarithm:

{{ myvar | log(10) }}
Give me the power of 2! (or 5):

{{ myvar | pow(2) }}
{{ myvar | pow(5) }}
Square root, or the 5th:

{{ myvar | root }}
{{ myvar | root(5) }}

Managing network interactions

These filters help you with common network tasks.

Note

These filters have migrated to the ansible.netcommon collection. Follow the installation instructions to install that collection.

IP address filters

To test if a string is a valid IP address:

{{ myvar | ansible.netcommon.ipaddr }}
You can also require a specific IP protocol version:

{{ myvar | ansible.netcommon.ipv4 }}
{{ myvar | ansible.netcommon.ipv6 }}

IP address filter can also be used to extract specific information from an IP address. For example, to get the IP address itself from a CIDR, you can use:

{{ '192.0.2.1/24' | ansible.netcommon.ipaddr('address') }}


Network CLI filters

To convert the output of a network device CLI command into structured JSON output, use the parse_cli filter:

{{ output | ansible.netcommon.parse_cli('path/to/spec') }}

The parse_cli filter will load the spec file and pass the command output through it, returning JSON output. The YAML spec file defines how to parse the CLI output.

The spec file should be valid formatted YAML. It defines how to parse the CLI output and return JSON data. Below is an example of a valid spec file that will parse the output from the show vlan command.

---
vars:
  vlan:
    vlan_id: "{{ item.vlan_id }}"
    name: "{{ item.name }}"
    enabled: "{{ item.state != 'act/lshut' }}"
    state: "{{ item.state }}"

keys:
  vlans:
    value: "{{ vlan }}"
    items: "^(?P<vlan_id>\\d+)\\s+(?P<name>\\w+)\\s+(?P<state>active|act/lshut|suspended)"
  state_static:
    value: present
The spec file above will return a JSON data structure that is a list of hashes with the parsed VLAN information.

The same command could be parsed into a hash by using the key and values directives. Here is an example of how to parse the output into a hash value using the same show vlan command.

---
vars:
  vlan:
    key: "{{ item.vlan_id }}"
    values:
      vlan_id: "{{ item.vlan_id }}"
      name: "{{ item.name }}"
      enabled: "{{ item.state != 'act/lshut' }}"
      state: "{{ item.state }}"

keys:
  vlans:
    value: "{{ vlan }}"
    items: "^(?P<vlan_id>\\d+)\\s+(?P<name>\\w+)\\s+(?P<state>active|act/lshut|suspended)"
  state_static:
    value: present

Another common use case for parsing CLI commands is to break a large command into blocks that can be parsed. This can be done using the start_block and end_block directives to break the command into blocks that can be parsed.

---
vars:
  interface:
    name: "{{ item[0].match[0] }}"
    state: "{{ item[1].state }}"
    mode: "{{ item[2].match[0] }}"

keys:
  interfaces:
    value: "{{ interface }}"
    start_block: "^Ethernet.*$"
    end_block: "^$"
    items:
      - "^(?P<name>Ethernet\\d\\/\\d*)"
      - "admin state is (?P<state>.+),"
      - "Port mode is (.+)"

The example above will parse the output of show interface into a list of hashes.

The network filters also support parsing the output of a CLI command using the TextFSM library. To parse the CLI output with TextFSM use the following filter:

{{ output.stdout[0] | ansible.netcommon.parse_cli_textfsm('path/to/fsm') }}
Use of the TextFSM filter requires the TextFSM library to be installed.

Network XML filters

To convert the XML output of a network device command into structured JSON output, use the parse_xml filter:

{{ output | ansible.netcommon.parse_xml('path/to/spec') }}
The parse_xml filter will load the spec file and pass the command output through formatted as JSON.

The spec file should be valid formatted YAML. It defines how to parse the XML output and return JSON data.

Below is an example of a valid spec file that will parse the output from the show vlan | display xml command.

---
vars:
  vlan:
    vlan_id: "{{ item.vlan_id }}"
    name: "{{ item.name }}"
    desc: "{{ item.desc }}"
    enabled: "{{ item.state.get('inactive') != 'inactive' }}"
    state: "{% if item.state.get('inactive') == 'inactive'%} inactive {% else %} active {% endif %}"

keys:
  vlans:
    value: "{{ vlan }}"
    top: configuration/vlans/vlan
    items:
      vlan_id: vlan-id
      name: name
      desc: description
      state: ".[@inactive='inactive']"

The spec file above will return a JSON data structure that is a list of hashes with the parsed VLAN information.

The same command could be parsed into a hash by using the key and values directives. Here is an example of how to parse the output into a hash value using the same show vlan | display xml command.

---
vars:
  vlan:
    key: "{{ item.vlan_id }}"
    values:
        vlan_id: "{{ item.vlan_id }}"
        name: "{{ item.name }}"
        desc: "{{ item.desc }}"
        enabled: "{{ item.state.get('inactive') != 'inactive' }}"
        state: "{% if item.state.get('inactive') == 'inactive'%} inactive {% else %} active {% endif %}"

keys:
  vlans:
    value: "{{ vlan }}"
    top: configuration/vlans/vlan
    items:
      vlan_id: vlan-id
      name: name
      desc: description
      state: ".[@inactive='inactive']"

The value of top is the XPath relative to the XML root node. In the example XML output given below, the value of top is configuration/vlans/vlan, which is an XPath expression relative to the root node (<rpc-reply>). configuration in the value of top is the outer most container node, and vlan is the inner-most container node.

items is a dictionary of key-value pairs that map user-defined names to XPath expressions that select elements. The Xpath expression is relative to the value of the XPath value contained in top. For example, the vlan_id in the spec file is a user defined name and its value vlan-id is the relative to the value of XPath in top

Attributes of XML tags can be extracted using XPath expressions. The value of state in the spec is an XPath expression used to get the attributes of the vlan tag in output XML.:

<rpc-reply>
  <configuration>
    <vlans>
      <vlan inactive="inactive">
       <name>vlan-1</name>
       <vlan-id>200</vlan-id>
       <description>This is vlan-1</description>
      </vlan>
    </vlans>
  </configuration>
</rpc-reply>


Network VLAN filters

Use the vlan_parser filter to transform an unsorted list of VLAN integers into a sorted string list of integers according to IOS-like VLAN list rules. This list has the following properties:

Vlans are listed in ascending order.

Three or more consecutive VLANs are listed with a dash.

The first line of the list can be first_line_len characters long.

Subsequent list lines can be other_line_len characters.

To sort a VLAN list:

{{ [3003, 3004, 3005, 100, 1688, 3002, 3999] | ansible.netcommon.vlan_parser }}

This example renders the following sorted list:

['100,1688,3002-3005,3999']

Another example Jinja template:

{% set parsed_vlans = vlans | ansible.netcommon.vlan_parser %}
switchport trunk allowed vlan {{ parsed_vlans[0] }}
{% for i in range (1, parsed_vlans | count) %}
switchport trunk allowed vlan add {{ parsed_vlans[i] }}

This allows for dynamic generation of VLAN lists on a Cisco IOS tagged interface. You can store an exhaustive raw list of the exact VLANs required for an interface and then compare that to the parsed IOS output that would actually be generated for the configuration.

Encrypting and checksumming strings and passwords

To get the sha1 hash of a string:

{{ 'test1' | hash('sha1') }}

To get the md5 hash of a string:

{{ 'test1' | hash('md5') }}

Get a string checksum:

{{ 'test2' | checksum }}

Other hashes (platform dependent):

{{ 'test2' | hash('blowfish') }}

To get a sha512 password hash (random salt):

{{ 'passwordsaresecret' | password_hash('sha512') }}

To get a sha256 password hash with a specific salt:

{{ 'secretpassword' | password_hash('sha256', 'mysecretsalt') }}

An idempotent method to generate unique hashes per system is to use a salt that is consistent between runs:

{{ 'secretpassword' | password_hash('sha512', 65534 | random(seed=inventory_hostname) | string) }}

Hash types available depend on the master system running Ansible, ‘hash’ depends on hashlib, password_hash depends on passlib (https://passlib.readthedocs.io/en/stable/lib/passlib.hash.html).

Some hash types allow providing a rounds parameter:

{{ 'secretpassword' | password_hash('sha256', 'mysecretsalt', rounds=10000) }}

Manipulating text

Several filters work with text, including URLs, file names, and path names.

Adding comments to files

The comment filter lets you create comments in a file from text in a template, with a variety of comment styles. By default Ansible uses # to start a comment line and adds a blank comment line above and below your comment text. For example the following:

{{ "Plain style (default)" | comment }}

produces this output:

#
# Plain style (default)
#

Ansible offers styles for comments in C (//...), C block (/*...*/), Erlang (%...) and XML (<!--...-->):

{{ "C style" | comment('c') }}
{{ "C block style" | comment('cblock') }}
{{ "Erlang style" | comment('erlang') }}
{{ "XML style" | comment('xml') }}

You can define a custom comment character. This filter:

{{ "My Special Case" | comment(decoration="! ") }}

produces:

!
! My Special Case
!

You can fully customize the comment style:

{{ "Custom style" | comment('plain', prefix='#######\n#', postfix='#\n#######\n   ###\n    #') }}
That creates the following output:

#######
#
# Custom style
#
#######
   ###
    #

The filter can also be applied to any Ansible variable. For example to make the output of the ansible_managed variable more readable, we can change the definition in the ansible.cfg file to this:

[defaults]

ansible_managed = This file is managed by Ansible.%n
  template: {file}
  date: %Y-%m-%d %H:%M:%S
  user: {uid}
  host: {host}

and then use the variable with the comment filter:

{{ ansible_managed | comment }}
which produces this output:

#
# This file is managed by Ansible.
#
# template: /home/ansible/env/dev/ansible_managed/roles/role1/templates/test.j2
# date: 2015-09-10 11:02:58
# user: ansible
# host: myhost
#

Splitting URLs


The urlsplit filter extracts the fragment, hostname, netloc, password, path, port, query, scheme, and username from an URL. With no arguments, returns a dictionary of all the fields:

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('hostname') }}
# => 'www.acme.com'

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('netloc') }}
# => 'user:password@www.acme.com:9000'

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('username') }}
# => 'user'

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('password') }}
# => 'password'

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('path') }}
# => '/dir/index.html'

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('port') }}
# => '9000'

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('scheme') }}
# => 'http'

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('query') }}
# => 'query=term'

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit('fragment') }}
# => 'fragment'

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#fragment" | urlsplit }}
# =>
#   {
#       "fragment": "fragment",
#       "hostname": "www.acme.com",
#       "netloc": "user:password@www.acme.com:9000",
#       "password": "password",
#       "path": "/dir/index.html",
#       "port": 9000,
#       "query": "query=term",
#       "scheme": "http",
#       "username": "user"
#   }

Searching strings with regular expressions

To search a string with a regex, use the “regex_search” filter:

# search for "foo" in "foobar"
{{ 'foobar' | regex_search('(foo)') }}

# will return empty if it cannot find a match
{{ 'ansible' | regex_search('(foobar)') }}

# case insensitive search in multiline mode
{{ 'foo\nBAR' | regex_search("^bar", multiline=True, ignorecase=True) }}
To search for all occurrences of regex matches, use the “regex_findall” filter:

# Return a list of all IPv4 addresses in the string
{{ 'Some DNS servers are 8.8.8.8 and 8.8.4.4' | regex_findall('\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b') }}

To replace text in a string with regex, use the “regex_replace” filter:

# convert "ansible" to "able"
{{ 'ansible' | regex_replace('^a.*i(.*)$', 'a\\1') }}

# convert "foobar" to "bar"
{{ 'foobar' | regex_replace('^f.*o(.*)$', '\\1') }}

# convert "localhost:80" to "localhost, 80" using named groups
{{ 'localhost:80' | regex_replace('^(?P<host>.+):(?P<port>\\d+)$', '\\g<host>, \\g<port>') }}

# convert "localhost:80" to "localhost"
{{ 'localhost:80' | regex_replace(':80') }}

# change a multiline string
{{ var | regex_replace('^', '#CommentThis#', multiline=True) }}

Note

If you want to match the whole string and you are using * make sure to always wraparound your regular expression with the start/end anchors. For example ^(.*)$ will always match only one result, while (.*) on some Python versions will match the whole string and an empty string at the end, which means it will make two replacements:

# add "https://" prefix to each item in a list
GOOD:
{{ hosts | map('regex_replace', '^(.*)$', 'https://\\1') | list }}
{{ hosts | map('regex_replace', '(.+)', 'https://\\1') | list }}
{{ hosts | map('regex_replace', '^', 'https://') | list }}

BAD:
{{ hosts | map('regex_replace', '(.*)', 'https://\\1') | list }}

# append ':80' to each item in a list
GOOD:
{{ hosts | map('regex_replace', '^(.*)$', '\\1:80') | list }}
{{ hosts | map('regex_replace', '(.+)', '\\1:80') | list }}
{{ hosts | map('regex_replace', '$', ':80') | list }}

BAD:
{{ hosts | map('regex_replace', '(.*)', '\\1:80') | list }}

Note

Prior to ansible 2.0, if “regex_replace” filter was used with variables inside YAML arguments (as opposed to simpler ‘key=value’ arguments), then you needed to escape backreferences (for example, \\1) with 4 backslashes (\\\\) instead of 2 (\\).


To escape special characters within a standard Python regex, use the “regex_escape” filter (using the default re_type=’python’ option):

# convert '^f.*o(.*)$' to '\^f\.\*o\(\.\*\)\$'
{{ '^f.*o(.*)$' | regex_escape() }}

To escape special characters within a POSIX basic regex, use the “regex_escape” filter with the re_type=’posix_basic’ option:

# convert '^f.*o(.*)$' to '\^f\.\*o(\.\*)\$'
{{ '^f.*o(.*)$' | regex_escape('posix_basic') }}

Managing file names and path names

To get the last name of a file path, like ‘foo.txt’ out of ‘/etc/asdf/foo.txt’:

{{ path | basename }}
To get the last name of a windows style file path (new in version 2.0):

{{ path | win_basename }}
To separate the windows drive letter from the rest of a file path (new in version 2.0):

{{ path | win_splitdrive }}
To get only the windows drive letter:

{{ path | win_splitdrive | first }}
To get the rest of the path without the drive letter:

{{ path | win_splitdrive | last }}
To get the directory from a path:

{{ path | dirname }}
To get the directory from a windows path (new version 2.0):

{{ path | win_dirname }}
To expand a path containing a tilde (~) character (new in version 1.5):

{{ path | expanduser }}
To expand a path containing environment variables:

{{ path | expandvars }}

Note

expandvars expands local variables; using it on remote paths can lead to errors.

To get the real path of a link (new in version 1.8):

{{ path | realpath }}
To get the relative path of a link, from a start point (new in version 1.7):

{{ path | relpath('/etc') }}
To get the root and extension of a path or file name (new in version 2.0):

# with path == 'nginx.conf' the return would be ('nginx', '.conf')
{{ path | splitext }}

The splitext filter returns a string. The individual components can be accessed by using the first and last filters:

# with path == 'nginx.conf' the return would be 'nginx'
{{ path | splitext | first }}

# with path == 'nginx.conf' the return would be 'conf'
{{ path | splitext | last }}
To join one or more path components:

{{ ('/etc', path, 'subdir', file) | path_join }}
New in version 2.10.

Manipulating strings

To add quotes for shell usage:

- name: Run a shell command
  ansible.builtin.shell: echo {{ string_value | quote }}
To concatenate a list into a string:

{{ list | join(" ") }}
To work with Base64 encoded strings:

{{ encoded | b64decode }}
{{ decoded | string | b64encode }}

As of version 2.6, you can define the type of encoding to use, the default is utf-8:

{{ encoded | b64decode(encoding='utf-16-le') }}
{{ decoded | string | b64encode(encoding='utf-16-le') }}

Note

The string filter is only required for Python 2 and ensures that text to encode is a unicode string. Without that filter before b64encode the wrong value will be encoded.


Managing UUIDs

To create a namespaced UUIDv5:

{{ string | to_uuid(namespace='11111111-2222-3333-4444-555555555555') }}
New in version 2.10.

To create a namespaced UUIDv5 using the default Ansible namespace ‘361E6D51-FAEC-444A-9079-341386DA8E2E’:

{{ string | to_uuid }}
New in version 1.9.

To make use of one attribute from each item in a list of complex variables, use the Jinja2 map filter:

# get a comma-separated list of the mount points (for example, "/,/mnt/stuff") on a host
{{ ansible_mounts | map(attribute='mount') | join(',') }}

Handling dates and times

To get a date object from a string use the to_datetime filter:

# Get total amount of seconds between two dates. Default date format is %Y-%m-%d %H:%M:%S but you can pass your own format
{{ (("2016-08-14 20:00:12" | to_datetime) - ("2015-12-25" | to_datetime('%Y-%m-%d'))).total_seconds()  }}

# Get remaining seconds after delta has been calculated. NOTE: This does NOT convert years, days, hours, and so on to seconds. For that, use total_seconds()
{{ (("2016-08-14 20:00:12" | to_datetime) - ("2016-08-14 18:00:00" | to_datetime)).seconds  }}
# This expression evaluates to "12" and not "132". Delta is 2 hours, 12 seconds

# get amount of days between two dates. This returns only number of days and discards remaining hours, minutes, and seconds
{{ (("2016-08-14 20:00:12" | to_datetime) - ("2015-12-25" | to_datetime('%Y-%m-%d'))).days  }}
New in version 2.4.

To format a date using a string (like with the shell date command), use the “strftime” filter:

# Display year-month-day
{{ '%Y-%m-%d' | strftime }}

# Display hour:min:sec
{{ '%H:%M:%S' | strftime }}

# Use ansible_date_time.epoch fact
{{ '%Y-%m-%d %H:%M:%S' | strftime(ansible_date_time.epoch) }}

# Use arbitrary epoch value
{{ '%Y-%m-%d' | strftime(0) }}          # => 1970-01-01
{{ '%Y-%m-%d' | strftime(1441357287) }} # => 2015-09-04

Note

To get all string possibilities, check https://docs.python.org/3/library/time.html#time.strftime

Getting Kubernetes resource names

Note

These filters have migrated to the community.kubernetes collection. Follow the installation instructions to install that collection.

Use the “k8s_config_resource_name” filter to obtain the name of a Kubernetes ConfigMap or Secret, including its hash:

{{ configmap_resource_definition | community.kubernetes.k8s_config_resource_name }}
This can then be used to reference hashes in Pod specifications:

my_secret:
  kind: Secret
  name: my_secret_name

deployment_resource:
  kind: Deployment
  spec:
    template:
      spec:
        containers:
        - envFrom:
            - secretRef:
                name: {{ my_secret | community.kubernetes.k8s_config_resource_name }}