Package: jinjafx
JinjaFx - Jinja2 Templating ToolJinjaFx is a Templating Tool that usesJinja2as the templating engine. It is written in Python and is extremely lightweight and hopefully simple - it only requires a couple of Python modules that aren't in the base install -jinja2for obvious reasons andcryptographyfor Ansible Vault.JinjaFx differs from the Ansible "template" module as it allows data to be specified in a dynamic "csv" format as well as multiple yaml files. Providing data in "csv" format is easier if the data originates from a spreadsheet or is already in a tabular format. In networking it is common to find a list of physical connections within a patching schedule, which has each connection on a different row - this format isn't easily transposed into yaml, hence the need to be able to use "csv" as a data format in these scenarios.Installationpython3 -m pip install --upgrade --user jinjafxJinjaFx Usagejinjafx -t <template.j2> [-d [data.csv]] [-g <vars.yml>]
         -dt <dt.yml> [-ds <dataset>] [-g <vars.yml>]
         -encrypt/-decrypt [file1] [file2] [...]

    -t <template.j2>          - specify a Jinja2 template
    -d [data.csv]             - specify row/column based data (comma or tab separated) - omit for <stdin>
    -dt <dt.yml>              - specify a JinjaFx DataTemplate (combines template, data and vars)
    -ds <dataset>             - specify a regex to match a DataSet within a JinjaFx DataTemplate
    -g <vars.yml> [-g ...]    - specify global variables in yaml (supports Ansible Vault)
    -var <x=value> [-var ...] - specify global variables on the command line (overrides existing)
    -ed <exts dir> [-ed ...]  - specify where to look for extensions (default is "." and "~/.jinjafx")
    -o <output file>          - specify the output file (supports Jinja2 variables) (default is stdout)
    -od <output dir>          - set output dir for output files with a relative path (default is ".")
    -encrypt [file] [...]     - encrypt files or stdin (if file omitted) using Ansible Vault
    -decrypt [file] [...]     - decrypt files or stdin (if file omitted) using Ansible Vault
    -m                        - merge duplicate global variables (dicts and lists) instead of replacing
    -q                        - quiet mode - don't output version or usage information

 Environment Variables:
   ANSIBLE_VAULT_PASSWORD       - specify an Ansible Vault password
   ANSIBLE_VAULT_PASSWORD_FILE  - specify an Ansible Vault password fileJinjaFx allows you to specify a text based "csv" file using the-dargument - it is composed of a header row and a series of data rows. It supports both comma and tab separated data and will automagically detect what you are using by analysing the header row - it counts the number of occurrences to determine what one is most prevalent. If it detects a "#" at the beginning of a row then that row is ignored as it is treated as a comment.A, B, C    <- HEADER ROW
