Fortinet black logo

Playbooks Guide

Jinja Filters and Functions

Copy Link
Copy Doc ID a3853f63-1178-11ed-9eba-fa163e15d75b:767891
Download PDF

Jinja Filters and Functions

Overview

Use jinja2 filters to design and manipulate playbook step outputs. Jinja operations are supported in the Playbook Engine and you can also use the Custom Functions and Filters that are documented in this chapter.

Note

All filters are case-sensitive.


These examples present in this chapter provide a reference to common and very useful string operations that may be leveraged within the Playbook engine.

You can also use Jinja extensions to enrich expressions, for more information, see the Jinja Extensions topic.

Filters

FortiSOAR supports the following filters:

  • fromIRI: Will resolve an IRI and return the object(s) that live(s) there. This is similar to loading the object by id (IRI).
    {{ '/api/3/events/8' | fromIRI }}{{ vars.event.alert_iri | fromIRI }}
    You can use dot access for values returned by fromIRI.
    For example: To get a person record and return their 'name' field you can use the following:
    {{ (vars.person_iri | fromIRI).name }}
    You can also use fromIRI recursively, for example:
    {{ ((vars.event.alert |fromIRI).owner| fromIRI).name }}
    You can also retrieve relationship data for a record on which a playbook is running, for example:
    {{('/api/3/alerts/<alert_IRI>?$relationships=true' | fromIRI).indicators}}
  • toDict: attempt to coerce a string into a dictionary for access.
    {{ (request.data.incident_string | toDict).id }}
  • xml_to_dict: Converts an XML string into a dictionary for access:
    {{ '<?xml version="1.0" ?><person><name>john</name><age>20</age></person>' | xml_to_dict }}
  • extract_artifacts: Parses and extracts a list of IOCs from a given string:
    {{'abc.com 192.168.42.23' | extract_artifacts}}
  • parse_cef: Parses a given CEF string and converts the CEF string into a dictionary:
    {{ 'some string containing cef' | parse_cef }}
  • readfile: Fetches the contents of a file that is downloaded in FortiSOAR:
    {{ vars.result | readfile}}
    where vars.result is the name of the file.
  • ip_range: Checks if the IP address is in the specified range:
    {{vars.ip | ip_range(‘198.162.0.0/24’)}}
  • counter: Gets the count of each item's occurrence in an array of items:
    {{data| counter}}
    For example:
    data: [‘apple’,‘red’,‘apple’,‘red’,‘red’,‘pear’]
    {{data| counter}}
    Result: {‘red’: 3, ‘apple’: 2, ‘pear’: 1}

FortiSOAR also supports following filters, more information for which is present at http://docs.ansible.com/ansible/latest/playbooks_filters.html.

Filters for formatting data

The following filters take a data structure in a template and render it in a slightly different format. These 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 }}

It is also possible to change the indentation the variables:

{{ some_variable | to_nice_json(indent=2) }}
{{ some_variable | to_nice_yaml(indent=8) }}

Alternatively, you may be reading in some already formatted data:

{{ some_variable | from_json }}
{{ some_variable | from_yaml }}

Filters that operate on list variables

To get the minimum value from a list of numbers:

{{ list1 | min }}

To get the maximum value from a list of numbers:

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

Filters that return a unique set from sets or lists

To get a unique set from a list:

{{ list1 | unique }}

To get a union of two lists:

{{ list1 | union(list2) }}

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

{{ list1 | intersect(list2) }}

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

{{ list1 | difference(list2) }}

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

{{ list1 | symmetric_difference(list2) }}

Random Number filter

The following filters can be used similar to the default jinja2 random filter (returning a random item from a sequence of items), but they can also be used 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 from 0 to supplied end:

{{ 59 |random}}
# => 21

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

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

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

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

To initialize the random number generator from a seed. This way, you can create random-but-idempotent numbers:

{{ 59 |random(seed=inventory_hostname) }}

Shuffle filter

The following filters randomize 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']

To shuffle a list idempotent. For this you will require a seed:

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

When this filter is used with a non ‘listable’ item it is a noop. Otherwise, it always returns a list.

Filters for math operations

To get the logarithm (default is e):

{{ myvar | log }}

To get the base 10 logarithm:

{{ myvar | log(10) }}

To get the power of 2! (or 5):

{{ myvar | pow(2) }}
{{ myvar | pow(5) }}

To get the square root, or the 5th:

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

IP Address filters

To test if a string is a valid IP address:

{{ myvar | ipaddr }}

To get the IP address in a specific IP protocol version:

{{ myvar | ipv4 }}
{{ myvar | ipv6 }}

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' | ipaddr('address') }}

To filter a list of IP addresses:

test_list = ['192.24.2.1', 'host.fqdn', '::1', '192.168.32.0/24', 'fe80::100/10', True, '', '42540766412265424405338506004571095040/64']

# {{ test_list | ipaddr }}
['192.24.2.1', '::1', '192.168.32.0/24', 'fe80::100/10', '2001:db8:32c:faad::/64']

# {{ test_list | ipv4 }}
['192.24.2.1', '192.168.32.0/24']
# {{ test_list | ipv6 }}
['::1', 'fe80::100/10', '2001:db8:32c:faad::/64']

To get a host IP address from a list of IP addresses:

# {{ test_list | ipaddr('host') }}
['192.24.2.1/32', '::1/128', 'fe80::100/10']

To get a public IP address from a list of IP addresses:

# {{ test_list | ipaddr('public') }}
['192.24.2.1', '2001:db8:32c:faad::/64']

To get a private IP address from a list of IP addresses:

# {{ test_list | ipaddr('private') }}
['192.168.32.0/24', 'fe80::100/10']

Network range as a query:

# {{ test_list | ipaddr('192.0.0.0/8') }}
['192.24.2.1', '192.168.32.0/24']

Hashing filters

To get the sha1 hash of a string:

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

To get the md5 hash of a string:

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

To 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') }}

Note

FortiSOAR uses the haslib library for hash and passlib library for password_hash.

Filters for combining hashes and 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 also accepts an optional recursive=True parameter to not only override keys in the first hash, but also recursively into nested hashes and merge their keys too:

{{ {'a':{'foo':1, 'bar':2}, 'b':2}|combine({'a':{'bar':3, 'baz':4}}, recursive=True) }}

The resulting hash would be:

{'a':{'foo':1, 'bar':3, 'baz':4}, 'b':2}

The filter can also take multiple arguments to merge:

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

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

Filters for extracting values from containers

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’].

Comment filter

The comment filter allows you to decorate the text with a chosen comment style. For example, the following

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

will produce the following output:

#
# Plain style (default)
#

Similarly you can apply style to the comments for C (//...), C block (/*...*/), Erlang (%...) and XML (<!--...-->):

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

It is also possible to fully customize the comment style:

{{ "Custom style" | comment('plain', prefix='#######\n#', postfix='#\n#######\n ###\n #') }}

which creates the following output:

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

URL Split filter

The urlsplit filter extracts the fragment, hostname, netloc, password, path, port, query, scheme, and username from an URL. If you do not provide any arguments to this filter then it returns a dictionary of all the fields:

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

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

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

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

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

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

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

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#frament" | 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"
# }

Regular Expression filters

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)') }}

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') }}

To escape special characters within a regex, use the regex_escape filter:

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

Other useful filters

To add quotes for shell usage:

{{ string_value | quote }}

To use one value on true and another on false:

{{ (name == "John") | ternary('Mr','Ms') }}

To concatenate a list into a string:

{{ list | join(" ") }}

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:

{{ path | win_basename }}

To separate the windows drive letter from the rest of a file path:

