Templating 101
As Jinja2 is used as the templating engine it is highly recommended to familiarize yourself with Jinja2.
Here are several helpful articles:
And finally the Jinja2 Template Designer Documentation, a must read for Jinja2 template authors.
Template Configuration
The Template Configuration provides data, influences logic and control structures or points to further resources.
All data of the Template Configuration is available to the Declaration Template as python data structures
and can be accessed through the ninja
namespace.
Template Configuration Files
The Template Configuration is generated from Template Configuration Files.
Two data formats are supported as Template Configuration Files:
YAML
JSON
Combining Multiple Template Configuration Files is supported by AS3 Ninja, we discuss the details later.
Note
There are many Pros and Cons about JSON vs. YAML. While it is out of scope to discuss this in detail, YAML is often easier to start with for simple use-cases. Two good articles about the challenges YAML and JSON introduce for use as configuration files:
An example:
1services:
2 My Web Service:
3 type: http
4 address: 198.18.0.1
5 irules:
6 - ./files/irules/myws_redirects.iRule
7 backends:
8 - 192.0.2.1
9 - 192.0.2.2
The highlighted lines provide data which will be used in the Declaration Template to fill out specific fields, like the desired name for the service (line 2), its IP address and backend servers.
1services:
2 My Web Service:
3 type: http
4 address: 198.18.0.1
5 irules:
6 - ./files/irules/myws_redirects.iRule
7 backends:
8 - 192.0.2.1
9 - 192.0.2.2
On line 3 type: http
is used to indicate the service type.
This information is used in the Declaration Template logic to distinguish between types of services and apply type specific settings.
1services:
2 My Web Service:
3 type: http
4 address: 198.18.0.1
5 irules:
6 - ./files/irules/myws_redirects.iRule
7 backends:
8 - 192.0.2.1
9 - 192.0.2.2
irules
on line 5 references to a list of iRule files.
The logic within the Declaration Template can use this list to load the iRule files dynamically and add them to the service.
as3ninja namespace
The namespace as3ninja
within the Template Configuration is reserved for AS3 Ninja specific directives and configuration values.
Here is an overview of the current as3ninja
namespace configuration values.
1as3ninja:
2 declaration_template: /path/to/declaration_template_file.j2
The declaration_template
points to the Declaration Template File on the filesystem.
It is optional and ignored when a Declaration Template is referenced explicitly, for example through a CLI parameter.
The as3ninja
namespace is accessible under the ninja
namespace, as with any other data from Template Configurations.
Caution
The as3ninja
namespace is reserved and might be used by additional integrations, therefore it should not be used for custom configurations.
Back to our service example:
1as3ninja:
2 declaration_template: ./files/templates/main.j2
3services:
4 My Web Service:
5 type: http
6 address: 198.18.0.1
7 irules:
8 - ./files/irules/myws_redirects.iRule
9 backends:
10 - 192.0.2.1
11 - 192.0.2.2
We extended our Template Configuration with the declaration_template
directive to point to the Declaration Template ./files/templates/main.j2
.
AS3 Ninja will use this Declaration Template unless instructed otherwise (eg. through a CLI parameter).
Git and the as3ninja namespace
In addition as3ninja.git
is updated during runtime when using AS3 Ninja’s Git integration.
It holds the below information which can be used in the Declaration Template.
1as3ninja:
2 git:
3 commit:
4 id: commit id (long)
5 id_short: abbreviated commit id
6 epoch: unix epoch of commit
7 date: human readable date of commit
8 subject: subject of commit message
9 author:
10 name: author's name of commit message
11 email: author's email
12 epoch: epoch commit was authored
13 date: human readable format of epoch
14 branch: name of the branch
To use the short git commit id within the Declaration Template you would reference it as ninja.as3ninja.git.commit.id_short
.
Note
Git Authentication is not explicitly supported by AS3 Ninja.
However there are several options:
AS3 Ninja invokes the git command with privileges of the executing user, hence the same authentication facilities apply.
Implicitly providing credentials through the URL should work:
https://<username>:<password>@gitsite.domain/repository
When using Github: Personal Access Tokens can be used instead of the user password.
.netrc, which can be placed in the docker container at
/as3ninja/.netrc
, see confluence.atlassian.com : Using the .netrc file for an example.
Merging multiple Template Configuration Files
AS3 Ninja supports multiple Template Configuration Files. This provides great flexibility to override and extend Template Configurations.
Template Configuration Files are loaded, de-serialized and merged in the order specified. Starting from the first configuration every following configuration is merged into the Template Configuration. As the de-serialization takes place before merging, JSON and YAML can be combined.
Let’s use our previous example, and add two additional Template Configuration Files.
as3ninja
is removed for conciseness.
1# main.yaml
2services:
3 My Web Service:
4 type: http
5 address: 198.18.0.1
6 irules:
7 - ./files/irules/myws_redirects.iRule
8 backends:
9 - 10.0.2.1
10 - 10.0.2.2
1# internal_service.yaml
2services:
3 My Web Service:
4 address: 172.16.0.1
5 backends:
6 - 172.16.2.1
7 - 172.16.2.2
1# backends_dev.yaml
2services:
3 My Web Service:
4 backends:
5 - 192.168.200.1
6 - 192.168.200.2
main.yaml
is our original example.
internal_service.yaml
specifies the same My Web Service
and contains two keys: address
and backends
.
backends_dev.yaml
again contains our My Web Service
but only lists different backends
.
When AS3 Ninja is instructed to use the Template Configurations Files in the order:
main.yaml
internal_service.yaml
AS3 Ninja loads, de-serializes and then merges the configuration. This results in the below python dict.
1# merged: main.yaml, internal_service.yaml
2{
3 'services': {
4 'My Web Service': {
5 'address': '172.16.0.1',
6 'backends': ['172.16.2.1', '172.16.2.2'],
7 'irules': ['./files/irules/myws_redirects.iRule'],
8 'type': 'http',
9 }
10 }
11}
'address'
and 'backends'
was overridden by the data in internal_service.yaml
.
When AS3 Ninja is instructed to use all three Template Configurations Files in the order:
main.yaml
internal_service.yaml
backends_dev.yaml
The resulting python dict looks as below.
1# merged: main.yaml, internal_service.yaml, backends_dev.yaml
2{
3 'services': {
4 'My Web Service': {
5 'address': '172.16.0.1',
6 'backends': ['192.168.200.1', '192.168.200.2'],
7 'irules': ['./files/irules/myws_redirects.iRule'],
8 'type': 'http',
9 }
10 }
11}
The 'address'
and 'backends'
definition was first overridden by the data in internal_service.yaml
and 'backends'
was then again overridden by backends_dev.yaml
.
Important
Please note that sequences (lists, arrays) are not merged, they are replaced entirely.
Including further Template Configurations using as3ninja.include namespace
Further Template Configuration files can be included using include
within the as3ninja
namespace.
Combined with the ability to merge multiple Template Configuration files, this becomes a powerful feature which can raise complexity. So use with care.
Important rules for using as3ninja.include
:
Files included via
as3ninja.include
cannot include further Template Configuration files.All Template Configuration files supplied to as3ninja can use
as3ninja.include
.Every file included via
as3ninja.include
will only be included once, even if multiple configuration files reference this file.Files will be included in the order specified.
Files are included just after the current configuration file (containing the include statement).
When filename and/or path globbing is used, all matching files will be included alphabetically.
Finally when all includes have been identified
as3ninja.include
will be updated with the full list of all includes in the order loaded.
The following example illustrates the behavior. Suppose we have the below tree structure and three Template Configuration files.
1./configs
2├── one.yaml
3├── second
4│ ├── 2a.yaml
5│ ├── 2b.yaml
6│ └── 2c.yaml
7└── third
8 ├── 3rd.yaml
9 ├── a
10 │ ├── 3a.yaml
11 │ └── a2
12 │ └── 3a2.yaml
13 ├── b
14 │ ├── 3b1.yaml
15 │ └── 3b2.yaml
16 └── c
17 └── 3c.yaml
1# first.yaml
2as3ninja:
3 include: ./configs/one.yaml # a single file include can use key:value
1# second.yaml
2as3ninja:
3 include: # multiple file includes require a list
4 - ./configs/second/2c.yaml # explicitly include 2c.yaml first
5 - ./configs/second/*.yaml # include all other files
6 # The above order ensures that 2c.yaml is merged first and the
7 # remaining files are merged afterwards.
8 # 2c.yaml will not be imported twice, hence this allows to
9 # control merge order with wildcard includes.
1# third.yaml
2as3ninja:
3 include:
4 - ./configs/third/**/*.yaml # recursively include all .yaml files
5 - ./configs/one.yaml # try including one.yaml again
This will result in the following list of files, which will be merged to one configuration in the order listed:
1first.yaml
2configs/one.yaml
3second.yaml
4configs/second/2c.yaml # notice 2c.yaml is included first
5configs/second/2a.yaml
6configs/second/2b.yaml
7third.yaml
8configs/third/3rd.yaml
9configs/third/a/3a.yaml
10configs/third/a/a2/3a2.yaml
11configs/third/b/3b1.yaml
12configs/third/b/3b2.yaml
13configs/third/c/3c.yam
14# notice that configs/one.yaml is not included by third.yaml
Assume every YAML file has an data: <filename>
entry and you have a template.jinja2 with {{ ninja | jsonify }}
.
1as3ninja transform --no-validate -t template.jinja2 \
2 -c first.yaml \
3 -c second.yaml \
4 -c third.yaml \
5 | jq .
would produce:
1{
2 "as3ninja": {
3 "include": [
4 "configs/one.yaml",
5 "configs/second/2c.yaml",
6 "configs/second/2a.yaml",
7 "configs/second/2b.yaml",
8 "configs/third/3rd.yaml",
9 "configs/third/a/3a.yaml",
10 "configs/third/a/a2/3a2.yaml",
11 "configs/third/b/3b1.yaml",
12 "configs/third/b/3b2.yaml",
13 "configs/third/c/3c.yaml"
14 ]
15 },
16 "data": "configs/third/c/3c.yaml"
17}
Note
The above example is intended to demonstrate the behavior but could be seen as an example for bad practice due to the include complexity.
Including further YAML files using !include
AS3 Ninja uses a custom yaml !include
tag which provides additional functionality to include further YAML files.
!include
is followed by a filename (including the path from the current working directory) or a python list of filenames.
The filename(s) can include a globbing pattern following the rules of python3’s pathlib Path.glob.
Note
Nesting !include
is possible, e.g. a.yaml includes b.yaml which includes c.yaml but should be avoided in favor of a cleaner and more understandable design.
Suppose we have the below tree structure:
1.
2├── main.yaml
3└── services
4 ├── A
5 │ ├── serviceA1.yaml
6 │ ├── serviceA2.yaml
7 │ └── serviceA3.yaml
8 └── B
9 ├── serviceB1.yaml
10 └── serviceB2.yaml
Each serviceXY.yaml file contains definitions for its service, for example:
1ServiceXY:
2 address: 198.18.x.y
In main.yaml we use !include
to include the serviceXY.yaml files as follows:
1# Use globbing to traverse all subdirectories in `./services/`
2# and include all `.yaml` files:
3all_services: !include ./services/**/*.yaml
4
5# simply include a single yaml file:
6service_a1: !include ./services/A/serviceA1.yaml
7
8# include a single yaml file but make sure it is included as a list element:
9service_b1_list: !include [./services/B/serviceB1.yaml]
10
11# include two yaml files explicitly:
12service_a2_b2: !include [./services/A/serviceA2.yaml, ./services/B/serviceB2.yaml]
13
14# include all files matching serviceB*.yaml in the directory ./services/B/
15services_b: !include ./services/B/serviceB*.yaml
The above yaml describes all syntaxes of !include
and is equivalent to the below yaml.
Please specifically note the behavior for the following examples:
all_services contains a list of all the yaml files the globbing pattern matched.
service_a1 only contains the one yaml file, because only one file was specified, it is included as an object not a list.
service_a2_b2 contain a list with the entries of serviceA2.yaml and serviceB2.yaml
service_b1_list includes only serviceB1.yaml but as a list entry due to the explicit use of []
Note
Also note that the above paths are relative to the CWD where as3ninja is executed. That means if ls ./services/A/serviceA2.yaml is successful running as3ninja from the current directory will work as well.
1all_services:
2 - ServiceA2:
3 address: 198.18.1.2
4 - ServiceA3:
5 address: 198.18.1.3
6 - ServiceA1:
7 address: 198.18.1.1
8 - ServiceB2:
9 address: 198.18.2.2
10 - ServiceB1:
11 address: 198.18.2.1
12
13service_a1:
14 ServiceA1:
15 address: 198.18.1.1
16
17service_b1_list:
18 - ServiceB1:
19 address: 198.18.2.1
20
21service_a2_b2:
22 - ServiceA2:
23 address: 198.18.1.2
24 - ServiceB2:
25 address: 198.18.2.2
26
27services_b:
28 - ServiceB2:
29 address: 198.18.2.2
30 - ServiceB1:
31 address: 198.18.2.1
It is important to note that !include
does not create a “new yaml file” similar to the above example,
instead it de-serializes the main.yaml file and treats !include
as an “instruction”,
which then de-serializes the files found based on the !include
statement.
So de-serializing the main.yaml actually results in the below python data structure (dict):
1{
2 "all_services": [
3 { "ServiceA2": { "address": "198.18.1.2" } },
4 { "ServiceA3": { "address": "198.18.1.3" } },
5 { "ServiceA1": { "address": "198.18.1.1" } },
6 { "ServiceB2": { "address": "198.18.2.2" } },
7 { "ServiceB1": { "address": "198.18.2.1" } }
8 ],
9 "service_a1": { "ServiceA1": { "address": "198.18.1.1" } },
10 "service_b1_list": [
11 { "ServiceB1": { "address": "198.18.2.1" } }
12 ],
13 "service_a2_b2": [
14 { "ServiceA2": { "address": "198.18.1.2" } },
15 { "ServiceB2": { "address": "198.18.2.2" } }
16 ],
17 "services_b": [
18 { "ServiceB2": { "address": "198.18.2.2" } },
19 { "ServiceB1": { "address": "198.18.2.1" } }
20 ]
21}
Caution
!include
does not prevent against circular inclusion loops, which would end in a RecursionError exception.
Default Template Configuration File
If no Template Configuration File is specified, AS3 Ninja will try to use the first of the following files.
./ninja.json
./ninja.yaml
./ninja.yml
This is useful if you do not need multiple Template Configuration Files or only occasionally need them.
Declaration Template
The Declaration Template defines how the configuration is used to render an AS3 Declaration.
Declaration Templates use the Template Configuration, which is available in the Jinja2 Context.
A question of paradigms: Declarative or Imperative
If you thought you already choose the declarative paradigm with AS3 you are mostly correct. The AS3 Declaration is declarative.
But how do you produce the AS3 Declaration?
This is where AS3 Ninja and specifically Jinja2 comes into play. Jinja2 provides a wide spectrum between declarative and imperative to fit your specific needs.
A quick overview of Imperative vs. Declarative Programming, which can help understand the topic better: Imperative vs Declarative Programming
AS3 Ninja the declarative way
Let’s look at a declarative way to render an AS3 Declaration.
1{# Declaration Template #}
2{
3 "class": "AS3",
4 "declaration": {
5 "class": "ADC",
6 "schemaVersion": "3.11.0",
7 "id": "urn:uuid:{{ ninja.uuid }}",
8 "{{ ninja.tenant }}": {
9 "class": "Tenant",
10 "{{ ninja.app.name }}": {
11 "class": "Application",
12 "template": "http",
13 "backends": {
14 "class": "Pool",
15 "monitors": ["http"],
16 "members": [
17 {
18 "servicePort": 80,
19 "serverAddresses": [ {{ ninja.app.backends }} ]
20 }
21 ]
22 },
23 "serviceMain": {
24 "class": "Service_HTTP",
25 "virtualAddresses": ["{{ ninja.app.address }}"],
26 "pool": "backends"
27 }
28 }
29 }
30 }
31}
The above Declaration Template uses Jinja2 to fill specific values using variables. No logic, no control structures nor commands are used.
1# Template Configuration
2tenant: MyTenant
3uuid: 2819307c-d8c3-4d1e-911e-40889e1df6c7
4app:
5 name: MyApp
6 address: 198.18.0.1
7 backends: "\"192.168.0.1\", \"192.168.0.2\""
Above is an example Template Configuration for our Declaration Template.
As our backends are expected to be a JSON array, the value of backends
isn’t very pretty.
Adding additional services, tenants or service specific configurations will require changes in the Template Configuration as well as the Declaration Template.
AS3 Ninja the imperative way
Now let’s find an imperative way to render a similar AS3 Declaration.
1{# Declaration Template #}
2{
3 "class": "AS3",
4 "declaration": {
5 "class": "ADC",
6 "schemaVersion": "3.11.0",
7 "id": "urn:uuid:{{ uuid() }}",
8 {% for tenant in ninja.tenants %}
9 "{{ tenant.name }}": {
10 "class": "Tenant",
11 {% for app in tenant.apps %}
12 "{{ app.name }}": {
13 "class": "Application",
14 "template": "{{ app.type }}",
15 "backends": {
16 "class": "Pool",
17 "monitors":
18 {% if app.monitors is defined %}
19 {{ app.monitors | jsonify }},
20 {% else %}
21 {{ ninja.mappings.monitor[app.type] | jsonify }},
22 {% endif %}
23 "members": {{ app.backends | jsonify }}
24 },
25 "serviceMain": {
26 "class": "{{ ninja.mappings.service[app.type] }}",
27 "virtualAddresses": {{ app.address | jsonify }},
28 "pool": "backends"
29 }
30 }
31 {% if not loop.last %},{% endif %}
32 {% endfor %}
33 }
34 {% if not loop.last %},{% endif %}
35 {% endfor %}
36 }
37}
This Declaration Template not only uses Jinja2 to fill specific values using variables but also uses control structures, mainly loops and conditions (highlighted), to render the AS3 Declaration.
You can already see that this Declaration Template iterates over a list of tenants and a list of apps for each tenant. This clearly shows this example is probably easy to extend with additional tenants and apps.
As this Declaration Template contains a lot more details we will take a closer look at each step, but first let’s have a look at the Template Configuration:
1# Template Configuration
2tenants:
3- name: MyTenant
4 apps:
5 - name: MyApp
6 type: http
7 address:
8 - 198.18.0.1
9 backends:
10 - servicePort: 80
11 serverAddresses:
12 - 192.168.0.1
13 - 192.168.0.2
14mappings:
15 service:
16 http: Service_HTTP
17 monitor:
18 http:
19 - http
The Template Configuration is longer than the previous declarative example, but it is also more flexible.
The non-pretty representation of the backends has been replaced with a more flexible backends
definition (highlighted).
As this Configuration Template works hand in hand with the Declaration Template we will take a closer look at both in the next section.
Building a Declaration Template
A declarative Declaration Template and the corresponding Template Configuration is pretty straightforward as you saw earlier.
So instead we will look at the imperative example above and walk through each step. For conciseness we will remove parts from the Declaration Template and Template Configuration and focus on the subject.
Looping Tenants and their Apps
1# Template Configuration
2tenants:
3- name: MyTenant
4 # ... tenant specific configuration
5 apps:
6 - name: MyApp
7 type: http
8 # ... app specific configuration
The above Template Configuration excerpt contains a list of Tenants (line 2) with the first list entry having name
key with value MyTenant
(line 3).
Within this Tenant a list of Applications (Apps) is defined (line 5), with the first list entry having a name
key with value MyApp
(line 6).
1{# Declaration Template #}
2{
3 "class": "AS3",
4 {# ... more code ... #}
5 {% for tenant in ninja.tenants %}
6 "{{ tenant.name }}": {
7 "class": "Tenant",
8 {% for app in tenant.apps %}
9 "{{ app.name }}": {
10 {# ... app specific code ... #}
11 }
12 {% if not loop.last %},{% endif %}
13 {% endfor %}
14 }
15 {% if not loop.last %},{% endif %}
16 {% endfor %}
17 }
18}
The Declaration Template is built to iterate over a list of Tenants (line 5).
The Template Configuration list of Tenants is accessible via ninja.tenants
and each Tenant is assigned to tenant
, which is now available within the for loop.
On line 6 the Tenant name is read from tenant.name
.
Furthermore on line 8 the Declaration Template will iterate the list of Applications defined for this Tenant.
The list of Applications for this particular Tenant is available via tenant.apps
. apps
refers to the definition in the Template Configuration (on line 5).
The Application specific configuration starts on line 9, where app.name
is used to declarative the Application class of the AS3 Declaration.
Line 12 is checking for the last iteration of the inner “Application loop” and makes sure the comma (,
) is included when there are further elements in the Application list.
This is important as JSON does not tolerate a trailing comma.
Line 13 defines the end of the loop.
The same is done on line 15 and 16 for the outer “Tenants loop”.
Note
More details on control structures in Jinja2 can be found at List of Control Structures in the Jinja2 Template Designer Documentation.
Application specific settings
Now let’s look at the Application specific settings.
1# Template Configuration
2tenants:
3- name: Tenant1
4 apps:
5 - name: MyApp
6 type: http
7 address:
8 - 198.18.0.1
9 backends:
10 - servicePort: 80
11 serverAddresses:
12 - 192.168.0.1
13 - 192.168.0.2
14mappings:
15 service:
16 http: Service_HTTP
17 monitor:
18 http:
19 - http
The YAML is more structured to not only fit the Declaration Template but also the AS3 data structures.
A mappings
data structure was added to assist with default values / mappings to Application types.
1{# Declaration Template #}
2 {# ... more code ... #}
3 "{{ app.name }}": {
4 "class": "Application",
5 "template": "{{ app.type }}",
6 "backends": {
7 "class": "Pool",
8 "monitors":
9 {% if app.monitors is defined %}
10 {{ app.monitors | jsonify }},
11 {% else %}
12 {{ ninja.mappings.monitor[app.type] | jsonify }},
13 {% endif %}
14 "members": {{ app.backends | jsonify }}
15 },
16 "serviceMain": {
17 "class": "{{ ninja.mappings.service[app.type] }}",
18 "virtualAddresses": {{ app.address | jsonify }},
19 "pool": "backends"
20 {# ... more code ... #}
The app.type
is used on line 5 to map to the http
AS3 template,
on line 12 app.type
is used again as a key for mappings.service
.
This allows us to create multiple App type to Service_<type> mappings.
In this case http
maps to the AS3 service class Service_HTTP
.
Line 9-13 deals with monitors, if app.monitors
is defined it is used,
otherwise app.type
is used again to lookup the default monitor to use, based on the Template Configuration (line 17-19).
Note that "monitors"
is expected to be a JSON array of monitors, this is why the Template Configuration YAML uses a list for monitor.http
.
jsonify
is an AS3 Ninja Filter (see as3ninja.jinja2.filterfunctions.jsonify()
) which will convert any “piped” data to a valid JSON format.
A python list (which the YAML de-serializes to) is converted to a JSON array.
The "members"
key for a AS3 Pool class is expected to be a list, each list entry is an object with several key:value pairs.
serverAddresses
are again expected to be a list of IP addresses.
Looking at the backends
part of the Template Configuration again:
1 backends:
2 - servicePort: 80
3 serverAddresses:
4 - 192.168.0.1
5 - 192.168.0.2
app.backends
and it’s YAML exactly represents this structure, making it easy for the Declaration Template to just convert it to JSON (using the jsonify
filter).
Sometimes it is easier to look at the resulting JSON, as it is used by AS3 as well.
Here is how the above YAML for backends
looks like:
1{
2 "backends": [
3 {
4 "servicePort": 80,
5 "serverAddresses": ["192.168.0.1", "192.168.0.2"]
6 }
7 ]
8}
"virtualAddresses"
, on line 18 Declaration Template, is also expected to be a JSON array, which is what the Template Configuration perfectly represents and jsonify
converts to.
Adding more Tenants
Based on the above imperative example, it is easy to add further Tenants.
Here is an example adding one more Tenant:
1# Template Configuration
2tenants:
3- name: Tenant1
4 apps:
5 - name: MyApp
6 type: http
7 address:
8 - 198.18.0.1
9 backends:
10 - servicePort: 80
11 serverAddresses:
12 - 192.168.0.1
13 - 192.168.0.2
14- name: Tenant2
15 apps:
16 - name: TheirApp
17 type: http
18 address:
19 - 198.18.100.1
20 monitors:
21 - http
22 - icmp
23 backends:
24 - servicePort: 80
25 serverAddresses:
26 - 192.168.100.1
27mappings:
28 service:
29 http: Service_HTTP
30 monitor:
31 http:
32 - http
Adding an additional App type
What if we want to add an additional type of Application? Let’s assume we want to add a SSH server, using AS3’s Service_TCP.
As this service class doesn’t come with a default value for
virtualPort
we will need to modify our Declaration Template.
1{# Declaration Template #}
2{
3 "class": "AS3",
4 "declaration": {
5 "class": "ADC",
6 "schemaVersion": "3.11.0",
7 "id": "urn:uuid:{{ uuid() }}",
8 {% for tenant in ninja.tenants %}
9 "{{ tenant.name }}": {
10 "class": "Tenant",
11 {% for app in tenant.apps %}
12 "{{ app.name }}": {
13 "class": "Application",
14 "template": "{{ app.type }}",
15 "backends": {
16 "class": "Pool",
17 "monitors":
18 {% if app.monitors is defined %}
19 {{ app.monitors | jsonify }},
20 {% else %}
21 {{ ninja.mappings.monitor[app.type] | jsonify }},
22 {% endif %}
23 "members": {{ app.backends | jsonify }}
24 },
25 "serviceMain": {
26 {% if app.port is defined %}
27 "virtualPort": {{ app.port }},
28 {% endif %}
29 "class": "{{ ninja.mappings.service[app.type] }}",
30 "virtualAddresses": {{ app.address | jsonify }},
31 "pool": "backends"
32 }
33 }
34 {% if not loop.last %},{% endif %}
35 {% endfor %}
36 }
37 {% if not loop.last %},{% endif %}
38 {% endfor %}
39 }
40}
We added a conditional check for app.port
(line 26-28).
If it is set, "virtualPort"
will be added to the AS3 Declaration with the value of app.port
.
Of course this app.port
can be used by other service types as well.
1# Template Configuration
2tenants:
3- name: Tenant1
4 apps:
5 - name: MyApp
6 type: http
7 address:
8 - 198.18.0.1
9 backends:
10 - servicePort: 80
11 serverAddresses:
12 - 192.168.0.1
13 - 192.168.0.2
14- name: Tenant2
15 apps:
16 - name: TheirApp
17 type: http
18 address:
19 - 198.18.100.1
20 monitors:
21 - http
22 - icmp
23 backends:
24 - servicePort: 80
25 serverAddresses:
26 - 192.168.100.1
27 - name: TcpApp
28 type: tcp
29 port: 22
30 address:
31 - 198.18.100.1
32 backends:
33 - servicePort: 22
34 serverAddresses:
35 - 192.168.100.1
36mappings:
37 service:
38 http: Service_HTTP
39 tcp: Service_TCP
40 monitor:
41 http:
42 - http
43 tcp:
44 - tcp
Line 29 has the new port
key, which is used in the Declaration Template.
Along with the TCP based service we also updated the mappings.
Hint
If you use Visual Studio Code, the jinja-json-syntax Syntax Highlighter is very helpful.