1, 2, 3    <- DATA ROW 1
4, 5, 6    <- DATA ROW 2
7, 8, 9    <- DATA ROW 3The case-sensitive header row (seejinjafx_adjust_headersinJinjaFx Variables) determines the Jinja2 variables that you will use in your template (which means they can only containA-Z,a-z,0-9or_in their value) and the data rows determine the value of that variable for a given row/template combination. Each data row within your data will be passed to the Jinja2 templating engine to construct an output. In addition or instead of the "csv" data, you also have the option to specify multiple yaml files (using the-gargument) to include additional variables that would be global to all rows - multiple-garguments can be specified to combine variables from multiple files. If you define the same key in different files then the last file specified will overwrite the key value, unless you specify-mwhich tells JinjaFx to merge keys, although this only works for keys of the same type that are mergable (i.e. dicts and lists). If you do omit the data then the template will still be executed, but with a single empty row of data.RegEx Style Character Classes and GroupsApart from normal data you can also specify regex based static character classes or static groups as values within the data rows using(value1|value2|value3)or[a-f]. These will be expanded using thejinjafx.expand()function to multiple rows, for example:DEVICE, TYPE
us(ma|n[yh]|tx)-pe-1[ab], peThe above would be expanded to the following, which JinjaFx would then loop through like normal rows (be careful as you can easily create huge data sets with no boundaries) - if you do wish to use literal brackets then they would need to be escaped (e.g. "\[" or "\(") - JinjaFx tries to be intelligent with regards to curly brackets - if it thinks you meant to escape them then it will automatically escape them (i.e. if it can't detect a "|" between the brackets and you aren't referencing them in capture groups).DEVICE, TYPE
usma-pe-1a, pe
usma-pe-1b, pe
ustx-pe-1a, pe
ustx-pe-1b, pe
usny-pe-1a, pe
usny-pe-1b, pe
usnh-pe-1a, pe
usnh-pe-1b, peRegEx Style Capture GroupsIt also supports the ability to use regex style capture groups in combination with static groups, which allows the following syntax where we have used "\1" to reference the first capture group that appears within the row:DEVICE, INTERFACE, HOST
spine-0[1-3], et-0/0/([1-4]), leaf-0\1The above would then be expanded to the following, where the leaf number has been populated based on the interface number:DEVICE, INTERFACE, HOST
spine-01, et-0/0/1, leaf-01
spine-01, et-0/0/2, leaf-02
spine-01, et-0/0/3, leaf-03
spine-01, et-0/0/4, leaf-04
spine-02, et-0/0/1, leaf-01
spine-02, et-0/0/2, leaf-02
spine-02, et-0/0/3, leaf-03
spine-02, et-0/0/4, leaf-04
spine-03, et-0/0/1, leaf-01
spine-03, et-0/0/2, leaf-02
spine-03, et-0/0/3, leaf-03
spine-03, et-0/0/4, leaf-04Expansion CountersWe also support the ability to use active and passive counters during data expansion with the{ start[-end]:step[:repeat] }syntax (step must be positive and repeat specifies the number of times the same number is repeated) - counters are row specific (i.e. they don't persist between different rows). Active counters are easier to explain as they are used to expand rows based on a start and end number (they are bounded) as per the example below. In this instance as we have specified a start (0) and an end (9) it will expand the row to 10 rows using the values from 0 to 9 (i.e. 'et-0/0/0' to 'et-0/0/9').INTERFACE
et-0/0/{0-9:1}Passive counters (i.e. counters where you don't specify an end) don't actually create any additional rows or determine the range of the expansion (they are unbounded). They are used in combination with static character classes, static groups or active counters to increment as the data is expanded into multiple rows. If we take our previous example and modify it to allocate a HOST field to each interface, which uses a number starting at 33, then the following (see 'Pad Operator' for explanation of%3):INTERFACE, HOST
et-0/0/{0-9:1}, r740-{33:1}%3Would be expanded to the following (we haven't actually specified 42 as the end number, but it will increment based on the number of rows it is being expanded into):INTERFACE, HOST
et-0/0/0, r740-033
et-0/0/1, r740-034
et-0/0/2, r740-035
et-0/0/3, r740-036
et-0/0/4, r740-037
et-0/0/5, r740-038
et-0/0/6, r740-039
et-0/0/7, r740-040
et-0/0/8, r740-041
et-0/0/9, r740-042Another type of passive counter is a looping counter, which is used to loop through a set of predefined numerical values via the syntax{n1|n2|n3|..[:repeat]}. For example, if we were to use{1|3|5:1}then it would output 1, 1, 3, 3, 5, 5, 1, 1, 3, 3, etc - it will only keep going based on the number of rows it is being expanded into.Field ValuesBy default all field values are treated as strings which means you need to use theintfilter (e.g.{{ NUMBER|int }}) if you wish to perform mathematical functions on them (e.g.{{ NUMBER|int + 1 }}). If you have a field where all the values are numbers and you wish them to be treated as numerical values without having to use theintfilter, then you can suffix:intonto the field name (if it detects a non-numerical value in the data then an error will occur), e.g:NUMBER:int, NAME
1, one
10, ten
2, two
20, twentyLike:intyou can also use:floatto treat all values as a float.Pad OperatorJinjaFx supports the ability to add or remove leading zeros from numbers using the%pad operator. The pad operator is used by suffixing a%to a number followed by the number of zeros to pad the number to (e.g.100%4would result in0100and003%0would result in3). Using the following example, we might want to have no padding on theINTERFACEfield but leave the leading zero on theHOSTfield, e.g:DEVICE, INTERFACE, HOST
spine-01, et-0/0/\1%0, leaf-(0[1-4]|1[0-3])This would then be expanded to the following:DEVICE, INTERFACE, HOST
spine-01, et-0/0/1, leaf-01
spine-01, et-0/0/2, leaf-02
spine-01, et-0/0/3, leaf-03
spine-01, et-0/0/4, leaf-04
spine-01, et-0/0/10, leaf-10
spine-01, et-0/0/11, leaf-11
spine-01, et-0/0/12, leaf-12
spine-01, et-0/0/13, leaf-13If you are using capture groups then you need to ensure the%character is outside the group (as per the above example) else it will be copied as well. In the event you want to use a%character then you can escape it using\%.The-oargument is used to specify the output file, as by default the output is sent tostdout. This can be a static file, where all the row outputs will be appended, or you can use Jinja2 syntax (e.g.-o "{{ DEVICE }}.txt") to specify a different output file per row. If you specify a directory path then all required directories will be automatically created - any existing files will be overwritten.JinjaFx TemplatesJinjaFx templates are Jinja2 templates with one exception - they support a JinjaFx specific syntax that allows you to specify a different output file (or_stdout_for stdout) within a Jinja2 template to override the value of-o(or output name if being used with the JinjaFx Server):<output "output file">
...
</output>The above syntax is transparent to Jinja2 and will be ignored by Jinja2, but JinjaFx will parse it and use a different output file for the contents of that specific block. Full Jinja2 syntax is supported within the block as well as supporting nested blocks.This data could then be used in a template as follows, which would output a different text file per "DEVICE":<output "{{ DEVICE|lower }}.txt">
edit interfaces {{ INTERFACE }}
set description "## Link to {{ HOST }} ##"
</output>You also have the option of specifying a numerical output block index to order them, e.g:<output "{{ DEVICE|lower }}.txt">[1]
first
</output>
<output "{{ DEVICE|lower }}.txt">[0]
second
</output>
<output "{{ DEVICE|lower }}.txt">[-1]
third
</output>The outputs are then sorted based on the index (the default index if omitted is 0), which results in the following output:third
second
firstBy default the following Jinja2 templating options are enabled, but they can be overridden as required in the template as per standard Jinja2 syntax:trim_blocks = True
lstrip_blocks = True
keep_trailing_newline = TrueAnsible FiltersJinja2 is commonly used with Ansible which has a wide variety ofcustom filtersthat can be used in your Jinja2 templates. However, these filters aren't included in Jinja2 as they are part of Ansible. JinjaFx contains some of them that have been ported from Ansible - please raise an Issue if there is one that is missing that people commonly use and I will get it added.This contains the following Ansible filters:to_yamlto_nice_yamlfrom_yamlto_jsonto_nice_jsonfrom_jsonboolto_datetimestrftimeb64encodeb64decodehashregex_escaperegex_replaceregex_searchregex_findalldict2itemsitems2dictrandomshuffleternaryextractflattenproductpermutationscombinationsuniqueintersectdifferencesymmetric_differenceunionzipzip_longestlogpowrooturlsplitvlan_expandervlan_parserThis also contains the following "ipaddr" Ansible filters:cidr_mergeipaddripmathipwrapip4_hexipv4ipv6ipsubnetnext_nth_usablenetwork_in_networknetwork_in_usablereduce_on_networknthhostprevious_nth_usableslaachwaddrmacaddrAnsible TestsIn additional to Ansible Filters, Ansible also introducesteststhat can be performed with various filters (e.g.selectandselect_attr) - the following Ansible tests have been included in JinjaFx:regexmatchsearchcontainsanyallAnsible LookupsJinjaFx supports the following Ansible lookups:lookup("vars", variable: String, default: Optional[String])-> Stringorvars(variable: String, default: Optional[String])-> StringThevars lookupbuiltin is used to dynamically access variables based on the content of other variables, e.g:{{ lookup("vars", "my" ~ "variable") }}lookup("varnames", regex: String, ...)-> List[String]orvarnames(regex: String, ...)-> List[String]Thevarnames lookupbuiltin is used to list variables that are accessible within the scope of the Jinja2 template, e.g:{{ lookup("varnames", ".+") }} {# return all variables #}

{{ lookup("varnames", "^a", "^b") }} {# return all variables which begin with "a" or "b" #}JinjaFx VariablesThe following variables, if defined withinvars.ymlcontrol how JinjaFx works:jinjafx_adjust_headersThere might be some situations where you can't control the format of the header fields that are provided indata.csv- it might come from a spreadsheet where someone hasn't been consistent with the header row and has used uppercase in some situations and lowercase in others or they might have used non-standard characters. The header fields are used by Jinja2 as case-sensitive variables and can't contain spaces or punctuation characters - they can only contain alphanumerical characters and the underscore. To help in these situations, the variablejinjafx_adjust_headerscan be set invars.ymlto either "yes", "no" (the default), "upper" or "lower", which will remove any non-standard characters and change the case depending on the value (i.e. "Assigned / Unassigned" would becomeASSIGNEDUNASSIGNEDif the value was "upper" andAssignedUnassignedif the value was "yes").---jinjafx_adjust_headers:"yes"|"no" | "upper" | "lower"jinjafx_render_varsJinjaFx by default will attempt to render yourvars.ymlusing Jinja2, which means the following syntax is valid:---fullname:"FirstnameSurname"firstname:"{{fullname.split()[0]}}"surname:"{{fullname.split()[1]}}"However, the rendering will happen aftervars.ymlhas been processed by YAML, which means it must be valid YAML to start with - the following isn't valid YAML:---{%if x != y %}x:"{{y}}"{%endif %}If you wish to use literal braces within your YAML (i.e.{{) then you need to ensure they are escaped, or you can disable this functionality by specifying the following variable invars.yml:---jinjafx_render_vars:"no"jinjafx_filterandjinjafx_sortJinjaFx supports the ability to filter as well as sort the data withindata.csvbefore it is passed to the templating engine. From a filtering perspective, while you could include and exclude certain rows within yourtemplate.j2with a conditionalifstatement, it won't allow you to usejinjafx.first()andjinjafx.last()on the reduced data set. This is where thejinjafx_filterkey which can be specified invars.ymlcomes into play - it lets you specify using regular expressions what field values you wish to include in your data, e.g:---jinjafx_filter:"HOST":"^r740""INTERFACE":"^et"The above will filterdata.csvand only include rows where the "HOST" field value starts with "r740" and where the "INTERFACE" field value starts with "et" (by default it performs a case sensitive match, but "(?i)" can be specified at the beginning of the match string to ignore case).While data is normally processed in the order in which it is provided, it can be sorted through the use of thejinjafx_sortkey when specified withinvars.yml. It takes a case-sensitive list of the fields you wish to sort by, which will then sort the data before it is processed, e.g to sort by "HOST" followed by "INTERFACE" you would specify the following:---jinjafx_sort:-"HOST"-"INTERFACE"Sorting is in ascending order as standard, but you can prefix the sort key with "+" (for ascending - the default) or "-" (for descending), e.g: "-INTERFACE" would sort the "INTERFACE" field in descending order. By default all fields are treated as strings - this means "2" will get placed after "10" but before "20" if sorted - if you have numbers and wish them to be sorted numerically then you need to ensure you designate the field as numerical using:inton the field name.While sorting is performed in either ascending or descending order, you can also specify a custom sort order using the following syntax:---jinjafx_sort:-"HOST":{"r740-036":-2,"r740-035":-1,"r740-039":1}The above syntax allows you to specify an order key for individual field values - by default all fields have an order key of 0, which means the field name is used as the sort key. If you specify an order key < 0 then the field value will appear before the rest and if you specify an order key > 0 then the values will appear at the end. If multiple field values have the same order key then they are sorted based on actual field value. In the above example, "r740-036" will appear first, "r740-035" will appear second and everything else afterwards, with "r740-039" appearing last.JinjaFx InputThere might be some situations where you want inputs to be provided during the generation of the template which are not known beforehand. JinjaFx supports the ability to prompt the user for input using thejinjafx_inputvariable which can be specified invars.yml. The following example demonstrates how we can prompt the user for two inputs ("Name" and "Age") before the template is generated:---jinjafx_input:prompt:name:"Name"age:"Age"These inputs can then be referenced in your template using{{ jinjafx_input.name }}or{{ jinjafx_input['age'] }}- the variable name is the field name and the prompt text is the value. However, there might be some situations where you want a certain pattern to be followed or where an input is mandatory, and this is where the advanced syntax comes into play (you can mix and match syntax for different fields):---jinjafx_input:prompt:name:text:"Name"required:Trueage:text:"Age"required:Truepattern:"[1-9]+[0-9]*"Under the field thetextkey is always mandatory, but the following optional keys are also valid:required- can be True or False (default is False)pattern- a regular expression that the input value must matchtype- if set to "password" then echo is turned off - used for inputting sensitive valuesKeyless YAMLThere might be some scenarios where you want to define YAML without a root key, e.g:-name:"Alice"age:27-name:"Bob"age:37-name:"Eve"age:29While this is valid in YAML, it isn't valid to have a list when Jinja2 expects a dict. In this scenario, JinjaFx will create a parent key of_to allow you to iterate over it within a Jinja2 template:{% for k in _ %}
  {{ k.name }} is {{ k.age }}
{% endfor %}JinjaFx DataTemplatesJinjaFx also supports the ability to combine the data, template and vars into a single YAML file (called a DataTemplate), which you can pass to JinjaFx using-dt. This is the same format used by the JinjaFx Server when you click on 'Export DataTemplate'. It uses headers with block indentation to separate out the different components - you must ensure the indentation is maintained on all lines as this is how YAML knows when one section ends and another starts.---dt:data:|2... DATA.CSV ...template:|2... TEMPLATE.J2 ...vars:|2... VARS.YML ...You also have the option to specify different DataSets within a DataTemplate - this is where the template is common, but the data and vars can be specified multiple times for different scenarios (e.g. "Test" and "Live"). When you use the DataSet format you will also need to specify which DataSet to generate the outputs for with-ds, which takes a case insensitive regular expression to match against.---dt:global:|2... GLOBAL.YML ...datasets:"Test":data:|2... DATA.CSV ...vars:|2... VARS.YML ..."Live":data:|2... DATA.CSV ...vars:|2... VARS.YML ...template:|2... TEMPLATE.J2 ...Jinja2 ExtensionsJinja2 supports the ability to provide extended functionality throughextensions. To enable specific Jinja2 extensions in JinjaFx you can use thejinja2_extensionsglobal variable, which you can set within one of yourvars.ymlfiles (it expects a list):---jinja2_extensions:-'jinja2.ext.i18n'-'jinja2.ext.loopcontrols'-'jinja2.ext.do'-'jinja2.ext.debug'JinjaFx will then attempt to load and enable the extensions that will then be used when processing your Jinja2 templates. You also have the ability to check whether an extensions is loaded within your template by queryingjinja2_extensionsdirectly.Unfortunately writing Jinja2 Extensions isn't that obvious - well, I didn't find it that obvious as it took me quite a while to work out how to write a custom filter. Let's assume we want to write a custom filter calledaddthat simply adds a value to a number, for example:{{ 10|add(1) }}We start off by creating our Extension in a file calledjinjafx_extensions.py(the name of the file is arbitrary) - this file basically defines a new class which extends Extension and a private__addmethod that is mapped to a new filter calledadd:fromjinja2.extimportExtensionclassAddExtension(Extension):def__init__(self,environment):Extension.__init__(self,environment)environment.filters['add']=self.__adddef__add(self,number,value):returnnumber+valueWe would then use the new Extension by adding the following YAML to ourvars.ymlfile - based on the name it will automatically look injinjafx_extensions.pyfor theAddExtensionclass and will then load and enable theaddfilter.---jinja2_extensions:-'jinjafx_extensions.AddExtension'JinjaFx Built-InsTemplates should be written using Jinja2 template syntax to make them compatible with Ansible and other tools which use Jinja2. However, there are a few JinjaFx specific extensions that have been added to make JinjaFx much more powerful when dealing with rows of data, as well as providing some much needed functionality which isn't currently present in Jinja2 (e.g. being able to store persistent variables across templates). These are used within a template like any other variable or function (e.g.{{ jinjafx.version }}).jinjafx.version-> StringThis variable will contain the current version of JinjaFx (e.g. "1.0.0").jinjafx.jinja2_version-> StringThis variable will return the current version of the Jinja2 templating engine (e.g. "2.10.3").jinjafx.row-> IntegerThis variable will contain the current row number being processed.jinjafx.rows-> IntegerThis variable will contain the total number of rows within the data.jinjafx.data(row: Integer, col: Optional[Integer | String])-> List[String] | StringThis function is used to access all the row and column data that JinjaFx is currently traversing through. The first row (0) will contain the header row with subsequent rows containing the row data - it is accessed usingjinjafx.data(row, col). If you wish to access the columns via their case-sensitive name then you can also usejinjafx.data(row, 'FIELD'). Therowargument is mandatory, but if you omit thecolargument then it will return the whole row as a list.jinjafx.expand(value: String)-> List[String]This function is used to expand a string that contains static character classes (i.e.[0-9]), static groups (i.e.(a|b)) or active counters (i.e.{ start-end:increment }) into a list of all the different permutations. You are permitted to use as many classes, groups or counters within the same string - if it doesn't detect any classes, groups or counters within the string then the "string" will be returned as the only list element. Character classes support "A-Z", "a-z" and "0-9" characters, whereas static groups allow any string of characters (including static character classes). If you wish to include "[", "]", "(", ")", "{" or "}" literals within the string then they will need to be escaped.jinjafx.counter(key: Optional[String], increment: Optional[Integer]=1, start: Optional[Integer]=1)-> Integer | StringThis function is used to provide a persistent counter within a row or between rows. If you specify a case insensitivekeythen it is a global counter that will persist between rows, but if you don't or you includejinjafx.rowwithin thekey, then the counter only persists within the template of the current row.This function also supports a hierarchical counter which can be used for numbering of headings, e.g:1.
1.1.
1.1.1.
1.1.2.
2.This is achieved by using a special syntax for thekeyvalue:{{ jinjafx.counter('A.') }}
{{ jinjafx.counter('A.A.') }}
{{ jinjafx.counter('A.A.A.') }}
{{ jinjafx.counter('A.A.A.') }}
{{ jinjafx.counter('A.') }}When this function detects akeywhich consists of single letters preceded by a dot, then it will act as a hierarchical counter and will produce the above output. Using different letters will reset the counter value back to 1 (or a different number if you have specifiedstart). You aren't able to skip levels (i.e. 'A.A.A.' isn't valid if 'A.A.' and 'A.' haven't been used previously, etc).jinjafx.now(format: Optional[String], tz: Optional[String]="UTC")-> StringThis function is used to output the current date and time. If you don't specify the optionalformatargument then it will output using the default ISO 8601 format - if you wish to specify a custom format then it needs to be as perstrftime. Thetzargument is also optional and allows you to specify a timezone as opposed toUTCwhich is used by default.jinjafx.exception(message: String)-> NoReturnThis function is used to stop processing and raise an exception with a meaningful message - for example, if an expected header field doesn't exist you could use it as follows:{% if name is not defined %}
  {% set null = jinjafx.exception("header field 'name' is not defined in data") %}
{% endif %}jinjafx.warning(message: String, repeat: Optional[Boolean]=False)-> NoReturnNearly identical to the previous function, except this won't stop processing of the template but will raise a warning message when the output is generated - this can be specified multiple times for multiple warnings, although repeated warnings are suppressed unless you set the second parameter to True.jinjafx.first(field: Optional[List[String]], filter_fields: Optional[Dict[field: String, regex: String]])-> BooleanThis function is used to determine whether this is the first row where you have seen this particular field value or not - if you don't specify any fields then it will returnTruefor the first row andFalsefor the rest.A, B, C    <- HEADER ROW
1, 2, 3    <- DATA ROW 1
2, 2, 3    <- DATA ROW 2
2, 3, 3    <- DATA ROW 3If we take the above example, thenjinjafx.first(['A'])would returnTruefor rows 1 and 2, butFalsefor row 3 as these rows are the first time we have seen this specific value for "A". We also have the option of specifying multiple fields, sojinjafx.first(['A', 'B'])would returnTruefor all rows as the combination of "A" and "B" are different in all rows.There is also an optionalfilter_fieldsargument that allows you to filter the data using a regular expression to match certain rows before performing the check. For example,jinjafx.first(['A'], { 'B': '3' })would returnTruefor row 3 only as it is the only row which matches the filter, where 'B' contains a '3'.There might be some scenarios where you don't want to match the entire value, but part of it - to support this, there is an optional split operator using the following syntaxFIELD:<CHAR><POS>, e.g:DEVICE
LEAF-01
LEAF-02
LEAF-03By using the split operator we can specifyjinjafx.first(['DEVICE:-0']), which uses-as the split character and0as the numeric position - this will use the value 'LEAF' when deciding if it is the first value or not. Like Python syntax we also support negative positions, soDEVICE:--1would represent the last value.jinjafx.last(field: Optional[List[String]], filter_fields: Optional[Dict[field: String, regex: String]])-> BooleanThis function is used to determine whether this is the last row where you have seen this particular field value or not - if you don't specify any fields then it will return 'True' for the last row and 'False' for the rest. Similar tojinjafx.first()it supports the same syntax.jinjafx.fields(field: String, filter_fields: Optional[Dict[field: String, regex: String]])-> List[String]This function is used to return a unique list of non-empty field values for a specific header field. It also allows the ability to limit what values are included in the list by specifying an optionalfilter_fieldsargument that allows you to filter the data using a regular expression to match certain rows.jinjafx.setg(key: String, value: String | Integer | Boolean])-> NoReturnThis function is used to set a global variable that will persist throughout the processing of all rows.jinjafx.getg(key: String, default: Optional[String | Integer | Boolean]) -> String | Integer | BooleanThis function is used to get a global variable that has been set withjinjafx.setg()- optionally you can specify a default value that is returned if thekeydoesn't exist.JinjaFx FiltersJinjaFx comes with a custom Jinja2 Extension (extensions/ext_jinjafx.py) that is enabled by default which provides the following custom filters:cisco_snmpv3_key(engineid: String, algorithm: Optional[String]="sha1")-> StringThis filter is used to generate localised SNMPv3 authentication and privacy keys (section A.2 ofRFC3414) for use by Cisco devices, e.g:snmp-server engineID local {{ engineID }}
snmp-server user {{ snmpUser }} {{ snmpGroup }} v3 encrypted auth sha {{ authPassword|cisco_snmpv3_key(engineID) }} priv aes 128 {{ privPassword|cisco_snmpv3_key(engineID) }}junos_snmpv3_key(engineid: String, algorithm: Optional[String]="sha1")-> StringThis filter is used to generate localised SNMPv3 authentication and privacy keys (section A.2 ofRFC3414) for use by Juniper devices, e.g:set snmp engine-id local {{ engineID }}
set snmp v3 usm local-engine user {{ snmpUser }} authentication-sha authentication-key {{ authPassword|junos_snmpv3_key(engineID) }}
set snmp v3 usm local-engine user {{ snmpUser }} privacy-aes128 privacy-key {{ privPassword|junos_snmpv3_key(engineID) }}cisco7encode(value: String, seed: Optional[String])-> StringThis filter will encode a string using Cisco's Type 7 encoding scheme. An optional "seed" can be provided which makes the encoded string deterministic for idempotent operations.junos9encode(value: String, seed: Optional[String])-> StringThis filter will encode a string using Juniper's Type 9 encoding scheme. An optional "seed" can be provided which makes the encoded string deterministic for idempotent operations.cisco8hash(value: String, salt: Optional[String])-> StringThis filter will hash a string using Cisco's Type 8 hashing scheme (SHA256 with PBKDF2). An optional "salt" (length must be 14 characters) can be provided which makes the hashed string deterministic for idempotent operations.cisco9hash(value: String, salt: Optional[String])-> StringThis filter will hash a string using Cisco's Type 9 hashing scheme (SCrypt). An optional "salt" (length must be 14 characters) can be provided which makes the hashed string deterministic for idempotent operations.cisco10hash(value: String, salt: Optional[String])-> StringThis filter will hash a string using Cisco's Type 10 hashing scheme (Unix Crypt based SHA512). An optional "salt" (length must be 16 characters) can be provided which makes the hashed string deterministic for idempotent operations.junos6hash(value: String, salt: Optional[String])-> StringThis filter will hash a string using Juniper's Type 6 hashing scheme (Unix Crypt based SHA512). An optional "salt" (length must be 8 characters) can be provided which makes the hashed string deterministic for idempotent operations.ipsort(query: List[String])-> List[String]This filter will sort a list of IPv4 and/or IPv6 addresses into numerical order.
This custom filter will be removed if and when the standard Ansibleipaddrfilter provides support.summarize_address_range(range: String)-> List[String]This filter will summarise an IPv4 or IPv6 range into a list of CIDR networks, e.g:{{ "192.0.2.0-192.0.2.255"|summarize_address_range }}This will result in the following output:['192.0.2.0/24']This custom filter will be removed if and when the standard Ansibleipaddrfilter provides support.xpath(query: String)-> String(requireslxmlpython module)This filter is used to perform an xpath query on an XML based output and return the matching sections as a list (if you use namespaces you need to ensure you define them using thexmlns:syntax), e.g:---xml:|2<interfaces xmlns:junos="http://xml.juniper.net/junos/*/junos"><interface><name>et-0/1/0</name><description junos:changed='changed'>## [DCI] 100GE Circuit to ROUTER-01 (et-0/1/0) ##</description></interface><interface><name>et-0/1/1</name><description>## 100GE Link to SPINE-01 (et-0/0/0) ##</description></interface><interface><name>et-0/1/2</name><description>## 100GE Link to SPINE-02 (et-0/0/0) ##</description></interface></interfaces>The following xpath query can be used to find all the "et" interfaces which have the attribute "junos:changed" applied to them and then output the interface's description:{% for description in xml|xpath("//interfaces/interface[starts-with(name, 'et')]/description[@junos:changed]/text()") %}
{{ description }}
{% endfor %}vaulty_encrypt(plaintext: String, password: String, cols: Optional[Integer])-> StringThis filter will encrypt a string usingVaultywhich is an alternative to Ansible Vault. Vaulty provides 256-bit authenticated symmetric encryption (AEAD) using ChaCha20-Poly1305 and Scrypt as the password based key derivation function. Ansible Vault is very inefficient when it comes to output size - for example, a 256 byte string encrypted using Ansible Vault will take up 1,390 bytes (a 443% increase) compared to Vaulty where the same string will only take up 412 bytes (a 61% increase). The optional "cols" argument will split the output into rows of "cols" width similar to Ansible Vault output.vaulty_decrypt(ciphertext: String, password: String)-> StringThis filter will perform the opposite ofvaulty_encryptand takes a Vaulty encrypted string and the password, e.g:{{ "$VAULTY;..."|vaulty_decrypt("password") }}