{{ 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:

{{ path | win_dirname }}

To expand a path containing a tilde (~) character:

{{ path | expanduser }}

To get the real path of a link:

{{ path | realpath }}

To get the relative path of a link, from a start point:

{{ path | relpath('/etc') }}

To get the root and extension of a path or filename:

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

To work with Base64 encoded strings:

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

To create a UUID from a string:

{{ hostname | to_uuid }}

To get date object from string use the to_datetime filter:

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

Combination filters

This set of filters returns a list of combined lists.

To get permutations of a list:

To get the largest permutations (order matters):

{{ [1,2,3,4,5] |permutations|list }}

To get the permutations of sets of three:

{{ [1,2,3,4,5] |permutations(3)|list }}

Combinations always require a set size:

To get the combinations for sets of two:

{{ [1,2,3,4,5] |combinations(2)|list }}

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

To get a combination of two lists:

{{ [1,2,3,4,5] |zip(['a','b','c','d','e','f'])|list }}

To get the shortest combination of two lists:

{{ [1,2,3] |zip(['a','b','c','d','e','f'])|list }}

To always exhaust all lists use zip_longest:

To get the longest combination of all three lists, fill with X:

{{ [1,2,3] |zip_longest(['a','b','c','d','e','f'], [21, 22, 23], fillvalue='X')|list }}

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

Debugging filters

Use the 'type_debug' filter to display the underlying Python type of a variable. This can be useful in debugging in cases where you might need to know the exact type of a variable:

{{ myvar | type_debug }}

FortiSOAR also supports following built-in filters from Jinja, more information for which is present at Template Designer Documentation — Jinja Documentation (2.11.x).

  • abs (number): Returns the absolute value of the argument.
  • attr (obj, name): Gets an attribute of an object. foo|attr("bar") works like foo.bar just that always an attribute is returned and items are not looked up.
    See Notes on subscriptions for more details.
  • batch (value, linecount, fill_with=None): Batches items. It works pretty much like slice just the other way around. It returns a list of lists with the given number of items. If you provide a second parameter, this is used to fill up missing items. For example:
    {% for row in items|batch(3, 'FillerString') %}
        {% for column in row %}
           {{ column }}
       {% endfor %}
    {% endfor %}
    
  • capitalize(s): Capitalizes a value. The first character will be uppercase, all others lowercase.
  • center(value, width=80): Centers the value in a field of a given width.
  • default(value, default_value=u'', boolean=False): If the value is undefined it will return the passed default value, otherwise the value of the variable:
    {{ my_variable|default('my_variable is not defined') }}
    

    This would output the value of my_variable if the variable was defined. Otherwise, my_variable is not defined. If you want to use default with variables that evaluate to false, you have to set the second parameter to true:

    {{ ''|default('the string was empty', true) }}
    
  • dictsort(value, case_sensitive=False, by='key'): Sorts a dict and yields (key, value) pairs. Because python dicts are unsorted you might want to use this function to order them by either key or value:
    {% for item in mydict|dictsort %}
        sort the dict by key, case insensitive
    
    {% for item in mydict|dictsort(true) %}
        sort the dict by key, case sensitive
    
    {% for item in mydict|dictsort(false, 'value') %}
        sort the dict by value, case insensitive
    
  • escape(s): Converts the characters &, <, >, , and in strings to HTML-safe sequences. Use this if you need to display text that might contain such characters in HTML. Marks return value as markup string.
  • filesizeformat(value, binary=False): Formats the value like a human-readable file size (i.e. 13 kB, 4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega, Giga, etc.) if the second parameter is set to True the binary prefixes are used (Mebi, Gibi).-
  • first(seq): Returns the first item of a sequence.
  • float(value, default=0.0): Converts the value into a floating point number. If the conversion doesn’t work, it will return 0.0. You can override this default using the first parameter.
  • forceescape(value): Enforces HTML escaping. This will probably double escape variables.
  • format(value, args,**kwargs): Applies python string formatting on an object:
    {{ %s"|format("Hello?", "Foo!") }}
        -> Hello? - Foo!
    
  • groupby(value, attribute): Groups a sequence of objects by a common attribute. If you, for example, have a list of dicts or objects that represent persons with gender, first_name, and last_name attributes and you want to group all users by genders you can do something like the following snippet:
    {% for group in persons|groupby('gender') %}
           {{ group.grouper }}
        {% for person in group.list %}
           {{ person.first_name }} {{ person.last_name }}
        {% endfor %}
    {% endfor %}
    

    Additionally, it is possible to use tuple unpacking for the grouper and list:

    {% for grouper, list in persons|groupby('gender') %}
        ...
    {% endfor %}
    

    As you can see the item, we are grouping by are stored in the grouper attribute, and the list contains all the objects that have this grouper in common. You can also use dotted notation to group by the child attribute of another attribute.

  • indent(s, width=4, indentfirst=False): Returns a copy of the passed string, each line indented by four spaces. The first line is not indented. If you want to change the number of spaces or indent the first line too you can pass additional parameters to the filter:
      {{ mytext|indent(2, true) }}
        indent by two spaces and indent the first line too.
    
  • int(value, default=0, base=10): Converts the value into an integer. If the conversion does not work, it will return 0. You can override this default using the first parameter. You can also override the default base (10) in the second parameter, which handles input with prefixes such as 0b, 0o and 0x for bases 2, 8 and 16 respectively. The base is ignored for decimal numbers and non-string values.
  • join(value, d=u'', attribute=None): Returns a string which is the concatenation of the strings in the sequence. The separator between elements is an empty string per default; you can define it with the optional parameter:
     {{ [1, 2, 3] |join('|') }}
        -> 1 |2|3
    
    {{ [1, 2, 3]|join }}
        -> 123
    

It is also possible to join certain attributes of an object:

{{ users|join(', ', attribute='username') }}
  • last(seq): Returns the last item of a sequence.
  • length(object): Returns the number of items of a sequence or mapping.
    Aliases: count
  • list(value): Converts the value into a list. If it were a string, the returned list would be a list of characters.
  • lower(s): Converts a value to lowercase.
  • map(): Applies a filter on a sequence of objects or looks up an attribute. This is useful when dealing with lists of objects, but you are only interested in a certain value of it.
    The basic usage is mapping on an attribute. Imagine you have a list of users, but you are only interested in a list of usernames:
    Users on this page: {{ users |map(attribute='username')|join(', ') }}
    

    Alternatively, you can let it invoke a filter by passing the name of the filter and the arguments afterward. A good example would be applying a text conversion filter on a sequence:

    Users on this page: {{ titles |map('lower')|join(', ') }}
    
  • pprint(value, verbose=False): Pretty print a variable. Useful for debugging. With Jinja 1.2 onwards you can pass it a parameter. If this parameter is truthy, the output will be more verbose (this requires pretty).
  • random(seq): Returns a random item from the sequence.
  • reject(): Filters a sequence of objects by applying a test to each object, and rejecting the objects whose tests succeed.
    If no test is specified, each object will be evaluated as a boolean. For example:
    {{ numbers|reject("odd") }}
    
  • rejectattr(): Filters a sequence of objects by applying a test to the specified attribute of each object, and rejecting the objects whose tests succeed.
    If no test is specified, the attribute’s value will be evaluated as a boolean.
    {{ users|rejectattr("is_active") }}
    {{ users|rejectattr("email", "none") }}
    
  • replace(s, old, new, count=None): Returns a copy of the value with all occurrences of a substring replaced with a new one. The first argument is the substring that should be replaced; the second is the replacement string. If the optional third argument count is given, only the firstcount occurrences are replaced:
    {{ "Hello World"|replace("Hello", "Goodbye") }}
        -> Goodbye World
    
    {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
        -> d'oh, d'oh, aaargh
    
  • reverse(value): Reverses the object or returns an iterator that iterates over it the other way round.
  • round(value, precision=0, method='common'): Round the number to a given precision. The first parameter specifies the precision (default is 0), the second the rounding method:
    • 'common' rounds either up or down: Default method.
    • 'ceil' always rounds up
    • 'floor' always rounds down
      {{ 42.55|round }}
          -> 43.0
      {{ 42.55|round(1, 'floor') }}
          -> 42.5
      

    Note that even if rounded to 0 precision, a float is returned. If you need a real integer, pipe it through int:

    {{ 42.55 |round|int }}
        -> 43
    
  • safe(value): Marks the value as safe which means that in an environment with automatic escaping enabled this variable will not be escaped.
  • select(): Filters a sequence of objects by applying a test to each object, and only selecting the objects whose tests succeed.
    If no test is specified, each object will be evaluated as a boolean. For example,
    {{ numbers|select("odd") }}
    {{ numbers|select("odd") }}
    
  • selectattr(): Filters a sequence of objects by applying a test to the specified attribute of each object, and only selecting the objects whose tests succeed.
    If no test is specified, the attribute’s value will be evaluated as a boolean. For example,
    {{ users|selectattr("is_active") }}
    {{ users|selectattr("email", "none") }}
    
  • slice(value, slices, fill_with=None): Slices an iterator and returns a list of lists containing those items. Useful if you want to create a div containing three ul tags that represent columns:
    <div class="columwrapper">
      {% for column in items|slice(3) %}
        <ul class="column-{{ loop.index }}">
        {% for item in column %}
          <li>{{ item }}</li>
        {% endfor %}
        </ul>
      {% endfor %}
    </div>
    

    If you pass it a second argument, it is used to fill missing values on the last iteration.

  • sort(value, reverse=False, case_sensitive=False, attribute=None): Sorts an iterable. Per default it sorts ascending, if you pass it true as the first argument, it will reverse the sorting.
    If the iterable is made of strings, the third parameter can be used to control the case sensitiveness of the comparison which is disabled by default.
    {% for item in iterable|sort %}
        ...
    {% endfor %}
    

    It is also possible to sort by an attribute (for example to sort by the date of an object) by specifying the attribute parameter:

    {% for item in iterable|sort(attribute='date') %}
        ...
    {% endfor %}
    
  • string(object): Makes a string unicode if it isn’t already. That way a markup string is not converted back to unicode.
  • striptags(value): Strips SGML/XML tags and replace adjacent whitespace by one space.
  • sum(iterable, attribute=None, start=0): Returns the sum of a sequence of numbers plus the value of parameter ‘start’ (which defaults to 0). When the sequence is empty, it returns to start.
    It is also possible, to sum up only certain attributes:
    Total: {{ items|sum(attribute='price') }}
    

    The attribute parameter was added to allow summing up over attributes. Also, the start parameter was moved on to the right.

  • title(s): Returns a titlecased version of the value, i.e., words will start with uppercase letters, all remaining characters are lowercase.
  • tojson or toJSON (value, indent=None): Dumps a structure to JSON so that it’s safe to use in <script> tags. It accepts the same arguments and returns a JSON string. Note that this is available in templates through the |tojson filter which will also mark the result as safe. Due to how this function escapes certain characters this is safe even if used outside of <script> tags.
    The following characters are escaped in strings: <>&'
    This makes it safe to embed such strings in any place in HTML with the notable exception of double quoted attributes. In that case single quote your attributes or HTML escape also.
    The indent parameter can be used to enable pretty printing. Set it to the number of spaces that the structures should be indented with.
    Note that this filter is for use in HTML contexts only.
  • trim(value): Strips the leading and trailing whitespace.
  • truncate(s, length=255, killwords=False, end='...', leeway=None): Returns a truncated copy of the string. The length is specified with the first parameter which defaults to 255. If the second parameter is true, the filter will cut the text at length. Otherwise, it will discard the last word. If the text was in fact truncated, it would append an ellipsis sign ("..."). If you want a different ellipsis sign than "..." you can specify it using the third parameter. Strings that only exceed the length by the tolerance margin given in the fourth parameter will not be truncated.
    {{ "foo bar baz qux"|truncate(9) }}
        -> "foo..."
    {{ "foo bar baz qux"|truncate(9, True) }}
        -> "foo ba..."
    {{ "foo bar baz qux"|truncate(11) }}
        -> "foo bar baz qux"
    {{ "foo bar baz qux"|truncate(11, False, '...', 0) }}
        -> "foo bar...
    

    The default leeway on newer Jinja2 versions is 5 and was 0 before but can be reconfigured globally.

  • upper(s): Converts a value to uppercase.
  • urlencode(value): Escape strings for use in URLs (uses UTF-8 encoding). It accepts both dictionaries and regular strings as well as pairwise iterables.
  • urlize(value, trim_url_limit=None, nofollow=False, target=None, rel=None): Converts URLs in plain text into clickable links.
    If you pass the filter an additional integer, it will shorten the URLs to that number. Also, a third argument exists that makes the URLs “nofollow”:
    {{ mytext|urlize(40, true) }}
        links are shortened to 40 chars and defined with rel="nofollow"
    

    If the target is specified, the target attribute will be added to the <a> tag:

    {{ mytext|urlize(40, target='_blank') }}
    
  • wordcount(s): Counts the words in that string.
  • wordwrap(s, width=79, break_long_words=True, wrapstring=None): Returns a copy of the string passed to the filter wrapped after 79 characters. You can override this default using the first parameter. If you set the second parameter to false Jinja will not split words apart if they are longer than the width. By default, the newlines will be the default newlines for the environment, but this can be changed using the wrapstring keyword argument.
  • xmlattr(d, autospace=True): Creates an SGML/XML attribute string based on the items in a dict. All values that are neither none nor undefined are automatically escaped:
    <ul{{ {'class': 'my_list', 'missing': none,
    	'id': 'list-%d' |format(variable)}|xmlattr }}>
    ...
    </ul>

    Results in something like this:

    <ul class="my_list" id="list-42">
    ...
    </ul>

    As you can see it automatically prepends a space in front of the item if the filter returned something unless the second parameter is false.

json_query filter

Use the json_query filter when you have a complex data structure in the JSON format from which you require to extract only a small set of data. The json_query filter enables you to query and iterate a complex JSON structure. The filter is built using jmespath, and you can use the same syntax in the json_query filter. For details on jmespath, see JMESPath Examples.

Example

The result of your playbook step is as follows:

[
 {"name": "a", "state": "running"},
 {"name": "b", "state": "stopped"},
 {"name": "b", "state": "running"}
]

From this result, you want to query only the names of those objects who are in the running state. For this query the valid jinja expression would be: {{ vars.result | json_query(" [?state=='running'].name") }} , which would have the following result:

[
  "a",
  "b"
]

To create a valid JSON query, you can refer to JMESPath Tutorial.

Comprehensive list of filters

The following table contains a comprehensive list of filters that you can use:

Filter Description Source
abs Absolute value of a number jinja2_docs
attr(x) Gets attribute 'x' of an object. Does not return *items*, which are dictionary keys jinja2_docs
b64decode Decodes a base64 value ansible
b64encode Encodes a value as base64 ansible
basename Last name in a filepath ansible
batch(n) Separates a list into various lists with size 'n' jinja2_docs
bool Casts a string as a boolean value (i.e. "True" or "False" to a boolean value) ansible

capitalize

Capitalizes the first letter of a value

jinja2_docs

center(n)

Centers the value in a field of 'n' characters by adding spaces on either side

jinja2_docs

checksum

Get a string checksum

ansible

cidr_merge

Merges a list of subnets or IP addresses to their minimal representation

ansible_ipaddr

combinations(n)

Returns an iterator of n-size combinations of items from an input list

ansible

combine(dict_x, recursive=False)

Merges dect_x into input dictionary, overwriting any values that overlap. Setting recursive=True also allows for nested keys to be merged.

ansible

comment

Converts a string into a python-style comment

ansible

comp_type5

ansible.netcommon comp_type5 filter plugin

ansible.netcommon

count

Alias of length

jinja2_docs

count_occurrence

Retrieves the number of times each element appears in the list.

FortiSOAR

counter

Gets the count of each item's occurrence in an array of items.

FortiSOAR

d(x)

Alias of "default"

jinja2_docs

default(x)

Outputs default value 'x' if the passed input is not defined.

jinja2_docs

dict2items

Turn a dictionary into a list of items suitable for looping

ansible

dictsort(false,reverse=false)

Sorts a dict and yields key, value pairs. Set the first argument to true for case sensitive sort. Provide 'value' as second argument to sort by value instead of key.

jinja2_docs

difference(list_x)

Gets items from an input list that are not present in 'list_x'.

ansible

dirname

Gets the directory from a path

ansible

e

Alias of escape

jinja2_docs

escape

Escapes characters &, M, >, ', and " with HTML-safe sequences

jinja2_docs

expanduser

Expands a path containing a telde(~) character

ansible

expandvars

Expands a path containing environment variables

ansible

extract()

Maps a list of indices to a list of values from a container (hash or array)

FortiSOAR

extract_artifacts

Parses and extracts a list of IOCs from a given string

FortiSOAR

extract_cef/parse_cef

Parses a given CEF string and converts the CEF string into a dictionary

FortiSOAR

fileglob

Provides an output list of matching files from the given input path (can include wildcards, for example '/tmp/*.txt').

ansible

filesizeformat

Formats a number into "human readable" file size, for example, 13 KB

jinja2_docs

first

Returns the first item of a sequence

jinja2_docs

flatten

Flattens a list

ansible

float

Converts a value into floating point number

jinja2_docs

forceescape

Enforces HTML escaping. Can lead to double-escaping

jinja2_docs

format(string_x)

Formats 'string_x' based on the passed format string

jinja2_docs

fromIRI

Resolve an IRI and return the object(s) that live(s) there. This is similar to loading the object by id (IRI)

FortiSOAR

from_json

Converts a json-formatted string to dict.

ansible

from_yaml

Converts a yaml-formatted string to dict

ansible

from_yaml_all

Parses a multi-document yaml string to an iterator of parsed yaml documents

ansible

groupby(value)

Groups a sequence of objects by their attribute value

jinja2_docs

hash(hashtype)

Gets the hash of a string, using hashtype, for example, 'md5', or 'sha1'

ansible

html2texthash

Converts an HTML string to a text string

FortiSOAR

human_readable

Asserts whether the given string is human readable or not

ansible

human_to_bytes

Returns the given string in bytes format

ansible

hwaddr

Checks if a string is a MAC address

ansible_ipaddr

indent

Returns the input string with each line indented by four spaces

jinja2_docs

int

Converts the value to an integer

jinja2_docs

intersect(list_x)

Gets a list of unique items that is present in both the 'input list' and 'list_x'

ansible

ip4_hex

Converts IPv4 to an Hexadecimal notation

ansible_ipaddr

ip_range(ip_range)

Checks if the IP address is in the specified CIDR range

FortiSOAR

ipaddr

Checks if a string is a valid IP address

FortiSOAR

ipmath(n)

Gets the next 'n' addresses based on the passed parameter in specified IP address

ansible_ipaddr

ipsubnet

Converts an ip address to a subnet

ansible_ipaddr

ipv4

Checks if the given IP address is an IPv4 address

ansible_ipaddr

ipv6

Checks if the given IP address is an IPv6 address

ansible_ipaddr

ipwrap

Wraps any IPv6 addresses in brackets in the provide a list of strings, leaving other items intact

ansible_ipaddr

items2dict(key=k, value=v)

Reverse of dict2items, i.e., maps key and value into a dictionary

ansible

join(delim_x)

Returns a string that is the concatenation of the strings in the passed sequence. If 'delim_x' is provided, it is used to separate the items in the string

jinja2_docs

json2html

Converts JSON data into HTML

FortiSOAR

json_query

Allows the use of jmespath expressions (see jmespath.org) to manipulate input data

ansible

last

Returns the last item of a sequence

jinja2_docs

length

Returns the number of items in a sequence

jinja2_docs

list

Converts the value into a list

jinja2_docs

loadRelationships

Fetches details of a related (correlation) record

FortiSOAR

log(base=e)

Gets the log (default base e) of the passed values

ansible

lower

Converts a value to lowercase

jinja2_docs

mandatory

Raises an error if the passed variable is undefined

ansible

map

Applies a filter on a sequence of objects or looks up an attribute

jinja2_docs

max

Returns the largest item from the sequence

jinja2_docs

md5

Gets the md5 hash of a string

ansible

min

Returns the smallest item from the sequence

jinja2_docs

network_in_network

Returns whether 'address_x' is in the passed network

ansible_ipaddr

network_in_usable

Returns whether an address passed as an argument is usable in a network

ansible_ipaddr

next_nth_usable(n)

Returns the next 'n' usable IP addresses in relation to the passed IP addresses/ranges

ansible_ipaddr

nthhost(n)

Returns the nth IP address in the passed CIDR range

ansible_ipaddr

parse_cli

Converts the output of a network device CLI command into a structured JSON output

ansible

parse_cli_textfsm

Parses output of a network device CLI command using the TextFSM library

ansible

parse_xml(path_to_specfile)

Converts XML output of a network device command into a structured JSON output

ansible

password_hash(algorithm, salt)

Gets a password hash with a specified hashing algorithm, and optionally a provided salt value

ansible

permutations

Gets an iterator of all permutations of values in a list

ansible

picklist

Loads the specified picklist item object

FortiSOAR

pow(x)

Returns passed values to the power of 'x'

ansible

pprint

Pretty prints the passed variable

jinja2_docs

previous_nth_usable

Returns the previous 'n' usable IP addresses in relation to the passed IP addresses/ranges

ansible_ipaddr

product(iterable_x)

Returns the cartesian product of the passed iterable with 'iterable_x'

ansible

quote

Wrap the passed string in quotes

ansible

random

Returns a random item from the passed sequence

jinja2_docs

random_mac

Generates a random MAC address from the passed string prefix

ansible

readfile

Fetches the contents of a file that is downloaded in FortiSOAR.

FortiSOAR

realpath

Gets the real path of a link

ansible

reduce_on_network(ip_range)

Checks whether multiple addresses belongs to a network

ansible_ipaddr

regex_escape

Escapes special characters within the passed standard pythton regex

ansible

regex_findall(regex_pattern)

Searches for all occurrences of regex matches in the passed string

ansible

regex_replace(regex_to_replace, replacement_regex)

Replaces text in the passed string using regex

ansible

regex_search(regex_to_find)

Finds the first occurrence of regex_to_find in the passed string

ansible

reject(test)

Filters the passed list, removing elements where 'test_x' succeeds

jinja2_docs

rejectattr(attribute_x)

Filters the passed list removing elements where 'attribute_x' evaluates as true

jinja2_docs

relpath(start_point)

Gets the relative path of the passed link from the 'start_point'

ansible

replace(substr_to_replace)

Returns a copy of the passed values with all occurrences of 'substr_to_replace' replaced with the 'new_substring'

jinja2_docs

reverse

Reverses the passed object or returns an iterator that iterates over the passed object in the reverse order

jinja2_docs

root(x)

Returns the 'x' root of the passed value

ansible

round(precision)

Rounds the passed number to the given precision (default 0)

jinja2_docs

safe

Marks the provided value as safe

select(test)

Filters the passed sequence, keeping only the objects for which the test succeeds

jinja2_docs

selectattr(attribute_x)

Filters the passed sequence, keeping only the objects for which 'attribute_x' evaluates as true

jinja2_docs

sha1

Gets the sha1 hash of the passed string

ansible

shuffle

Randomizes the passed list

ansible

slaac

Generates an IPv6 address for a given network and MAC address in stateless configuration

ansible_ipaddr

slice

Slices an iterator and return a list containing those items

jinja2_docs

sort

Sorts an iterable using Python's sorted() function

jinja2_docs

splitext

Gets the root and extension of the passed path or filename

ansible

strftime

Formats a date using the passed date format string

ansible

string

Converts an object to a string if it is not already a string

jinja2_docs

striptags

Strips XML/SGML tags and replaces adjacent whitespaces with one space

jinja2_docs

subelements

Produces a product of the passed list and a subelement of the objects in that list

ansible

sum

Returns the sum of the passed sequence of numbers

jinja2

symmetric_difference(list_x)

Returns the items exclusive to 'list_x' and the passed list

ansible

ternary(output_1, output_2)

Returns 'output_2' if the passed value is false, and 'output_1' if it is true

ansible

title

Return a titlecased version of the passed value

jinja2_docs

toDict

Converts a string into a dictionary

FortiSOAR

toJSON

Dumps a structure to a JSON string

FortiSOAR

to_datetime

Gets a date object from a string

ansible

to_json

Converts a data structure to a JSON format

ansible

to_nice_json

Converts a data structure to a human-readable JSON format

ansible

to_nice_yaml

Converts a data structure to human-readableYAML format

ansible

to_uuid

Creates a UUID from a string

ansible

to_yaml

Converts a data structure to a YAML format

ansible

tojson

Alias of toJSON

FortiSOAR

trim

Strips leading and trailing characters, by default whitespace

jinja2_docs

truncate(n)

Returns a truncated copy of the passed string; truncated to length 'n'

jinja2_docs

type_debug

Displays the underlying Python type of the passed variable

FortiSOAR

union(list_x)

Get the union of 'list_x' with the passed list

ansible

unique

The list of unique items in the passed list

jinja2_docs

upper

Converts the passed string to uppercase

jinja2_docs

urldecode

Decodes the passed URL

FortiSOAR

urlencode

Escapes the strings for use in URLs

FortiSOAR

urlize

Converts URLs into clickable links

jinja2_docs

urlsplit

Extracts the fragment, hostname, netloc, password, patht, port, query, scheme, and username from a URL

ansible

vlan_parser

Transforms the passed unsorted list of VLAN integers into a sorted string list of integers according to IOS-like VLAN list rules

ansible

win_basename

Gets the last name of a windows-style file path

ansible

win_dirname

Gets the directory from a windows path

ansible

win_splitdrive

Separate the windows drive letter from the rest of a file path

ansible

wordcount

Counts the words in the passed string

jinja2_docs

wordwrap(n)

Wraps the given string to width 'n'

jinja2_docs

xml_to_dict

Converts an XML string into a dictionary

FortiSOAR

xmlattr

Creates an XML attribute string based on the items in the passed dict

jinja2_docs

yaql

YAQL (Yet Another Query Language) is an embeddable and extensible query language, which allows users to perform complex queries against arbitrary objects. For more information, see YAQL Filters.

zip(list_x)

Get a list by combining the items from the passed list with those from 'list_x'

ansible

zip_longest

Like 'zip' but always exhausts all input lists

ansible

Notes:

  • If an iterator is returned, pass the output into the "list" filter to get a list.

Sources

Jinja Expressions in FortiSOAR

Following are some examples of jinja expressions used in FortiSOAR:

For Loop

At times, it is useful to use a combination of conditional logic and looping to check particular conditions across a list of values. In this case, the for and the if loop is useful in the Dynamic Variable language.

In the following example, an evaluation of assignment is run across a specific dictionary representing all associated teams within a particular record. These teams have already been assigned to a particular variable from the parent entity:

{% for item in vars.teamName %} 
	{% if item == '/api/3/teams/97fd5a3f-4eaf-4cc1-a132-1c9274bd8428' %}
	Yes  {% endif %}
{% endfor %}

If Condition

{% if 1485561600000 > 1484092800000 %}
        {{vars.input.records[0]}} 
            {% elif 5==6 %}
        {{vars.input.records[0]}} 
{% endif %}

An if condition can cause a playbook to fail if the jinja that you have added returns an empty string, which is not compatible with the field datatype defined in the database. For example, if you have added the following jinja to a {% if vars.currentValue == "Aftermath" %}{{@Current_Date}}{% endif %} field, then the playbook will fail if the jinja returns an empty string.

To ensure that your playbook does not fail due to the issue of jinja returning an empty string, add the following jinja in the field: {% if vars.currentValue == “Aftermath” %}{{Current_Date}}{% else %} None {% endif %}.

For Loop along with the If condition

At times, it can be useful to use a combination of conditional logic and looping during a check of particular conditions across a list of values. In this case, the for and the if loop is useful in the Dynamic Variable language.

{% for i in vars.var_list  %}
        { %  if i not in vars.var_response % } 
            {{vars.var_response.append(i)}}
        {% endif %}
{% endfor %}

The following example uses the for and the if loop to check if a particular team is tagged to a record. In the following example, an evaluation of assignment is run across a specific dictionary representing all of the associated teams within a particular record. These teams have already been assigned to a particular variable from the parent entity.

{% for item in vars.teamName %}  
	{% if item == '/api/3/teams/97fd5a3f-4eaf-4cc1-a132-1c9274bd8428' %} 
	Yes  {% endif %} 
{% endfor %}

If Else condition

If conditions within the Dynamic Variable templating engine can be very useful to avoid unnecessary decision steps. These conditions allow you to specify values defined within specific conditions such that copying, or value branches are not needed.

The following example uses the if else condition to create mapping between Alert Severity and Incident Category.

Here is an example of a particular case in which the value of an IRI is defined based upon the specific conditions evaluated during the execution. Bear in mind these IRI values must be known in this case.

{% if vars.alertSeverity == "/api/3/picklists/ad5eacc1-7c05-3c4e-bba7-eb356c8547c9" %}

	/api/3/picklists/58d0753f-f7e4-403b-953c-b0f521eab759

{% elif vars.alertSeverity == "/api/3/picklists/ddbc7842-dde0-392a-ae94-a1e7a3c7c2f7" %}

	/api/3/picklists/40187287-89fc-4e9c-b717-e9443d57eedb

{% elif vars.alertSeverity == "/api/3/picklists/6c7a8653-a2d5-3611-bc86-8ca307a02e88" %}

	/api/3/picklists/7efa2220-39bb-44e4-961f-ac368776e3b0

{% endif %}

Time Operations

To get timestamp:

{{ arrow.get('2013-05-30 12:30:45', 'YYYY-MM-DD HH:mm:ss') }}

To convert current time into epoch and multiply by 10000:

{{arrow.utcnow().timestamp*1000 |int| abs}}

To convert date to epoch time:

{{ arrow.Arrow(2017, 3, 30).timestamp}}

Convert timezone from UTC to any with formatting

Use the Arrow library to convert dates and times from one-time zone/format to another.

The general format is as follows.

{{ arrow.get( % VARIABLE % ).to('% TIME ZONE ACRONYM %').format('% FORMAT STRING%') }}

The following is an example that is converting the Date of Compromise field into a readable Eastern Standard Time format.

{{arrow.get(vars.input.records[0].dateOfCompromise).to('EST').format('YYYY-MM-DD HH:mm:ss ZZ')}}

Convert timezone from any to UTC and move it up

Using the Arrow library, the general format is as follows.

{{ arrow.get( % VARIABLE %).replace( % TIME VALUE % = % OPERATOR + VALUE %) }}

An example where the Alert Time value is replaced with a four-hour increase.

{{ arrow.get(vars.alertTime).shift(hours=+4) }}

Convert to epoch time to insert into a Record

In this example, a particular UTC time is converted into epoch time in order to format it for API insertion. The API will accept times in epoch as the default.

{{ arrow.get(vars.utcTime).timestamp }}

String Operations

To find the length of list or string:

{{vars.emails | length }}

To replace a string:

{{ vars.var_keys.replace("dict_keys(","" ) | replace( ")", "" )}}

Strip the first X characters of a string

Starting with a string variable, you can pull a portion of the string based on counting the characters.

The general format for this Jinja expressions is as follows where # is the number of characters.

{{ % VARIABLE %[:#] }}

An example here pulls the first 17 characters of the string timeRange.

timeRange = 2016-09-02 13:45:00 EDT - 2016-09-02 14:00:00 ET
alertTime = {{vars.timeRange[:17]}} 

print(alertTime) 

2016-09-02 13:45:00

Remove Strip tags

A particularly useful filter within Dynamic Variables is the striptags() function. This allows you to remove the HTML tags on a particular value, which may be present in rich text or other HTML formats.

The function preserves the data contained within the tags and removes any tagged information contained around the actual values.

{{ vars.input.records[0].name.striptags() }}

Code in block

{% block body %}
        {% for key, value in vars.loop_resource.items() %} 
            {{ key }}: {{ value }} 
        {% endfor %} 
{% endblock %}

Set variable based on condition

{% for i in vars.result['hydra:member'] %}
        {% set id = i['@id'] %} 
        {{ vars.inc_fdata.append(id) }}        
{%endfor%}

Second example:

{% for i in vars.result['hydra:member'] %}
        {% set id = i['@id'] %}
        {% set createDate = i.createDate | string %}
        {% set list_item = [id,createDate] %}
            {{ vars.inc_fdata.append(list_item) }}
{%endfor%}

YAQL Filters

FortiSOAR release 7.2.0 adds support for YAQL as an additional filter language (in addition to JINJA). YAQL (Yet Another Query Language) is an embeddable and extensible query language, which allows users to perform complex queries against arbitrary objects. YAQL contains a comprehensive standard library of frequently used querying functions and can be extended even further with user-specified functions. YAQL is written in python and is distributed via PyPI. For more information on YAQL, see the Getting started with YAQL document.

Usage

In a YAQL query, the $ symbol is used to refer to "this" object. Therefore, $.lastname in a YAQL query pulls the last name of each object in the list passed to the filter.

The $ symbol can also have different meanings within the same query. For example, in $.pets.flatten().where($.type='cat'), the first instance of $ refers to each of the 'User' objects, while the second instance of $ refers to each of the 'Pets' objects. In this example, where() and flatten() are examples of functions available in YAQL. For a list of available YAQL functions see the Standard YAQL Library document.

Usage Examples

  • {{ {"var1":1,"var2":"a"} | yaql('$.var1') }}
    returns# 1
  • {{ "test" | yaql('$.toUpper()') }}
    returns# TEST
  • Example to filter down to non-false data:
    Sample data:
    {
       "data":{
          "av_cate":"Riskware/NetCat",
          "wf_cate":"",
          "ioc_cate":"",
          "ioc_tags":[
             
          ],
          "confidence":"High",
          "spam_cates":[
             
          ],
          "reference_url":"https://ioc.fortiguard.com/search?query=E8FBEC25DB4F9D95B5E8F41CCA51A4B32BE8674A4DEA7A45B6F7AEB22DBC38DB&filter=indicator"
       },
       "success":true
    }
    {{ data | yaql('dict($.items().where(bool($[1])))') }}
    returns#
    {
        "av_cate": "Riskware/NetCat",
        "confidence": "High",
        "reference_url": "https://ioc.fortiguard.com/search?query=E8FBEC25DB4F9D95B5E8F41CCA51A4B32BE8674A4DEA7A45B6F7AEB22DBC38DB&filter=indicator"
    }
  • If you have the following example dataset:
[{

   "firstname": "Billy",

   "lastname": "gu",

   "email": "billsgu@example.com",

   "pets": []

}, {

   "firstname": "Trevor",

   "lastname": "Palmer",

   "email": "palmer928@example.com",

   "pets": [{

      "name": "Butter",

      "type": "dog"

   }]

}, {

   "firstname": "Jimmy",

   "lastname": "Bauer",

   "email": "jbles45@example2.com",

   "pets": [{

      "name": "Biscuit",

      "type": "cat"

   }, {

      "name": "Pepper",

      "type": "cat"

   }]

}]

You can filter this dataset using YAQL as follows:
Get Users where firstname starts with B: vars.users | yaql("$.where($.firstname.startsWith('B'))")
Get Users where email contains @example.com: vars.users | yaql("$.where('@example.com' in $.email)")
Get Users who have pets with type cat: vars.users | yaql("$.where($.pets.where($.type='cat').any())")

Jinja Extensions

There are some official Jinja Extensions that can be used in playbooks and help in enriching expressions:
Jinja Extensions
Note: Support to 'break' and 'continue' ansible loops has been added.

Some examples of using expression statements follow:

Appending a list in a loop example:

{% for i in range(100000)%}
{{vars.printThis}}
{% do vars.res.append(vars.printThis) %}
{%if i==vars.input.params.breakAfterNumberOfLoops %}
{%break%}
{%endif%}
{%endfor%} 

Continue Statement example:

{% for i in range(8)%}
 {% if i == vars.input.params.skipForThisValue%}
  {%continue%}
{%endif%}
 {{i}}
{%endfor%}

Break Statement example:

{% for i in range(100000)%}
{{vars.printThis}}

{%if i==vars.input.params.breakAfterNumberOfLoops %}

{%break%}

{%endif%}

Custom Functions and Filters

FortiSOAR supports following custom functions/filters:

  • Get_current_date: Returns the current date for the file.
  • Get_current_datetime: Returns the current date and time for the file.
  • currentdateminus: Returns a timestamp value of the current date minus the specified days from the current date.
    For example, {{ currentDateMinus(10) }} returns a timestamp after deducting 10 days from the current date.
  • uuid: Returns the UUID of the file {{ uuid() }}.
  • arrow: Returns a python arrow library.
  • toJSON: Converts a JSON to a string. Useful for storing a JSON in a FortiSOAR textarea field, for example, Source Data, so that JSON renders correctly and the content can be presented nicely in the UI.
  • html2text: Converts an HTML string to a text string.
    {{ html_string | html2text}}
    For example, {{'<br>this is html text </br>' | html2text}} .
    Output will be - this is html text.
  • json2html: Converts JSON data into HTML. The FortiSOAR template is used for HTML and styling of the output.
    {{ jsondata | json2html(row_fields)}}
    row_fields= ['pid', 'sid']. You can optionally specify the row_fields attribute. If you do not specify the row_fields, by default, this filter takes all keys as row fields.
    An example without row fields specified: {{ [{"pid": 123, "sid": "123", "path": "abc.txt"}] | json2html}}.
    An example with row fields specified: {{ [{"pid": 123, "sid": "123", "path": "abc.txt"}] | json2htmll([‘pid’, ‘sid’])}}.
    The HTML output of the above example will be:
    <table class="cs-data-table"> <tr> <th>pid</th> <th>sid</th> </tr> <tr> <td>123</td> <td>123</td> </tr> </table><button style="display:none" class="cs-datatable-btn btn-link cs-datatable-showmore-btn" type="button" onClick="event.target.previousElementSibling.className += ' cs-data-table-show-more'; event.target.nextElementSibling.style.display = 'block'; event.target.style.display = 'none';">Show more</button><button class="cs-datatable-btn btn-link cs-datatable-showless-btn" type="button" onClick="event.target.previousElementSibling.previousElementSibling.className = 'cs-data-table'; event.target.previousElementSibling.style.display = 'block'; event.target.style.display = 'none';">Show less</button>
  • resolveIRI : Resolves the given IRI.
    For example, {{ “/api/3/alerts/<alert-id>” | resolveIRI}}
  • count_occurrence: Retrieves the number of times each element appears in the list.
    For example, {{ [‘apple','red','apple','red','red','pear’] | count_occurrence }}
    The output of this example is: {'red': 3, 'apple': 2, 'pear': 1}
  • urlencode: Encodes the given URL.
    For example, {{"/api/3/alerts/?name=test" | urlencode}}
    The output of this example is: %2Fapi%2F3%2Falerts%2F%3Fname%3Dtest
  • urldecode: Decodes the given (encoded) URL.
    For example, {{"%2Fapi%2F3%2Falerts%2F%3Fname%3Dtest" | urldecode}}
    The output of this example is: /api/3/alerts/?name=test
  • loadRelationships(moduleName, selectFields = []): Used to fetch details of a related (correlation) record. For example, {{ vars.incidentIRI | loadRelationships('indicators') }}
    To fetch complete details of the correlation record, use {{ #recordIRI# | loadRelationships('#CorrelationModuleFieldName#') }}
    To fetch specific fields of the correlation record, use {{ #recordIRI# | loadRelationships('#CorrelationModuleFieldName#',['#field1#','#field2#']) }}
  • picklist: Loads the specified picklist item object. For example, {{"PicklistName" | picklist("ItemValue") }}
    The output of this example is an object including the @id, color, itemValue, listName, and orderIndex of the picklist item. You can extract just a particular key from the object by specifying a second argument to the filter:
    {{"PicklistName" | picklist("ItemValue", "@id") }}. This will generate /api/3/picklists/<uuid>

Jinja Filters and Functions

Overview

Use jinja2 filters to design and manipulate playbook step outputs. Jinja operations are supported in the Playbook Engine and you can also use the Custom Functions and Filters that are documented in this chapter.

Note

All filters are case-sensitive.


These examples present in this chapter provide a reference to common and very useful string operations that may be leveraged within the Playbook engine.

You can also use Jinja extensions to enrich expressions, for more information, see the Jinja Extensions topic.

Filters

FortiSOAR supports the following filters:

  • fromIRI: Will resolve an IRI and return the object(s) that live(s) there. This is similar to loading the object by id (IRI).
    {{ '/api/3/events/8' | fromIRI }}{{ vars.event.alert_iri | fromIRI }}
    You can use dot access for values returned by fromIRI.
    For example: To get a person record and return their 'name' field you can use the following:
    {{ (vars.person_iri | fromIRI).name }}
    You can also use fromIRI recursively, for example:
    {{ ((vars.event.alert |fromIRI).owner| fromIRI).name }}
    You can also retrieve relationship data for a record on which a playbook is running, for example:
    {{('/api/3/alerts/<alert_IRI>?$relationships=true' | fromIRI).indicators}}
  • toDict: attempt to coerce a string into a dictionary for access.
    {{ (request.data.incident_string | toDict).id }}
  • xml_to_dict: Converts an XML string into a dictionary for access:
    {{ '<?xml version="1.0" ?><person><name>john</name><age>20</age></person>' | xml_to_dict }}
  • extract_artifacts: Parses and extracts a list of IOCs from a given string:
    {{'abc.com 192.168.42.23' | extract_artifacts}}
  • parse_cef: Parses a given CEF string and converts the CEF string into a dictionary:
    {{ 'some string containing cef' | parse_cef }}
  • readfile: Fetches the contents of a file that is downloaded in FortiSOAR:
    {{ vars.result | readfile}}
    where vars.result is the name of the file.
  • ip_range: Checks if the IP address is in the specified range:
    {{vars.ip | ip_range(‘198.162.0.0/24’)}}
  • counter: Gets the count of each item's occurrence in an array of items:
    {{data| counter}}
    For example:
    data: [‘apple’,‘red’,‘apple’,‘red’,‘red’,‘pear’]
    {{data| counter}}
    Result: {‘red’: 3, ‘apple’: 2, ‘pear’: 1}

FortiSOAR also supports following filters, more information for which is present at http://docs.ansible.com/ansible/latest/playbooks_filters.html.

Filters for formatting data

The following filters take a data structure in a template and render it in a slightly different format. These 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 }}

It is also possible to change the indentation the variables:

{{ some_variable | to_nice_json(indent=2) }}
{{ some_variable | to_nice_yaml(indent=8) }}

Alternatively, you may be reading in some already formatted data:

{{ some_variable | from_json }}
{{ some_variable | from_yaml }}

Filters that operate on list variables

To get the minimum value from a list of numbers:

{{ list1 | min }}

To get the maximum value from a list of numbers:

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

Filters that return a unique set from sets or lists

To get a unique set from a list:

{{ list1 | unique }}

To get a union of two lists:

{{ list1 | union(list2) }}

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

{{ list1 | intersect(list2) }}

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

{{ list1 | difference(list2) }}

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

{{ list1 | symmetric_difference(list2) }}

Random Number filter

The following filters can be used similar to the default jinja2 random filter (returning a random item from a sequence of items), but they can also be used 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 from 0 to supplied end:

{{ 59 |random}}
# => 21

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

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

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

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

To initialize the random number generator from a seed. This way, you can create random-but-idempotent numbers:

{{ 59 |random(seed=inventory_hostname) }}

Shuffle filter

The following filters randomize 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']

To shuffle a list idempotent. For this you will require a seed:

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

When this filter is used with a non ‘listable’ item it is a noop. Otherwise, it always returns a list.

Filters for math operations

To get the logarithm (default is e):

{{ myvar | log }}

To get the base 10 logarithm:

{{ myvar | log(10) }}

To get the power of 2! (or 5):

{{ myvar | pow(2) }}
{{ myvar | pow(5) }}

To get the square root, or the 5th:

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

IP Address filters

To test if a string is a valid IP address:

{{ myvar | ipaddr }}

To get the IP address in a specific IP protocol version:

{{ myvar | ipv4 }}
{{ myvar | ipv6 }}

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' | ipaddr('address') }}

To filter a list of IP addresses:

test_list = ['192.24.2.1', 'host.fqdn', '::1', '192.168.32.0/24', 'fe80::100/10', True, '', '42540766412265424405338506004571095040/64']

# {{ test_list | ipaddr }}
['192.24.2.1', '::1', '192.168.32.0/24', 'fe80::100/10', '2001:db8:32c:faad::/64']

# {{ test_list | ipv4 }}
['192.24.2.1', '192.168.32.0/24']
# {{ test_list | ipv6 }}
['::1', 'fe80::100/10', '2001:db8:32c:faad::/64']

To get a host IP address from a list of IP addresses:

# {{ test_list | ipaddr('host') }}
['192.24.2.1/32', '::1/128', 'fe80::100/10']

To get a public IP address from a list of IP addresses:

# {{ test_list | ipaddr('public') }}
['192.24.2.1', '2001:db8:32c:faad::/64']

To get a private IP address from a list of IP addresses:

# {{ test_list | ipaddr('private') }}
['192.168.32.0/24', 'fe80::100/10']

Network range as a query:

# {{ test_list | ipaddr('192.0.0.0/8') }}
['192.24.2.1', '192.168.32.0/24']

Hashing filters

To get the sha1 hash of a string:

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

To get the md5 hash of a string:

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

To 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') }}

Note

FortiSOAR uses the haslib library for hash and passlib library for password_hash.

Filters for combining hashes and 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 also accepts an optional recursive=True parameter to not only override keys in the first hash, but also recursively into nested hashes and merge their keys too:

{{ {'a':{'foo':1, 'bar':2}, 'b':2}|combine({'a':{'bar':3, 'baz':4}}, recursive=True) }}

The resulting hash would be:

{'a':{'foo':1, 'bar':3, 'baz':4}, 'b':2}

The filter can also take multiple arguments to merge:

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

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

Filters for extracting values from containers

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’].

Comment filter

The comment filter allows you to decorate the text with a chosen comment style. For example, the following

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

will produce the following output:

#
# Plain style (default)
#

Similarly you can apply style to the comments for C (//...), C block (/*...*/), Erlang (%...) and XML (<!--...-->):

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

It is also possible to fully customize the comment style:

{{ "Custom style" | comment('plain', prefix='#######\n#', postfix='#\n#######\n ###\n #') }}

which creates the following output:

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

URL Split filter

The urlsplit filter extracts the fragment, hostname, netloc, password, path, port, query, scheme, and username from an URL. If you do not provide any arguments to this filter then it returns a dictionary of all the fields:

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

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

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

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

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

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

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

{{ "http://user:password@www.acme.com:9000/dir/index.html?query=term#frament" | 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"
# }

Regular Expression filters

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)') }}

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') }}

To escape special characters within a regex, use the regex_escape filter:

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

Other useful filters

To add quotes for shell usage:

{{ string_value | quote }}

To use one value on true and another on false:

{{ (name == "John") | ternary('Mr','Ms') }}

To concatenate a list into a string:

{{ list | join(" ") }}

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:

{{ path | win_basename }}

To separate the windows drive letter from the rest of a file path:

{{ 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:

{{ path | win_dirname }}

To expand a path containing a tilde (~) character:

{{ path | expanduser }}

To get the real path of a link:

{{ path | realpath }}

To get the relative path of a link, from a start point:

{{ path | relpath('/etc') }}

To get the root and extension of a path or filename:

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

To work with Base64 encoded strings:

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

To create a UUID from a string:

{{ hostname | to_uuid }}

To get date object from string use the to_datetime filter:

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

Combination filters

This set of filters returns a list of combined lists.

To get permutations of a list:

To get the largest permutations (order matters):

{{ [1,2,3,4,5] |permutations|list }}

To get the permutations of sets of three:

{{ [1,2,3,4,5] |permutations(3)|list }}

Combinations always require a set size:

To get the combinations for sets of two:

{{ [1,2,3,4,5] |combinations(2)|list }}

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

To get a combination of two lists:

{{ [1,2,3,4,5] |zip(['a','b','c','d','e','f'])|list }}

To get the shortest combination of two lists:

{{ [1,2,3] |zip(['a','b','c','d','e','f'])|list }}

To always exhaust all lists use zip_longest:

To get the longest combination of all three lists, fill with X:

{{ [1,2,3] |zip_longest(['a','b','c','d','e','f'], [21, 22, 23], fillvalue='X')|list }}

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

Debugging filters

Use the 'type_debug' filter to display the underlying Python type of a variable. This can be useful in debugging in cases where you might need to know the exact type of a variable:

{{ myvar | type_debug }}

FortiSOAR also supports following built-in filters from Jinja, more information for which is present at Template Designer Documentation — Jinja Documentation (2.11.x).

  • abs (number): Returns the absolute value of the argument.
  • attr (obj, name): Gets an attribute of an object. foo|attr("bar") works like foo.bar just that always an attribute is returned and items are not looked up.
    See Notes on subscriptions for more details.
  • batch (value, linecount, fill_with=None): Batches items. It works pretty much like slice just the other way around. It returns a list of lists with the given number of items. If you provide a second parameter, this is used to fill up missing items. For example:
    {% for row in items|batch(3, 'FillerString') %}
        {% for column in row %}
           {{ column }}
       {% endfor %}
    {% endfor %}
    
  • capitalize(s): Capitalizes a value. The first character will be uppercase, all others lowercase.
  • center(value, width=80): Centers the value in a field of a given width.
  • default(value, default_value=u'', boolean=False): If the value is undefined it will return the passed default value, otherwise the value of the variable:
    {{ my_variable|default('my_variable is not defined') }}
    

    This would output the value of my_variable if the variable was defined. Otherwise, my_variable is not defined. If you want to use default with variables that evaluate to false, you have to set the second parameter to true:

    {{ ''|default('the string was empty', true) }}
    
  • dictsort(value, case_sensitive=False, by='key'): Sorts a dict and yields (key, value) pairs. Because python dicts are unsorted you might want to use this function to order them by either key or value:
    {% for item in mydict|dictsort %}
        sort the dict by key, case insensitive
    
    {% for item in mydict|dictsort(true) %}
        sort the dict by key, case sensitive
    
    {% for item in mydict|dictsort(false, 'value') %}
        sort the dict by value, case insensitive
    
  • escape(s): Converts the characters &, <, >, , and in strings to HTML-safe sequences. Use this if you need to display text that might contain such characters in HTML. Marks return value as markup string.
  • filesizeformat(value, binary=False): Formats the value like a human-readable file size (i.e. 13 kB, 4.1 MB, 102 Bytes, etc). Per default decimal prefixes are used (Mega, Giga, etc.) if the second parameter is set to True the binary prefixes are used (Mebi, Gibi).-
  • first(seq): Returns the first item of a sequence.
  • float(value, default=0.0): Converts the value into a floating point number. If the conversion doesn’t work, it will return 0.0. You can override this default using the first parameter.
  • forceescape(value): Enforces HTML escaping. This will probably double escape variables.
  • format(value, args,**kwargs): Applies python string formatting on an object:
    {{ %s"|format("Hello?", "Foo!") }}
        -> Hello? - Foo!
    
  • groupby(value, attribute): Groups a sequence of objects by a common attribute. If you, for example, have a list of dicts or objects that represent persons with gender, first_name, and last_name attributes and you want to group all users by genders you can do something like the following snippet:
    {% for group in persons|groupby('gender') %}
           {{ group.grouper }}
        {% for person in group.list %}
           {{ person.first_name }} {{ person.last_name }}
        {% endfor %}
    {% endfor %}
    

    Additionally, it is possible to use tuple unpacking for the grouper and list:

    {% for grouper, list in persons|groupby('gender') %}
        ...
    {% endfor %}
    

    As you can see the item, we are grouping by are stored in the grouper attribute, and the list contains all the objects that have this grouper in common. You can also use dotted notation to group by the child attribute of another attribute.

  • indent(s, width=4, indentfirst=False): Returns a copy of the passed string, each line indented by four spaces. The first line is not indented. If you want to change the number of spaces or indent the first line too you can pass additional parameters to the filter:
      {{ mytext|indent(2, true) }}
        indent by two spaces and indent the first line too.
    
  • int(value, default=0, base=10): Converts the value into an integer. If the conversion does not work, it will return 0. You can override this default using the first parameter. You can also override the default base (10) in the second parameter, which handles input with prefixes such as 0b, 0o and 0x for bases 2, 8 and 16 respectively. The base is ignored for decimal numbers and non-string values.
  • join(value, d=u'', attribute=None): Returns a string which is the concatenation of the strings in the sequence. The separator between elements is an empty string per default; you can define it with the optional parameter:
     {{ [1, 2, 3] |join('|') }}
        -> 1 |2|3
    
    {{ [1, 2, 3]|join }}
        -> 123
    

It is also possible to join certain attributes of an object:

{{ users|join(', ', attribute='username') }}
  • last(seq): Returns the last item of a sequence.
  • length(object): Returns the number of items of a sequence or mapping.
    Aliases: count
  • list(value): Converts the value into a list. If it were a string, the returned list would be a list of characters.
  • lower(s): Converts a value to lowercase.
  • map(): Applies a filter on a sequence of objects or looks up an attribute. This is useful when dealing with lists of objects, but you are only interested in a certain value of it.
    The basic usage is mapping on an attribute. Imagine you have a list of users, but you are only interested in a list of usernames:
    Users on this page: {{ users |map(attribute='username')|join(', ') }}
    

    Alternatively, you can let it invoke a filter by passing the name of the filter and the arguments afterward. A good example would be applying a text conversion filter on a sequence:

    Users on this page: {{ titles |map('lower')|join(', ') }}
    
  • pprint(value, verbose=False): Pretty print a variable. Useful for debugging. With Jinja 1.2 onwards you can pass it a parameter. If this parameter is truthy, the output will be more verbose (this requires pretty).
  • random(seq): Returns a random item from the sequence.
  • reject(): Filters a sequence of objects by applying a test to each object, and rejecting the objects whose tests succeed.
    If no test is specified, each object will be evaluated as a boolean. For example:
    {{ numbers|reject("odd") }}
    
  • rejectattr(): Filters a sequence of objects by applying a test to the specified attribute of each object, and rejecting the objects whose tests succeed.
    If no test is specified, the attribute’s value will be evaluated as a boolean.
    {{ users|rejectattr("is_active") }}
    {{ users|rejectattr("email", "none") }}
    
  • replace(s, old, new, count=None): Returns a copy of the value with all occurrences of a substring replaced with a new one. The first argument is the substring that should be replaced; the second is the replacement string. If the optional third argument count is given, only the firstcount occurrences are replaced:
    {{ "Hello World"|replace("Hello", "Goodbye") }}
        -> Goodbye World
    
    {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
        -> d'oh, d'oh, aaargh
    
  • reverse(value): Reverses the object or returns an iterator that iterates over it the other way round.
  • round(value, precision=0, method='common'): Round the number to a given precision. The first parameter specifies the precision (default is 0), the second the rounding method:
    • 'common' rounds either up or down: Default method.
    • 'ceil' always rounds up
    • 'floor' always rounds down
      {{ 42.55|round }}
          -> 43.0
      {{ 42.55|round(1, 'floor') }}
          -> 42.5
      

    Note that even if rounded to 0 precision, a float is returned. If you need a real integer, pipe it through int:

    {{ 42.55 |round|int }}
        -> 43
    
  • safe(value): Marks the value as safe which means that in an environment with automatic escaping enabled this variable will not be escaped.
  • select(): Filters a sequence of objects by applying a test to each object, and only selecting the objects whose tests succeed.
    If no test is specified, each object will be evaluated as a boolean. For example,
    {{ numbers|select("odd") }}
    {{ numbers|select("odd") }}
    
  • selectattr(): Filters a sequence of objects by applying a test to the specified attribute of each object, and only selecting the objects whose tests succeed.
    If no test is specified, the attribute’s value will be evaluated as a boolean. For example,
    {{ users|selectattr("is_active") }}
    {{ users|selectattr("email", "none") }}
    
  • slice(value, slices, fill_with=None): Slices an iterator and returns a list of lists containing those items. Useful if you want to create a div containing three ul tags that represent columns:
    <div class="columwrapper">
      {% for column in items|slice(3) %}
        <ul class="column-{{ loop.index }}">
        {% for item in column %}
          <li>{{ item }}</li>
        {% endfor %}
        </ul>
      {% endfor %}
    </div>
    

    If you pass it a second argument, it is used to fill missing values on the last iteration.

  • sort(value, reverse=False, case_sensitive=False, attribute=None): Sorts an iterable. Per default it sorts ascending, if you pass it true as the first argument, it will reverse the sorting.
    If the iterable is made of strings, the third parameter can be used to control the case sensitiveness of the comparison which is disabled by default.
    {% for item in iterable|sort %}
        ...
    {% endfor %}
    

    It is also possible to sort by an attribute (for example to sort by the date of an object) by specifying the attribute parameter:

    {% for item in iterable|sort(attribute='date') %}
        ...
    {% endfor %}
    
  • string(object): Makes a string unicode if it isn’t already. That way a markup string is not converted back to unicode.
  • striptags(value): Strips SGML/XML tags and replace adjacent whitespace by one space.
  • sum(iterable, attribute=None, start=0): Returns the sum of a sequence of numbers plus the value of parameter ‘start’ (which defaults to 0). When the sequence is empty, it returns to start.
    It is also possible, to sum up only certain attributes:
    Total: {{ items|sum(attribute='price') }}
    

    The attribute parameter was added to allow summing up over attributes. Also, the start parameter was moved on to the right.

  • title(s): Returns a titlecased version of the value, i.e., words will start with uppercase letters, all remaining characters are lowercase.
  • tojson or toJSON (value, indent=None): Dumps a structure to JSON so that it’s safe to use in <script> tags. It accepts the same arguments and returns a JSON string. Note that this is available in templates through the |tojson filter which will also mark the result as safe. Due to how this function escapes certain characters this is safe even if used outside of <script> tags.
    The following characters are escaped in strings: <>&'
    This makes it safe to embed such strings in any place in HTML with the notable exception of double quoted attributes. In that case single quote your attributes or HTML escape also.
    The indent parameter can be used to enable pretty printing. Set it to the number of spaces that the structures should be indented with.
    Note that this filter is for use in HTML contexts only.
  • trim(value): Strips the leading and trailing whitespace.
  • truncate(s, length=255, killwords=False, end='...', leeway=None): Returns a truncated copy of the string. The length is specified with the first parameter which defaults to 255. If the second parameter is true, the filter will cut the text at length. Otherwise, it will discard the last word. If the text was in fact truncated, it would append an ellipsis sign ("..."). If you want a different ellipsis sign than "..." you can specify it using the third parameter. Strings that only exceed the length by the tolerance margin given in the fourth parameter will not be truncated.
    {{ "foo bar baz qux"|truncate(9) }}
        -> "foo..."
    {{ "foo bar baz qux"|truncate(9, True) }}
        -> "foo ba..."
    {{ "foo bar baz qux"|truncate(11) }}
        -> "foo bar baz qux"
    {{ "foo bar baz qux"|truncate(11, False, '...', 0) }}
        -> "foo bar...
    

    The default leeway on newer Jinja2 versions is 5 and was 0 before but can be reconfigured globally.

  • upper(s): Converts a value to uppercase.
  • urlencode(value): Escape strings for use in URLs (uses UTF-8 encoding). It accepts both dictionaries and regular strings as well as pairwise iterables.
  • urlize(value, trim_url_limit=None, nofollow=False, target=None, rel=None): Converts URLs in plain text into clickable links.
    If you pass the filter an additional integer, it will shorten the URLs to that number. Also, a third argument exists that makes the URLs “nofollow”:
    {{ mytext|urlize(40, true) }}
        links are shortened to 40 chars and defined with rel="nofollow"
    

    If the target is specified, the target attribute will be added to the <a> tag:

    {{ mytext|urlize(40, target='_blank') }}
    
  • wordcount(s): Counts the words in that string.
  • wordwrap(s, width=79, break_long_words=True, wrapstring=None): Returns a copy of the string passed to the filter wrapped after 79 characters. You can override this default using the first parameter. If you set the second parameter to false Jinja will not split words apart if they are longer than the width. By default, the newlines will be the default newlines for the environment, but this can be changed using the wrapstring keyword argument.
  • xmlattr(d, autospace=True): Creates an SGML/XML attribute string based on the items in a dict. All values that are neither none nor undefined are automatically escaped:
    <ul{{ {'class': 'my_list', 'missing': none,
    	'id': 'list-%d' |format(variable)}|xmlattr }}>
    ...
    </ul>

    Results in something like this:

    <ul class="my_list" id="list-42">
    ...
    </ul>

    As you can see it automatically prepends a space in front of the item if the filter returned something unless the second parameter is false.

json_query filter

Use the json_query filter when you have a complex data structure in the JSON format from which you require to extract only a small set of data. The json_query filter enables you to query and iterate a complex JSON structure. The filter is built using jmespath, and you can use the same syntax in the json_query filter. For details on jmespath, see JMESPath Examples.

Example

The result of your playbook step is as follows:

[
 {"name": "a", "state": "running"},
 {"name": "b", "state": "stopped"},
 {"name": "b", "state": "running"}
]

From this result, you want to query only the names of those objects who are in the running state. For this query the valid jinja expression would be: {{ vars.result | json_query(" [?state=='running'].name") }} , which would have the following result:

[
  "a",
  "b"
]

To create a valid JSON query, you can refer to JMESPath Tutorial.

Comprehensive list of filters

The following table contains a comprehensive list of filters that you can use:

Filter Description Source
abs Absolute value of a number jinja2_docs
attr(x) Gets attribute 'x' of an object. Does not return *items*, which are dictionary keys jinja2_docs
b64decode Decodes a base64 value ansible
b64encode Encodes a value as base64 ansible
basename Last name in a filepath ansible
batch(n) Separates a list into various lists with size 'n' jinja2_docs
bool Casts a string as a boolean value (i.e. "True" or "False" to a boolean value) ansible

capitalize

Capitalizes the first letter of a value

jinja2_docs

center(n)

Centers the value in a field of 'n' characters by adding spaces on either side

jinja2_docs

checksum

Get a string checksum

ansible

cidr_merge

Merges a list of subnets or IP addresses to their minimal representation

ansible_ipaddr

combinations(n)

Returns an iterator of n-size combinations of items from an input list

ansible

combine(dict_x, recursive=False)

Merges dect_x into input dictionary, overwriting any values that overlap. Setting recursive=True also allows for nested keys to be merged.

ansible

comment

Converts a string into a python-style comment

ansible

comp_type5

ansible.netcommon comp_type5 filter plugin

ansible.netcommon

count

Alias of length

jinja2_docs

count_occurrence

Retrieves the number of times each element appears in the list.

FortiSOAR

counter

Gets the count of each item's occurrence in an array of items.

FortiSOAR

d(x)

Alias of "default"

jinja2_docs

default(x)

Outputs default value 'x' if the passed input is not defined.

jinja2_docs

dict2items

Turn a dictionary into a list of items suitable for looping

ansible

dictsort(false,reverse=false)

Sorts a dict and yields key, value pairs. Set the first argument to true for case sensitive sort. Provide 'value' as second argument to sort by value instead of key.

jinja2_docs

difference(list_x)

Gets items from an input list that are not present in 'list_x'.

ansible

dirname

Gets the directory from a path

ansible

e

Alias of escape

jinja2_docs

escape

Escapes characters &, M, >, ', and " with HTML-safe sequences

jinja2_docs

expanduser

Expands a path containing a telde(~) character

ansible

expandvars

Expands a path containing environment variables

ansible

extract()

Maps a list of indices to a list of values from a container (hash or array)

FortiSOAR

extract_artifacts

Parses and extracts a list of IOCs from a given string

FortiSOAR

extract_cef/parse_cef

Parses a given CEF string and converts the CEF string into a dictionary

FortiSOAR

fileglob

Provides an output list of matching files from the given input path (can include wildcards, for example '/tmp/*.txt').

ansible

filesizeformat

Formats a number into "human readable" file size, for example, 13 KB

jinja2_docs

first

Returns the first item of a sequence

jinja2_docs

flatten

Flattens a list

ansible

float

Converts a value into floating point number

jinja2_docs

forceescape

Enforces HTML escaping. Can lead to double-escaping

jinja2_docs

format(string_x)

Formats 'string_x' based on the passed format string

jinja2_docs

fromIRI

Resolve an IRI and return the object(s) that live(s) there. This is similar to loading the object by id (IRI)

FortiSOAR

from_json

Converts a json-formatted string to dict.

ansible

from_yaml

Converts a yaml-formatted string to dict

ansible

from_yaml_all

Parses a multi-document yaml string to an iterator of parsed yaml documents

ansible

groupby(value)

Groups a sequence of objects by their attribute value

jinja2_docs

hash(hashtype)

Gets the hash of a string, using hashtype, for example, 'md5', or 'sha1'

ansible

html2texthash

Converts an HTML string to a text string

FortiSOAR

human_readable

Asserts whether the given string is human readable or not

ansible

human_to_bytes

Returns the given string in bytes format

ansible

hwaddr

Checks if a string is a MAC address

ansible_ipaddr

indent

Returns the input string with each line indented by four spaces

jinja2_docs

int

Converts the value to an integer

jinja2_docs

intersect(list_x)

Gets a list of unique items that is present in both the 'input list' and 'list_x'

ansible

ip4_hex

Converts IPv4 to an Hexadecimal notation

ansible_ipaddr

ip_range(ip_range)

Checks if the IP address is in the specified CIDR range

FortiSOAR

ipaddr

Checks if a string is a valid IP address

FortiSOAR

ipmath(n)

Gets the next 'n' addresses based on the passed parameter in specified IP address

ansible_ipaddr

ipsubnet

Converts an ip address to a subnet

ansible_ipaddr

ipv4

Checks if the given IP address is an IPv4 address

ansible_ipaddr

ipv6

Checks if the given IP address is an IPv6 address

ansible_ipaddr

ipwrap

Wraps any IPv6 addresses in brackets in the provide a list of strings, leaving other items intact

ansible_ipaddr

items2dict(key=k, value=v)

Reverse of dict2items, i.e., maps key and value into a dictionary

ansible

join(delim_x)

Returns a string that is the concatenation of the strings in the passed sequence. If 'delim_x' is provided, it is used to separate the items in the string

jinja2_docs

json2html

Converts JSON data into HTML

FortiSOAR

json_query

Allows the use of jmespath expressions (see jmespath.org) to manipulate input data

ansible

last

Returns the last item of a sequence

jinja2_docs

length

Returns the number of items in a sequence

jinja2_docs

list

Converts the value into a list

jinja2_docs

loadRelationships

Fetches details of a related (correlation) record

FortiSOAR

log(base=e)

Gets the log (default base e) of the passed values

ansible

lower

Converts a value to lowercase

jinja2_docs

mandatory

Raises an error if the passed variable is undefined

ansible

map

Applies a filter on a sequence of objects or looks up an attribute

jinja2_docs

max

Returns the largest item from the sequence

jinja2_docs

md5

Gets the md5 hash of a string

ansible

min

Returns the smallest item from the sequence

jinja2_docs

network_in_network

Returns whether 'address_x' is in the passed network

ansible_ipaddr

network_in_usable

Returns whether an address passed as an argument is usable in a network

ansible_ipaddr

next_nth_usable(n)

Returns the next 'n' usable IP addresses in relation to the passed IP addresses/ranges

ansible_ipaddr

nthhost(n)

Returns the nth IP address in the passed CIDR range

ansible_ipaddr

parse_cli

Converts the output of a network device CLI command into a structured JSON output

ansible

parse_cli_textfsm

Parses output of a network device CLI command using the TextFSM library

ansible

parse_xml(path_to_specfile)

Converts XML output of a network device command into a structured JSON output

ansible

password_hash(algorithm, salt)

Gets a password hash with a specified hashing algorithm, and optionally a provided salt value

ansible

permutations

Gets an iterator of all permutations of values in a list

ansible

picklist

Loads the specified picklist item object

FortiSOAR

pow(x)

Returns passed values to the power of 'x'

ansible

pprint

Pretty prints the passed variable

jinja2_docs

previous_nth_usable

Returns the previous 'n' usable IP addresses in relation to the passed IP addresses/ranges

ansible_ipaddr

product(iterable_x)

Returns the cartesian product of the passed iterable with 'iterable_x'

ansible

quote

Wrap the passed string in quotes

ansible

random

Returns a random item from the passed sequence

jinja2_docs

random_mac

Generates a random MAC address from the passed string prefix

ansible

readfile

Fetches the contents of a file that is downloaded in FortiSOAR.

FortiSOAR

realpath

Gets the real path of a link

ansible

reduce_on_network(ip_range)

Checks whether multiple addresses belongs to a network

ansible_ipaddr

regex_escape

Escapes special characters within the passed standard pythton regex

ansible

regex_findall(regex_pattern)

Searches for all occurrences of regex matches in the passed string

ansible

regex_replace(regex_to_replace, replacement_regex)

Replaces text in the passed string using regex

ansible

regex_search(regex_to_find)

Finds the first occurrence of regex_to_find in the passed string

ansible

reject(test)

Filters the passed list, removing elements where 'test_x' succeeds

jinja2_docs

rejectattr(attribute_x)

Filters the passed list removing elements where 'attribute_x' evaluates as true

jinja2_docs

relpath(start_point)

Gets the relative path of the passed link from the 'start_point'

ansible

replace(substr_to_replace)

Returns a copy of the passed values with all occurrences of 'substr_to_replace' replaced with the 'new_substring'

jinja2_docs

reverse

Reverses the passed object or returns an iterator that iterates over the passed object in the reverse order

jinja2_docs

root(x)

Returns the 'x' root of the passed value

ansible

round(precision)

Rounds the passed number to the given precision (default 0)

jinja2_docs

safe

Marks the provided value as safe

select(test)

Filters the passed sequence, keeping only the objects for which the test succeeds

jinja2_docs

selectattr(attribute_x)

Filters the passed sequence, keeping only the objects for which 'attribute_x' evaluates as true

jinja2_docs

sha1

Gets the sha1 hash of the passed string

ansible

shuffle

Randomizes the passed list

ansible

slaac

Generates an IPv6 address for a given network and MAC address in stateless configuration

ansible_ipaddr

slice

Slices an iterator and return a list containing those items

jinja2_docs

sort

Sorts an iterable using Python's sorted() function

jinja2_docs

splitext

Gets the root and extension of the passed path or filename

ansible

strftime

Formats a date using the passed date format string

ansible

string

Converts an object to a string if it is not already a string

jinja2_docs

striptags

Strips XML/SGML tags and replaces adjacent whitespaces with one space

jinja2_docs

subelements

Produces a product of the passed list and a subelement of the objects in that list

ansible

sum

Returns the sum of the passed sequence of numbers

jinja2

symmetric_difference(list_x)

Returns the items exclusive to 'list_x' and the passed list

ansible

ternary(output_1, output_2)

Returns 'output_2' if the passed value is false, and 'output_1' if it is true

ansible

title

Return a titlecased version of the passed value

jinja2_docs

toDict

Converts a string into a dictionary

FortiSOAR

toJSON

Dumps a structure to a JSON string

FortiSOAR

to_datetime

Gets a date object from a string

ansible

to_json

Converts a data structure to a JSON format

ansible

to_nice_json

Converts a data structure to a human-readable JSON format

ansible

to_nice_yaml

Converts a data structure to human-readableYAML format

ansible

to_uuid

Creates a UUID from a string

ansible

to_yaml

Converts a data structure to a YAML format

ansible

tojson

Alias of toJSON

FortiSOAR

trim

Strips leading and trailing characters, by default whitespace

jinja2_docs

truncate(n)

Returns a truncated copy of the passed string; truncated to length 'n'

jinja2_docs

type_debug

Displays the underlying Python type of the passed variable

FortiSOAR

union(list_x)

Get the union of 'list_x' with the passed list

ansible

unique

The list of unique items in the passed list

jinja2_docs

upper

Converts the passed string to uppercase

jinja2_docs

urldecode

Decodes the passed URL

FortiSOAR

urlencode

Escapes the strings for use in URLs

FortiSOAR

urlize

Converts URLs into clickable links

jinja2_docs

urlsplit

Extracts the fragment, hostname, netloc, password, patht, port, query, scheme, and username from a URL

ansible

vlan_parser

Transforms the passed unsorted list of VLAN integers into a sorted string list of integers according to IOS-like VLAN list rules

ansible

win_basename

Gets the last name of a windows-style file path

ansible

win_dirname

Gets the directory from a windows path

ansible

win_splitdrive

Separate the windows drive letter from the rest of a file path

ansible

wordcount

Counts the words in the passed string

jinja2_docs

wordwrap(n)

Wraps the given string to width 'n'

jinja2_docs

xml_to_dict

Converts an XML string into a dictionary

FortiSOAR

xmlattr

Creates an XML attribute string based on the items in the passed dict

jinja2_docs

yaql

YAQL (Yet Another Query Language) is an embeddable and extensible query language, which allows users to perform complex queries against arbitrary objects. For more information, see YAQL Filters.

zip(list_x)

Get a list by combining the items from the passed list with those from 'list_x'

ansible

zip_longest

Like 'zip' but always exhausts all input lists

ansible

Notes:

  • If an iterator is returned, pass the output into the "list" filter to get a list.

Sources

Jinja Expressions in FortiSOAR

Following are some examples of jinja expressions used in FortiSOAR:

For Loop

At times, it is useful to use a combination of conditional logic and looping to check particular conditions across a list of values. In this case, the for and the if loop is useful in the Dynamic Variable language.

In the following example, an evaluation of assignment is run across a specific dictionary representing all associated teams within a particular record. These teams have already been assigned to a particular variable from the parent entity:

{% for item in vars.teamName %} 
	{% if item == '/api/3/teams/97fd5a3f-4eaf-4cc1-a132-1c9274bd8428' %}
	Yes  {% endif %}
{% endfor %}

If Condition

{% if 1485561600000 > 1484092800000 %}
        {{vars.input.records[0]}} 
            {% elif 5==6 %}
        {{vars.input.records[0]}} 
{% endif %}

An if condition can cause a playbook to fail if the jinja that you have added returns an empty string, which is not compatible with the field datatype defined in the database. For example, if you have added the following jinja to a {% if vars.currentValue == "Aftermath" %}{{@Current_Date}}{% endif %} field, then the playbook will fail if the jinja returns an empty string.

To ensure that your playbook does not fail due to the issue of jinja returning an empty string, add the following jinja in the field: {% if vars.currentValue == “Aftermath” %}{{Current_Date}}{% else %} None {% endif %}.

For Loop along with the If condition

At times, it can be useful to use a combination of conditional logic and looping during a check of particular conditions across a list of values. In this case, the for and the if loop is useful in the Dynamic Variable language.

{% for i in vars.var_list  %}
        { %  if i not in vars.var_response % } 
            {{vars.var_response.append(i)}}
        {% endif %}
{% endfor %}

The following example uses the for and the if loop to check if a particular team is tagged to a record. In the following example, an evaluation of assignment is run across a specific dictionary representing all of the associated teams within a particular record. These teams have already been assigned to a particular variable from the parent entity.

{% for item in vars.teamName %}  
	{% if item == '/api/3/teams/97fd5a3f-4eaf-4cc1-a132-1c9274bd8428' %} 
	Yes  {% endif %} 
{% endfor %}

If Else condition

If conditions within the Dynamic Variable templating engine can be very useful to avoid unnecessary decision steps. These conditions allow you to specify values defined within specific conditions such that copying, or value branches are not needed.

The following example uses the if else condition to create mapping between Alert Severity and Incident Category.

Here is an example of a particular case in which the value of an IRI is defined based upon the specific conditions evaluated during the execution. Bear in mind these IRI values must be known in this case.

{% if vars.alertSeverity == "/api/3/picklists/ad5eacc1-7c05-3c4e-bba7-eb356c8547c9" %}

	/api/3/picklists/58d0753f-f7e4-403b-953c-b0f521eab759

{% elif vars.alertSeverity == "/api/3/picklists/ddbc7842-dde0-392a-ae94-a1e7a3c7c2f7" %}

	/api/3/picklists/40187287-89fc-4e9c-b717-e9443d57eedb

{% elif vars.alertSeverity == "/api/3/picklists/6c7a8653-a2d5-3611-bc86-8ca307a02e88" %}

	/api/3/picklists/7efa2220-39bb-44e4-961f-ac368776e3b0

{% endif %}

Time Operations

To get timestamp:

{{ arrow.get('2013-05-30 12:30:45', 'YYYY-MM-DD HH:mm:ss') }}

To convert current time into epoch and multiply by 10000:

{{arrow.utcnow().timestamp*1000 |int| abs}}

To convert date to epoch time:

{{ arrow.Arrow(2017, 3, 30).timestamp}}

Convert timezone from UTC to any with formatting

Use the Arrow library to convert dates and times from one-time zone/format to another.

The general format is as follows.

{{ arrow.get( % VARIABLE % ).to('% TIME ZONE ACRONYM %').format('% FORMAT STRING%') }}

The following is an example that is converting the Date of Compromise field into a readable Eastern Standard Time format.

{{arrow.get(vars.input.records[0].dateOfCompromise).to('EST').format('YYYY-MM-DD HH:mm:ss ZZ')}}

Convert timezone from any to UTC and move it up

Using the Arrow library, the general format is as follows.

{{ arrow.get( % VARIABLE %).replace( % TIME VALUE % = % OPERATOR + VALUE %) }}

An example where the Alert Time value is replaced with a four-hour increase.

{{ arrow.get(vars.alertTime).shift(hours=+4) }}

Convert to epoch time to insert into a Record

In this example, a particular UTC time is converted into epoch time in order to format it for API insertion. The API will accept times in epoch as the default.

{{ arrow.get(vars.utcTime).timestamp }}

String Operations

To find the length of list or string:

{{vars.emails | length }}

To replace a string:

{{ vars.var_keys.replace("dict_keys(","" ) | replace( ")", "" )}}

Strip the first X characters of a string

Starting with a string variable, you can pull a portion of the string based on counting the characters.

The general format for this Jinja expressions is as follows where # is the number of characters.

{{ % VARIABLE %[:#] }}

An example here pulls the first 17 characters of the string timeRange.

timeRange = 2016-09-02 13:45:00 EDT - 2016-09-02 14:00:00 ET
alertTime = {{vars.timeRange[:17]}} 

print(alertTime) 

2016-09-02 13:45:00

Remove Strip tags

A particularly useful filter within Dynamic Variables is the striptags() function. This allows you to remove the HTML tags on a particular value, which may be present in rich text or other HTML formats.

The function preserves the data contained within the tags and removes any tagged information contained around the actual values.

{{ vars.input.records[0].name.striptags() }}

Code in block

{% block body %}
        {% for key, value in vars.loop_resource.items() %} 
            {{ key }}: {{ value }} 
        {% endfor %} 
{% endblock %}

Set variable based on condition

{% for i in vars.result['hydra:member'] %}
        {% set id = i['@id'] %} 
        {{ vars.inc_fdata.append(id) }}        
{%endfor%}

Second example:

{% for i in vars.result['hydra:member'] %}
        {% set id = i['@id'] %}
        {% set createDate = i.createDate | string %}
        {% set list_item = [id,createDate] %}
            {{ vars.inc_fdata.append(list_item) }}
{%endfor%}

YAQL Filters

FortiSOAR release 7.2.0 adds support for YAQL as an additional filter language (in addition to JINJA). YAQL (Yet Another Query Language) is an embeddable and extensible query language, which allows users to perform complex queries against arbitrary objects. YAQL contains a comprehensive standard library of frequently used querying functions and can be extended even further with user-specified functions. YAQL is written in python and is distributed via PyPI. For more information on YAQL, see the Getting started with YAQL document.

Usage

In a YAQL query, the $ symbol is used to refer to "this" object. Therefore, $.lastname in a YAQL query pulls the last name of each object in the list passed to the filter.

The $ symbol can also have different meanings within the same query. For example, in $.pets.flatten().where($.type='cat'), the first instance of $ refers to each of the 'User' objects, while the second instance of $ refers to each of the 'Pets' objects. In this example, where() and flatten() are examples of functions available in YAQL. For a list of available YAQL functions see the Standard YAQL Library document.

Usage Examples

  • {{ {"var1":1,"var2":"a"} | yaql('$.var1') }}
    returns# 1
  • {{ "test" | yaql('$.toUpper()') }}
    returns# TEST
  • Example to filter down to non-false data:
    Sample data:
    {
       "data":{
          "av_cate":"Riskware/NetCat",
          "wf_cate":"",
          "ioc_cate":"",
          "ioc_tags":[
             
          ],
          "confidence":"High",
          "spam_cates":[
             
          ],
          "reference_url":"https://ioc.fortiguard.com/search?query=E8FBEC25DB4F9D95B5E8F41CCA51A4B32BE8674A4DEA7A45B6F7AEB22DBC38DB&filter=indicator"
       },
       "success":true
    }
    {{ data | yaql('dict($.items().where(bool($[1])))') }}
    returns#
    {
        "av_cate": "Riskware/NetCat",
        "confidence": "High",
        "reference_url": "https://ioc.fortiguard.com/search?query=E8FBEC25DB4F9D95B5E8F41CCA51A4B32BE8674A4DEA7A45B6F7AEB22DBC38DB&filter=indicator"
    }
  • If you have the following example dataset:
[{

   "firstname": "Billy",

   "lastname": "gu",

   "email": "billsgu@example.com",

   "pets": []

}, {

   "firstname": "Trevor",

   "lastname": "Palmer",

   "email": "palmer928@example.com",

   "pets": [{

      "name": "Butter",

      "type": "dog"

   }]

}, {

   "firstname": "Jimmy",

   "lastname": "Bauer",

   "email": "jbles45@example2.com",

   "pets": [{

      "name": "Biscuit",

      "type": "cat"

   }, {

      "name": "Pepper",

      "type": "cat"

   }]

}]

You can filter this dataset using YAQL as follows:
Get Users where firstname starts with B: vars.users | yaql("$.where($.firstname.startsWith('B'))")
Get Users where email contains @example.com: vars.users | yaql("$.where('@example.com' in $.email)")
Get Users who have pets with type cat: vars.users | yaql("$.where($.pets.where($.type='cat').any())")

Jinja Extensions

There are some official Jinja Extensions that can be used in playbooks and help in enriching expressions:
Jinja Extensions
Note: Support to 'break' and 'continue' ansible loops has been added.

Some examples of using expression statements follow:

Appending a list in a loop example:

{% for i in range(100000)%}
{{vars.printThis}}
{% do vars.res.append(vars.printThis) %}
{%if i==vars.input.params.breakAfterNumberOfLoops %}
{%break%}
{%endif%}
{%endfor%} 

Continue Statement example:

{% for i in range(8)%}
 {% if i == vars.input.params.skipForThisValue%}
  {%continue%}
{%endif%}
 {{i}}
{%endfor%}

Break Statement example:

{% for i in range(100000)%}
{{vars.printThis}}

{%if i==vars.input.params.breakAfterNumberOfLoops %}

{%break%}

{%endif%}

Custom Functions and Filters

FortiSOAR supports following custom functions/filters:

  • Get_current_date: Returns the current date for the file.
  • Get_current_datetime: Returns the current date and time for the file.
  • currentdateminus: Returns a timestamp value of the current date minus the specified days from the current date.
    For example, {{ currentDateMinus(10) }} returns a timestamp after deducting 10 days from the current date.
  • uuid: Returns the UUID of the file {{ uuid() }}.
  • arrow: Returns a python arrow library.
  • toJSON: Converts a JSON to a string. Useful for storing a JSON in a FortiSOAR textarea field, for example, Source Data, so that JSON renders correctly and the content can be presented nicely in the UI.
  • html2text: Converts an HTML string to a text string.
    {{ html_string | html2text}}
    For example, {{'<br>this is html text </br>' | html2text}} .
    Output will be - this is html text.
  • json2html: Converts JSON data into HTML. The FortiSOAR template is used for HTML and styling of the output.
    {{ jsondata | json2html(row_fields)}}
    row_fields= ['pid', 'sid']. You can optionally specify the row_fields attribute. If you do not specify the row_fields, by default, this filter takes all keys as row fields.
    An example without row fields specified: {{ [{"pid": 123, "sid": "123", "path": "abc.txt"}] | json2html}}.
    An example with row fields specified: {{ [{"pid": 123, "sid": "123", "path": "abc.txt"}] | json2htmll([‘pid’, ‘sid’])}}.
    The HTML output of the above example will be:
    <table class="cs-data-table"> <tr> <th>pid</th> <th>sid</th> </tr> <tr> <td>123</td> <td>123</td> </tr> </table><button style="display:none" class="cs-datatable-btn btn-link cs-datatable-showmore-btn" type="button" onClick="event.target.previousElementSibling.className += ' cs-data-table-show-more'; event.target.nextElementSibling.style.display = 'block'; event.target.style.display = 'none';">Show more</button><button class="cs-datatable-btn btn-link cs-datatable-showless-btn" type="button" onClick="event.target.previousElementSibling.previousElementSibling.className = 'cs-data-table'; event.target.previousElementSibling.style.display = 'block'; event.target.style.display = 'none';">Show less</button>
  • resolveIRI : Resolves the given IRI.
    For example, {{ “/api/3/alerts/<alert-id>” | resolveIRI}}
  • count_occurrence: Retrieves the number of times each element appears in the list.
    For example, {{ [‘apple','red','apple','red','red','pear’] | count_occurrence }}
    The output of this example is: {'red': 3, 'apple': 2, 'pear': 1}
  • urlencode: Encodes the given URL.
    For example, {{"/api/3/alerts/?name=test" | urlencode}}
    The output of this example is: %2Fapi%2F3%2Falerts%2F%3Fname%3Dtest
  • urldecode: Decodes the given (encoded) URL.
    For example, {{"%2Fapi%2F3%2Falerts%2F%3Fname%3Dtest" | urldecode}}
    The output of this example is: /api/3/alerts/?name=test
  • loadRelationships(moduleName, selectFields = []): Used to fetch details of a related (correlation) record. For example, {{ vars.incidentIRI | loadRelationships('indicators') }}
    To fetch complete details of the correlation record, use {{ #recordIRI# | loadRelationships('#CorrelationModuleFieldName#') }}
    To fetch specific fields of the correlation record, use {{ #recordIRI# | loadRelationships('#CorrelationModuleFieldName#',['#field1#','#field2#']) }}
  • picklist: Loads the specified picklist item object. For example, {{"PicklistName" | picklist("ItemValue") }}
    The output of this example is an object including the @id, color, itemValue, listName, and orderIndex of the picklist item. You can extract just a particular key from the object by specifying a second argument to the filter:
    {{"PicklistName" | picklist("ItemValue", "@id") }}. This will generate /api/3/picklists/<uuid>