Fortinet black logo

Playbooks Guide

Jinja Filters and Functions

Copy Link
Copy Doc ID 923e85b9-1d0e-11ec-8c53-00505692583a: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.

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.

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%}

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.

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.

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%}

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>