Welcome to AS3 Ninja’s documentation!

AS3 Ninja

Free software: ISC license
Documentation: https://as3ninja.readthedocs.io
Works with Python 3.8 and up
What is AS3 Ninja and what can it do for you?
AS3 Ninja is a templating engine as well as a validator for AS3 declarations. It offers a CLI for local usage, as well as a OpenAPI/Swagger based REST API.
AS3 Ninja empowers you to create AS3 declarations in a DevOps way by embracing the ideas of GitOps and CI/CD.
It separates Configuration from Code (Templates) as far as YOU wish.
AS3 Ninja let’s you decide to scale between declarative and imperative paradigms to fit your needs.
What AS3 Ninja doesn’t do:
It does not provide you with a UI to create configurations
It does not deploy AS3 configurations
Features
Validate your AS3 Declarations against the AS3 Schema (via API, eg. for CI/CD) and AS3 specific formats
Create AS3 Declarations from templates using the full power of Jinja2 (CLI and API)
reads your JSON or YAML configurations to generate AS3 Declarations
carefully crafted Jinja2
as3ninja.jinja2.filters
,as3ninja.jinja2.functions
andas3ninja.jinja2.filterfunctions
further enhance the templating capabilities
Use Git(hub) to pull template configurations and declaration templates
HashiCorp Vault integration to retrieve secrets
AS3 Ninja provides a simple CLI..
..and a REST API including a Swagger/OpenAPI interface at /api/docs and /api/redoc (openapi.json @ /api/openapi.json)
AS3 Ninja Interface
Some impressions from the AS3 Ninja interfaces:
the Command Line
the API UI
ReDoc and Swagger UI:

Swagger UI demo:

Disclaimer and Security Note
AS3 Ninja is not a commercial product and is not covered by any form of support, there is no contract nor SLA!. Please read, understand and adhere to the license before use.
AS3 Ninja’s focus is flexibility in templating and features, it is not hardened to run in un-trusted environments.
It comes with a large set of dependencies, all of them might introduce security issues
Jinja2 is not using a Sandboxed Environment and the readfile filter allows arbitrary file includes.
The API is unauthenticated
Danger
Only use AS3 Ninja in a secure environment with restricted access and trusted input.
Where to start?
Read the Docs and then Try it out! :-)
Concepts
To get started with AS3 Ninja first let’s look at the concept.
The objective is to generate AS3 Declarations from templates where the parameterization of the template is done using a configuration.
AS3 Ninja uses the following main components to achieve this:
Templates (Jinja2)
Configurations (YAML and/or JSON)
The templates are also referred to as Declaration Templates, the configuration is referred to as Template Configuration.
AS3 Ninja doesn’t force you into a declarative or imperative paradigm. You can just “fill the blanks” (declarative) or implement excessive logic within the Declaration Templates (imperative).
Workflow
The workflow to generate a deployable AS3 Declaration is a follows:
Get the Declaration Template and Template Configuration from the local filesystem or Git
Load, de-serialize and merge all Template Configurations
Feed the Declaration Template and Template Configuration to Jinja2
Render the AS3 Declaration using jinja2 (“transform the Declaration Template using the Template Configuration”)
Validate the AS3 Declaration against the AS3 Schema (optional)
This workflow is also referred to as transformation.
Components
Let’s look at the components at play.
Here is a diagram.
The AS3 Ninja ecosystem
+-----------------+ +----------------+
| | | |
| HashiCorp Vault +--------+ | AS3 Schema |
| | | | |
+-----------------+ | +-------+--------+
| ^
| |
| |
| +-----+------+
+------->+_ |
+------------------+ |\\ , | +---------------------+
| | | \\ (**)~ | | |
| Git(Hub/Lab/Tea) +--------------->+ \\ AS3 ,% +------------->+ AS3 Declaration |
| or filesystem | | Ninja | | |
| | | / \ | +---------------------+
+-+----------------+ +------------+
| | API || CLI |
| +--+------+--+
| ^ ^
+--[Declaration Template(s)]----+ | |
| +----+------+
+--[Template Configuration(s)]--+
AS3 Declaration
The AS3 Declaration is the JSON file ready to be pushed to the AS3 API. In DevOps terms it is an artifact (see also here). It contains all configuration elements needed for AS3 to create the configuration.
Note
AS3 Declarations often contain very sensitive data, like cryptographic keys or passwords.
Declaration Templates
Declaration Templates are Jinja2 templates, which can include further templates for specific AS3 Declaration components, e.g. for pools or profiles. Jinja2 offers a variety of imperative programming techniques like control structures.
The Jinja2 Template Designer Documentation is a highly recommended read.
Filters and Functions
Jinja2 offers filters and functions which can be used in templates.
AS3 Ninja comes with additional filters and functions which are specifically aimed at AS3.
See also:
Template Configuration
The Template Configuration are one or more YAML or JSON files. These define various variables, lists and in general contain data to be used in the Declaration Template(s).
Multiple configuration files can be combined, where settings within the previous file are updated by all following files. This is quite powerful, as it allows to overwrite (“overlay”) specific configuration parameters, for example for different environments (DEV/QA/PROD).
Note
It is recommended to avoid storing secrets within the Template Configuration.
AS3 Schema
Once the AS3 Declaration is generated from the Declaration Template using the Template Configuration, the resulting artifact can be validated against the AS3 Schema, which is available on the GitHub AS3 Repository.
Note
AS3 Ninja doesn’t need to generate the AS3 Declaration to validate it. Any other declaration can be validated against the AS3 Schema using the API.
Git
Git has not only conquered the world of version control systems but is also very handy when you need to save, version, track and rollback any kind of configuration files. Therefore Git is a perfect place to store Declaration Template(s) as well as Template Configuration(s).
AS3 Ninja can fetch from Git and automatically generate an AS3 Declaration for you.
Vault
AS3 Declarations often contain very sensitive data, these are commonly called secrets in the DevOps context. Hashicorp’s Vault is a well established platform to manage any kind of secret and AS3 Ninja uses hvac to interface with vault.
AS3 Ninja retrieves relevant secrets during the transformation of the AS3 Declaration. The Declaration Template contains functions / filters which communicate to vault based on the settings within the template as well as the Template Configuration.
See Vault Integration for further details.
Using AS3 Ninja
Run AS3 Ninja with Docker
Starting an ephemeral/temporary container for use of the API:
docker run -it --rm -p 8000:8000 simonkowallik/as3ninja
Creating a persistent container for CLI and API usage:
docker container create --name as3ninja -p 8000:8000 simonkowallik/as3ninja
# start the as3ninja container in background
docker start -a as3ninja
Important
The AS3 Schema files need to be downloaded from github.com to validate AS3 Declarations.
AS3 Ninja as3ninja.AS3Schema.updateschemas()
is doing that for you automatically,
but the Docker Container will need access to https://github.com.
Using the CLI with Docker
Docker can be used to run the command line.
$ tree ./examples/simple/
./examples/simple/
├── http_path_header.iRule
├── ninja.yaml
├── sorry_page.iRule
└── template.j2
This example assumes the relevant Template Configurations and Declaration Templates are stored in ./examples/simple
.
1as3ninja:
2 declaration_template: "examples/simple/template.j2"
The declaration_template
statement within the ninja.yaml
provides the template location as examples/simple/template.j2
.
as3ninja expects to find the template at this location.
1$ docker run --rm --tty --interactive \
2 --volume \$(pwd)/examples:/examples \
3 simonkowallik/as3ninja:latest \
4 as3ninja transform -c /examples/simple/ninja.yaml \
5 | jq ."
Instructs docker to bind mount the $(pwd)/examples
folder to /examples
(line 2) for the container image simonkowallik/as3ninja:latest
(line 3).
Docker then executes as3ninja transform -c /examples/simple/ninja.yaml
(line 4) within the container and pipes the result to jq .
.
Todo
more cli examples
Command Line Usage
# for manual system wide installation (not recommended)
$ git clone https://github.com/simonkowallik/as3ninja
$ cd as3ninja
$ poetry build
$ pip3 install $(ls build/as3ninja*.whl)
# via PyPI using pip
$ pip=$(type -p pip3 || type -p pip)
$ $pip install as3ninja
API Usage
Use curl
or httpie
to query all available AS3 Schema versions:
$ http localhost:8000/api/schema/versions
$ curl -s localhost:8000/api/schema/versions | jq .
Navigate to http://localhost:8000/api/docs and http://localhost:8000/api/redoc to explore the API.
Todo
Postman collection for API calls
Validating a declaration
Using an ephemeral container with docker run
:
$ docker run -it --rm -v $PWD/declaration.json:/declaration.json \
simonkowallik/as3ninja:latest \
as3ninja validate -d /declaration.json
INFO: Validation passed for AS3 Schema version: 3.22.1
$ docker run -it --rm -v $PWD/declaration.json:/declaration.json \
simonkowallik/as3ninja:latest \
as3ninja validate -d /declaration.json --version 3.17.0
INFO: Validation passed for AS3 Schema version: 3.17.0
Using the API via curl
:
# start the docker container on port 8000
docker run -d --rm -p 8000:8000 simonkowallik/as3ninja:latest
6dd7a4a9cc65f84974a122e0605dd74fe087a7e61e67298e529bcd96fa133c7
# POST declaration to /api/schema/validate endpoint (curl)
curl -s http://localhost:8000/api/schema/validate -d @declaration.json | jq .
{
"valid": true,
"error": null
}
# POST declaration to /api/schema/validate endpoint (httpie)
cat $PWD/examples/dynamic-irule/declaration.json | \
http POST 'localhost:8000/api/schema/validate?version=3.20.0'
HTTP/1.1 200 OK
content-length: 27
content-type: application/json
date: Sun, 13 Sep 2020 12:14:03 GMT
server: uvicorn
{
"error": null,
"valid": true
}
Postman collection
An AS3 Ninja Postman collection is available on Github.
Python Package
Todo
Update usage as a module
To use AS3 Ninja in your python project:
1from as3ninja.schema import AS3Schema, AS3ValidationError
2from as3ninja.declaration import AS3Declaration
3
4# Declaration Template (str)
5declaration_template = """
6{
7 "class": "AS3",
8 "declaration": {
9 "class": "ADC",
10 "schemaVersion": "3.11.0",
11 "id": "urn:uuid:{{ uuid() }}",
12 "{{ ninja.Tenantname }}": {
13 "class": "Tenant"
14 }
15 }
16}
17"""
18
19# Template Configuration (dict)
20template_configuration = {
21 "Tenantname": "MyTenant"
22}
23
24# generate the AS3 Declaration
25as3declaration = AS3Declaration(
26 template_configuration=template_configuration,
27 declaration_template=declaration_template
28 )
29
30from pprint import pprint
31# the transformed AS3 Declaration is available via the declaration attribute
32pprint(as3declaration.declaration)
33{'class': 'AS3',
34 'declaration': {'MyTenant': {'class': 'Tenant'},
35 'class': 'ADC',
36 'id': 'urn:uuid:f3850951-4a63-43ec-b2a3-28ab2c315479',
37 'schemaVersion': '3.11.0'}}
38
39# create an AS3 schema instance
40as3schema = AS3Schema()
41
42# Validate the AS3 Declaration against the AS3 Schema (latest version)
43try:
44 as3schema.validate(declaration=as3declaration.declaration)
45except AS3ValidationError:
46 # an AS3ValidationError exception is raised when the validation fails
47 raise
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.
Vault Integration
HashiCorp Vault is a reliable and secure secret management engine widely used in the DevOps community.
The integration in AS3 Ninja is based on hvac.
Background
The term secrets describes secret information, like private cryptographic keys for x509 certificates, passwords and other shared secrets. As secrets are often used to ensure confidentiality and integrity, it is crucial to prevent compromise. Configuration management and version control systems, like git(hub), are not well suited nor meant to hold secret information. HashiCorp Vault provides a solution to manage secrets for AS3 Ninja.
Different types of secrets exist, therefore Vault provides a variety of Secrets Engines to fullfil the specific needs of these secret types. Two Secrets Engines are useful particular for AS3 Ninja:
KV1
KV2
Both are Key Value stores, where KV2 provides versioning of secrets. See Vault Docs: Secrets Engines: Key/Value for more details.
Concept
AS3 Ninja is a client to Vault and retrieves secrets during AS3 Declaration generation. Although secrets management is a complicated topic the AS3 Ninja Vault integration is relatively straightforward. One important assumption is that the authentication to Vault is not performed directly by AS3 Ninja. Also see Vault Docs: Concepts: Authentication.
AS3 Ninja assumes that a token is provided, which represents an authenticated session to communicate with Vault. Generating/Fetching the token is out of scope of AS3 Ninja.
AS3 Ninja can also communicate to multiple Vault instances in case secrets are spread across different Vault deployments.
Vault Communication
Communication with Vault is established through Vault’s REST API.
To initiate communication a three parameters are required:
Vault’s address
communication parameters
A valid token
There are multiple ways to specify these parameters depending on how you use the Vault integration.
Vault can be accessed with as3ninja.vault.vault
, which is available as Jinja2 filter as well as a Jinja2 function.
A specific client can be created using the Jinja2 function as3ninja.vault.VaultClient
.
This client is tied to the Vault instance defined by the parameters passed to VaultClient.
The vault filter/function optionally accepts this client as a parameter.
This is helpful in case a specific Vault instance must be contacted or multiple Vault instances are needed.
The usage of vault and VaultClient is explained later.
When using vault and not passing a specific client, AS3 Ninja will use the as3ninja.vault.VaultClient.defaultClient()
to initiate communication with Vault.
The defaultClient connection logic is as follows:
It will first check if an authenticated vault connection exists already.
This is helpful in case AS3 Ninja is executed from the command line and a Vault connection has been established using Vaults own cli.
If 1. isn’t successful It will then check the Jinja2 Context for the namespace
ninja.as3ninja.vault
and useaddr
,token
andssl_verify
for connection establishmentThis provides great flexibility as the Vault connection can be parametrized by the Template Configuration.
For any namespace variable in 2., it will check the environment variables
VAULT_ADDR
,VAULT_TOKEN
andVAULT_SKIP_VERIFY
.This allows to fallback to environment variables. This is helpful when AS3 Ninja is used through the cli. It is also very helpful when AS3 Ninja runs as a docker container as the default Vault connection can be specified on the container level.
If
VAULT_SKIP_VERIFY
doesn’t exist, it will useVAULT_SSL_VERIFY
from the AS3 Ninja configuration file (as3ninja.settings.json
).
The variables in ninja.as3ninja.vault
can be specified using the Template Configuration, for example:
as3ninja:
vault:
addr: https://192.0.2.100:8201
token: s.jbm5eO3rmh1kxrraNA9Q0N5r
ssl_verify: false
Note
Remember that anything defined in the Template Configuration will be stored in the ninja
namespace within the Jinja2 context. That’s why ninja.as3ninja.vault
is used but the YAML example starts by defining as3ninja:
.
Referencing Secrets in Template Configurations
To retrieve a secret from Vault a couple of parameters are required:
The mount_point of the Secrets Engine
The path of the Secret
The Secrets engine
The version (in case of Secrets Engine kv2)
The filter selects the exact piece of information required from the response
mount_point
If the mount_point is part of the path and is configured during setup of the Secrets engine in Vault. If the mount_point is just one level, for example /mySecretEngineKV2, it can be omitted if it is part of path.
path
The path defines which secret to retrieve. If mount_point is omitted is must include the mount_point, see paragraph above.
engine
engine defines the Secrets Engine the secret is stored in. Default is KV2.
Supported Secrets Engines:
KV1
KV2
version
In case KV2 is used, secrets can be versioned. When version is provided, a specific version of the secret is fetched. Default is version=0, which is the most recent version. version is optional.
filter
filter is an optional setting and can be used to select a specific element from the Vault response. The filter is a string of keys separated by dots (e.g. key1.key2.key3). If a key contains a dot in the name, it can be escaped (e.g. k\\.e\\.y.anotherKey would be split to k.e.y and anotherKey).
Examples
secrets:
myWebApp:
path: /secretkv2/myWebApp/privateKey
The simplest definition of a secret just contains the path. vault will use the KV2 secrets engine and return the most recent version of the secret.
secrets:
myAPI:
path: /secretOne/myAPI/sharedKey
engine: kv1
When using KV1, the engine must be explicitly specified.
secrets:
v1Service:
path: /otherService/privateKey
mount_point: /SecEnginePath/myKV2
version: 1
latestService:
path: /otherService/privateKey
mount_point: /SecEnginePath/myKV2
Say a secrets engine was created with:
vault secrets enable -path=/SecEnginePath/myKV2 kv-v2
As the path has multiple levels, the mount_point must be explicitly specified.
The secret v1Service references to a specific version of the secret (version: 1), where latestService refers to the most recent version. latestService could have used version: 0 to explicitly state that the most recent version should be used but this is optional.
Using Vault with AS3 Ninja
Let’s look at using vault as a jinja2 filter and function as well as using VaultClient.
Note
To keep the examples concise, none of the below produce a valid AS3 declaration. Therefore the –no-validate flag is required.
A simple example (Secrets Engine: KV1)
1# Template Configuration
2secrets:
3 myAPI:
4 path: /secretOne/myAPI/sharedKey
5 engine: kv1
Our secret will be accessible during transformation of the Declaration Template as ninja.secrets.myAPI
.
ninja.secrets.myAPI.path
will refer to the value /secretOne/myAPI/sharedKey
and ninja.secrets.myAPI.engine
will refer to kv1
.
1{# Declaration Template #}
2{
3 "myAPI": {{ ninja.secrets.myAPI | vault | jsonify }}
4}
We use vault as a filter and the value of ninja.secrets.myAPI
is passed as the first parameter automatically by jinja2.
vault
will read all keys in the passed parameter and try to retrieve the relevant secret from Vault.
Run as3ninja:
as3ninja transform -c ninja.yml -t template.j2 --no-validate | jq .
Resulting JSON:
1{
2 "myAPI": {
3 "request_id": "308c8b5c-fadc-ff32-8543-ad611fc53d72",
4 "lease_id": "",
5 "renewable": false,
6 "lease_duration": 2764800,
7 "data": {
8 "secretKey": "AES 128 4d3642df883756b0d5746f32463f6005"
9 },
10 "wrap_info": null,
11 "warnings": null,
12 "auth": null
13 }
14}
The value of "myAPI"
contains details about the fetched Vault secret, probably more than needed. Likely we are only interested in a specific value, for example data -> secretKey.
Modifying the Declaration Template like below would just extract this specific value:
1{
2 "myAPI": {{ (ninja.secrets.myAPI | vault)['data']['secretKey'] | jsonify }}
3}
Using a filter in the secret’s definition within the Template Configuration is a better alternative as this separates the configuration further from the implementation (the Declaration Template). Here is the updated Template Configuration:
1# Template Configuration
2secrets:
3 myAPI:
4 path: /secretOne/myAPI/sharedKey
5 engine: kv1
6 filter: data.secretKey
The resulting JSON now only contains the information we are looking for:
1{
2 "myAPI": "AES 128 4d3642df883756b0d5746f32463f6005"
3}
Example using Secrets Engine KV2
1 # Template Configuration
2 latestService:
3 path: /otherService/privateKey
4 mount_point: /SecEnginePath/myKV2
1{# Declaration Template #}
2{
3 "latestService": {{ ninja.secrets.latestService | vault | jsonify }}
4}
Run as3ninja:
as3ninja transform -c ninja.yml -t template.j2 --no-validate | jq .
Resulting JSON:
1{
2 "latestService": {
3 "request_id": "25b2debe-7514-de9a-8beb-dd798f898ddf",
4 "lease_id": "",
5 "renewable": false,
6 "lease_duration": 0,
7 "data": {
8 "data": {
9 "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIHzAgEAAjEAvAI1w37cQcrflizN6Qa6GYVO26Sup5J0WWirYDS1aoxXCjQDcN4Q\nf7cCQ82kSzcjAgMBAAECMFS5sjzdiKjlogjtPAYNkAQ8PSNifYrqxlpT4D5+TpWj\nM1ODUjTVZBPQXuUIJYo6gQIZAOBcs33j5C6k7sisCVAvJTCTmdMx037zYQIZANaF\nLSMLGaEhYz1da3OR6IHyM9Anx/h9AwIZAL4vlq+GeKzZfth4jMR90malF+Yg/IlG\nwQIZAJKgRqDMRoFfK9DW2MoOsgiX/xhJCKLs9wIYPHBqLjhfB5Ycuk+WyxHj2uNQ\nNpf7zbsE\n-----END RSA PRIVATE KEY-----"
10 },
11 "metadata": {
12 "created_time": "2019-11-30T13:05:16.5110593Z",
13 "deletion_time": "",
14 "destroyed": false,
15 "version": 2
16 }
17 },
18 "wrap_info": null,
19 "warnings": null,
20 "auth": null
21 }
22}
As we already know the result carries likely more information than we need. In contrast to KV1 the KV2 Secrets Engine uses one more level of nesting as it does provide explicit metadata (line 11) about the secret.
The information we are looking for is found at data -> data -> privateKey (line 7-9). Within the secret’s metadata the version of the retrieved secret is displayed ("version": 2
at line 15).
As we already learnt we can filter the response data by either updating the Declaration Template or using the filter. Updated Template Configuration:
1 # Template Configuration
2 latestService:
3 path: /otherService/privateKey
4 mount_point: /SecEnginePath/myKV2
5 filter: data.privateKey
Note
Although KV2 stores the privateKey in data -> data we can omit the first instance of data as this is automatically prepended by the vault jinja2 filter/function. If you would like to access the version in the metadata the filter would be metadata.version.
Result:
{
"latestService": "-----BEGIN RSA PRIVATE KEY-----\nMIHzAgEAAjEAvAI1w37cQcrflizN6Qa6GYVO26Sup5J0WWirYDS1aoxXCjQDcN4Q\nf7cCQ82kSzcjAgMBAAECMFS5sjzdiKjlogjtPAYNkAQ8PSNifYrqxlpT4D5+TpWj\nM1ODUjTVZBPQXuUIJYo6gQIZAOBcs33j5C6k7sisCVAvJTCTmdMx037zYQIZANaF\nLSMLGaEhYz1da3OR6IHyM9Anx/h9AwIZAL4vlq+GeKzZfth4jMR90malF+Yg/IlG\nwQIZAJKgRqDMRoFfK9DW2MoOsgiX/xhJCKLs9wIYPHBqLjhfB5Ycuk+WyxHj2uNQ\nNpf7zbsE\n-----END RSA PRIVATE KEY-----"
}
Using vault as a jinja2 function
Note
The below example is based on the KV2 example above
We can use vault as a jinja2 function as well. This allows us to implement more generic queries and re-use the secret information without asking Vault all the time.
1{
2{% set s = namespace() %}
3{% set s.latestService = vault(secret=ninja.secrets.latestService, filter="") %}
4{% set s.latestService_privKey = s.latestService['data']['data']['privateKey'] %}
5{% set s.latestService_ver = s.latestService['data']['metadata']['version'] %}
6 "latestService_privateKey": {{ s.latestService_privKey | jsonify }},
7 "latestService_version": {{ s.latestService_ver | jsonify }}
8}
The above Declaration Template creates a jinja2 variable namespace for better reusability.
vault is invoked passing ninja.secrets.latestService
to the secret parameter manually. When using vault as a jinja2 filter, this isn’t necessary as the “piped” variable name is passed to the secret parameter automatically.
In addition the filter parameter is set to an empty string to override any filter set within the Template Configuration. The empty string is not treated as a filter, therefore the whole secret is returned.
secrets.latestService
now contains all the data we saw in the previous example and we create two more variables to store and later use the specific secret information we are interested in.
The resulting JSON looks like this:
1{
2 "latestService_privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIHzAgEAAjEAvAI1w37cQcrflizN6Qa6GYVO26Sup5J0WWirYDS1aoxXCjQDcN4Q\nf7cCQ82kSzcjAgMBAAECMFS5sjzdiKjlogjtPAYNkAQ8PSNifYrqxlpT4D5+TpWj\nM1ODUjTVZBPQXuUIJYo6gQIZAOBcs33j5C6k7sisCVAvJTCTmdMx037zYQIZANaF\nLSMLGaEhYz1da3OR6IHyM9Anx/h9AwIZAL4vlq+GeKzZfth4jMR90malF+Yg/IlG\nwQIZAJKgRqDMRoFfK9DW2MoOsgiX/xhJCKLs9wIYPHBqLjhfB5Ycuk+WyxHj2uNQ\nNpf7zbsE\n-----END RSA PRIVATE KEY-----",
3 "latestService_version": 2
4}
Specifying a secret version
A secret version can be specified either in the secrets configuration statement or explicitly via vault’s version
parameter.
If we modify the vault call from the previous example like below, version 1 of the secret will be retrieved.
The version
parameter is optional and overrules any version configuration. It is valid regardless if vault is used as a filter or function.
1{% set secrets.latestService = vault(secret=ninja.secrets.latestService,version=1) %}
1{
2 "latestService_privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIGrAgEAAiEAyKNcibrMfVxuEwtifphGvEH1eP5Gjb3jbq8o0NfjjAMCAwEAAQIg\nRp5RJN0NupX83FEmgr5gLqSYKeiIFCF4/vEcLrvVhOkCEQD5WC8HQPmQLFU//171\n92OVAhEAzf5bxQk73WWXG6Wzcy7LNwIRANUDlQmpZIralOnbjJCtDBECECmOR6sf\nKsGGLg64xdPVu88CEQDrfrKtfD5cSVENuhJ1LLie\n-----END RSA PRIVATE KEY-----",
3 "latestService_version": 1
4}
Using VaultClient
as3ninja.vault.VaultClient
provides a way to connect to a specific Vault instance explicitly.
VaultClient will return a client which can be passed to the vault filter/function.
Re-using the myAPI example with the following Declaration Template:
1{
2{% set vc = namespace() %}
3{% set vc.client = VaultClient(addr="https://localhost:8201", verify=False) %}
4"myAPI": {{
5 ninja.secrets.myAPI | vault(client=vc.client) | jsonify
6 }}
7}
In this example the client is created on line 3 and stored in vc.client
, which is then used in the vault filter as an argument to the client parameter.
No explicit token was specified in this example. If no token is specified VaultClient will try to use the environment variable VAULT_TOKEN
or an existing authenticated session based on Vault’s cli (in that order).
An explicit token can be specified via the VaultClient token
parameter.
Here is a fully parametrized example.
1# Template Configuration
2dev:
3 vault:
4 token: s.iorspPP7f7EFpyudye6DB6Jn
5 server_url: "https://dev-vault.example.net:8200"
6 verify: false
7secrets:
8 myAPI:
9 path: /secretOne/myAPI/sharedKey
10 engine: kv1
11 filter: data.secretKey
1{# Declaration Template #}
2{
3{% set vc = namespace() %}
4{% set vc.client = VaultClient(
5 addr=ninja.dev.vault.server_url,
6 token=ninja.dev.vault.token,
7 verify=ninja.dev.vault.verify
8 )
9%}
10"myAPI": {{
11 ninja.secrets.myAPI | vault(client=vc.client) | jsonify
12 }}
13}
Using the AS3 Ninja vault integration directly with python
Although it is out of scope AS3 Ninja’s vault integration can be used from python directly.
1from as3ninja.vault import VaultClient, vault
2
3my_vault_token = "s.tCU2wabNVCcySNncK2Mf6dwT"
4
5s_myAPI = {
6 'path':'/secretOne/myAPI/sharedKey',
7 'engine':'kv1',
8 'filter':'data.secretKey',
9 }
10
11s_latestService = {
12 'path':'/otherService/privateKey',
13 'mount_point':'SecEnginePath/myKV2',
14 'filter':'data.privateKey',
15 }
16
17# using vault with an explicit Vault client
18
19vc = VaultClient(addr="http://localhost:8200/",token=my_vault_token)
20
21vault(ctx={}, client=vc, secret=s_myAPI)
22'AES 128 4d3642df883756b0d5746f32463f6005'
23
24vault(ctx={} ,client=vc, secret=s_latestService)['data']['data']['privateKey']
25'-----BEGIN RSA PRIVATE KEY-----\nMIHzAgEAAjEAvAI1w37cQcrflizN6Q...'
26
27
28# using vault with a mocked jinja2 context
29
30vault_settings = {'addr':'http://localhost:8200', 'token':my_vault_token}
31
32jinja2_context = {'ninja':{'as3ninja':{ 'vault': vault_settings }}}
33
34vault(ctx=jinja2_context, secret=s_myAPI)
35'AES 128 4d3642df883756b0d5746f32463f6005'
36
37vault(ctx=jinja2_context, secret=s_latestService)
38'-----BEGIN RSA PRIVATE KEY-----\nMIHzAgEAAjEAvAI1w37cQcrflizN6Q...'
39
40vault(ctx=jinja2_context, secret=s_latestService, version=1)
41'-----BEGIN RSA PRIVATE KEY-----\nMIGrAgEAAiEAyKNcibrMfVxuEwtifp...'
as3ninja
as3ninja package
Subpackages
as3ninja.jinja2 package
Submodules
as3ninja.jinja2.filterfunctions module
This module holds Jinja2 functions which also work as filters.
- as3ninja.jinja2.filterfunctions.b64decode(data, urlsafe=False)[source]
Accepts a string and returns the Base64 decoded representation of this string. urlsafe=True decodes urlsafe base64
- Return type:
Union
[str
,bytes
]
- as3ninja.jinja2.filterfunctions.b64encode(data, urlsafe=False)[source]
Accepts a string and returns the Base64 encoded representation of this string. urlsafe=True encodes string as urlsafe base64
- Return type:
str
- as3ninja.jinja2.filterfunctions.env(env_var, default=None)[source]
Reads an environment variable and returns its value.
Use
default
to specify a default value in case the environment variable does not exist, Empty environment variables will return an empty string.Examples:
{# using env as a filter #} "HOME_DIR": "{{ 'HOME' | env }}"
{# using env as a function #} {% set home_dir = env("HOME") %} {% set temp_dir = env("TEMPDIR", default="/tmp") %}
- Return type:
str
- as3ninja.jinja2.filterfunctions.hashfunction(data, hash_algo, digest_format='hex')[source]
Returns the digest of
data
for hash algorithmhash_algo
. The digest is returned as hex by default, but can be returned as binary as well (digest_format
)Check the hashlib documentation of your python version for supported hash functions.
- Parameters:
hash_algo (
str
) – hash algorithmdigest_format (
str
) – Digest format to return. Either hex (default) or binary.
- Return type:
Union
[str
,bytes
]
For the variable length shake digest, 256 bits are returned.
{% set whirlpool_hexdigest = hashfunction("fun with hashes", "whirlpool") %} {# value of whirlpool_hexdigest is "ce38f0a536e71b5b0758932c1d5f32d2ab6cc5bff9f02fb7c97a70291d45efa4516d4e3d99000956587c7c9f691f64b3444a91661d45f526552a9e2d42428b09" # note that whirlpool is not a guaranteed hash function, hence might not be available on all platforms #}
- as3ninja.jinja2.filterfunctions.jsonify(data, quote=True)[source]
serializes data to JSON format.
quote=False
avoids surrounding quotes, For example:"key": "{{ ninja.somevariable | jsonify(quote=False) }}"
Instead of:
"key": {{ ninja.somevariable | jsonify }}
- Return type:
str
- as3ninja.jinja2.filterfunctions.md5sum(data)[source]
Returns the hash as a hexdigest of
data
.data
is automatically converted to bytes, using backslashreplace for utf8 characters.- Return type:
str
- as3ninja.jinja2.filterfunctions.readfile(ctx, filepath, missing_ok=False)[source]
Reads a file and returns its content as ASCII. Expects the file to be a ASCII (not utf8!) encoded file.
missing_ok=True prevents raising an exception when the file is missing and will return an empty string (default: missing_ok=False).
- Return type:
str
- as3ninja.jinja2.filterfunctions.sha1sum(data)[source]
Returns the hash as a hexdigest of
data
.data
is automatically converted to bytes, using backslashreplace for utf8 characters.- Return type:
str
- as3ninja.jinja2.filterfunctions.sha256sum(data)[source]
Returns the hash as a hexdigest of
data
.data
is automatically converted to bytes, using backslashreplace for utf8 characters.- Return type:
str
- as3ninja.jinja2.filterfunctions.sha512sum(data)[source]
Returns the hash as a hexdigest of
data
.data
is automatically converted to bytes, using backslashreplace for utf8 characters.- Return type:
str
- as3ninja.jinja2.filterfunctions.to_list(data)[source]
Converts
data
to a list. Unlikelist
it will not convertstr
to a list of each character but wrap the wholestr
in a list. Does not convert existing lists.For example:
"virtualAddresses": {{ ninja.virtual_addresses | to_list | jsonify }},
If
ninja.virtual_addresses
is a list already it will not be nested, if it is a string, the string will be placed in a list.Another example using the python REPL:
( to_list("foo bar") == ['foo bar'] ) == True # strings ( to_list(["foo", "bar"]) == ['foo', 'bar'] ) == True # existing lists ( to_list(245) == [245] ) == True # integers
- Return type:
list
as3ninja.jinja2.filters module
This module holds Jinja2 filters for AS3 Ninja.
- as3ninja.jinja2.filters.ninjutsu(ctx, value, **kwargs)[source]
ninjutsu passes its input to jinja2 rendereing using the existing jinja2 environment and context. You can specify arbitary keyword arguments to pass additional variables to the renderer. This is important if you define variables within control structures, for example loops. These variables are not exported in the context and can therefore not be accessed within the jinja2 template passed to ninjutsu.
Example:
... {% for thisone in allofthem %} ... {# making thisone available to ninjutsu by passing # it as a keyword argument with the same name #} {{ somesource.content.with.jinja2 | ninjutsu(thisone=thisone) }} ... {% endfor %}
If
somesource.content.with.jinja2
usesmyvar
it would fail if we don’t specify the keyword argumentmyvar=myvar
, as it is not automatically exposed to the existing jinja2 context. An alternative are namespaces, just make sure the namespace is defined outside any of the control structures. Also note that the variable name will be an attribute of the namespace.... {% clipboard = namespace() %} {% for thisone in allofthem %} ... {% set clipboard.thisone = thisone %} ... {# thisone is now available to ninjutsu as clipboard.thisone #} {{ somesource.content.with.jinja2 | ninjutsu }} ... {% endfor %}
- Return type:
str
as3ninja.jinja2.functions module
This module holds Jinja2 functions for AS3 Ninja.
- class as3ninja.jinja2.functions.iterfiles(pattern, missing_ok=False)[source]
Bases:
object
iterates files, returns a tuple of all globbing matches and the file content as dict. Assumes the file content is either JSON or YAML.
iterfiles will ignore missing files if missing_ok=True is specified (default: False), otherwise will raise a FileNotFoundError exception.
as3ninja.jinja2.j2ninja module
J2Ninja collects jinja2 filters, functions an tests in a single class.
- class as3ninja.jinja2.j2ninja.J2Ninja[source]
Bases:
object
J2Ninja provides decorator methods to register jinja2 filters, functions and tests, which are available as class attributes (dict).
-
filters:
dict
= {'b64decode': <function b64decode>, 'b64encode': <function b64encode>, 'env': <function env>, 'hashfunction': <function hashfunction>, 'jsonify': <function jsonify>, 'md5sum': <function md5sum>, 'ninjutsu': <function ninjutsu>, 'readfile': <function readfile>, 'sha1sum': <function sha1sum>, 'sha256sum': <function sha256sum>, 'sha512sum': <function sha512sum>, 'to_list': <function to_list>, 'uuid': <function uuid>, 'vault': <function vault>}
-
functions:
dict
= {'VaultClient': <class 'as3ninja.vault.VaultClient'>, 'b64decode': <function b64decode>, 'b64encode': <function b64encode>, 'env': <function env>, 'hashfunction': <function hashfunction>, 'iterfiles': <class 'as3ninja.jinja2.functions.iterfiles'>, 'jsonify': <function jsonify>, 'md5sum': <function md5sum>, 'readfile': <function readfile>, 'sha1sum': <function sha1sum>, 'sha256sum': <function sha256sum>, 'sha512sum': <function sha512sum>, 'to_list': <function to_list>, 'uuid': <function uuid>, 'vault': <function vault>}
-
tests:
dict
= {}
-
filters:
as3ninja.jinja2.tests module
This module holds Jinja2 tests for AS3 Ninja.
Module contents
Jinja2 filters, functions and tests module for AS3 Ninja.
as3ninja.schema package
Submodules
as3ninja.schema.as3schema module
AS3 Schema Class module. Represents the AS3 JSON Schema as a python class.
- class as3ninja.schema.as3schema.AS3Schema(version='latest')[source]
Bases:
object
Creates a AS3Schema instance of specified version. The
validate()
method provides AS3 Declaration validation based on the AS3 JSON Schema.- Parameters:
version (
str
) – AS3 Schema version (Default value = “latest”)
- _SCHEMA_FILENAME_GLOB = '**/as3-schema-*.json'
- _SCHEMA_LOCAL_FSPATH = PosixPath('/home/docs/.as3ninja/f5-appsvcs-extension/schema')
- _SCHEMA_REF_URL_TEMPLATE = '/home/docs/.as3ninja/f5-appsvcs-extension/schema/{{version}}/as3-schema-{{version}}-*.json'
- __schemalist_sort_helper(value)
Private Method: A sort helper.
Sorts based on the schema version (converted to int).
- Return type:
int
- __version_sort_helper(value)
Private Method: A sort helper. converts value: str to int and removes “.”
- Parameters:
value (
str
) – str: A version str (example: “3.8.1”)- Return type:
int
- _build_ref_url(version)[source]
Private Method: _build_ref_url builds the absolute filesystem url to the AS3 Schema file for specified version.
- Parameters:
version (
str
) – The AS3 Schema version- Return type:
str
- _check_version(version)[source]
Private Method: _check_version checks if the specified version exists in available schemas. In case the specified schema version is not loaded, it will load the version. It converts “latest” to the actual version.
The checked version is returned as str.
- Return type:
str
-
_latest_version:
str
= ''
- _load_schema(version, force=False)[source]
Private Method: load schema file from disk for specified version.
force
parameter can be used to force load the schema file, even if it has been read already.- Return type:
None
- _ref_update(schema, _ref_url)[source]
Private Method: _ref_update performs an in-place update of relative $ref (starting with #) into absolute references by prepending _ref_url.
See: https://github.com/Julian/jsonschema/issues/570
- Return type:
None
- _schema_ref_update(version)[source]
Private Method: _schema_ref_update returns the AS3 Schema for specified version with updated references.
- Parameters:
version (
str
) – The AS3 Schema version- Return type:
dict
-
_schemas:
dict
= {}
-
_schemas_ref_updated:
dict
= {}
- _sort_schemas()[source]
Private Method: Sorts the schemas class attribute according to version
- Return type:
None
- _update_versions(versions)[source]
Private Method: Updates and sorts the versions class attribute
- Return type:
None
- static _validate_schema_version_format(version)[source]
Private Method: validates the format and minimum version.
- Parameters:
version (
str
) – str: AS3 Schema version- Return type:
None
- _validator(version)[source]
Creates jsonschema.Draft7Validator for specified AS3 schema version. Will check schema is valid and raise a jsonschema SchemaError otherwise. Memoizes the Draft7Validator instance for faster re-use.
- Return type:
None
-
_validators:
dict
= {}
-
_versions:
tuple
= ()
- property is_latest: bool
Property: returns bool(True) if this instance has the latest Schema version available. Returns False otherwise.
- property latest_version: str
Property: returns the latest AS3 schema version as str.
- property schema: dict
Property: returns the Schema of this AS3 Schema instance as dict.
- property schema_asjson: str
Property: returns the Schema as JSON of this AS3 Schema instance as a python str.
- property schemas: dict
Property: returns all known AS3 Schemas as dict.
- updateschemas(githubrepo='https://github.com/F5Networks/f5-appsvcs-extension', repodir='/home/docs/.as3ninja/f5-appsvcs-extension')[source]
Method: Fetches/Updates AS3 Schemas from the GitHub Repository.
- Parameters:
githubrepo (
str
) – str: Git/Github repository to fetch AS3 Schemas from (Default value = constant NINJASETTINGS.SCHEMA_GITHUB_REPO)repodir (
str
) – str: Target directory to clone to (Default value = constant NINJASETTINGS.SCHEMA_BASE_PATH)
- Return type:
None
- validate(declaration, version=None)[source]
Method: Validates a declaration against the AS3 Schema. Raises a AS3ValidationError on failure.
- Parameters:
declaration (
Union
[dict
,str
]) – Declaration to be validated against the AS3 Schema.version (
Optional
[str
]) – Allows to validate the declaration against the specified version instead of this AS3 Schema instance version. If set to “auto”, the version of the declaration is used.
- Return type:
None
- property version: str
Property: returns the Schema version of this AS3 Schema instance as str.
- property versions: tuple
Property: returns all versions available as a sorted tuple.
as3ninja.schema.formatcheckers module
AS3 Schema Format Checker for F5 specific formats.
- class as3ninja.schema.formatcheckers.AS3FormatChecker(*args, **kwargs)[source]
Bases:
FormatChecker
AS3FormatChecker subclasses jsonschema.FormatChecker to provide AS3 specific format checks.
- static _is_type(is_type, value)[source]
Helper function _is_type returns True when is_type(value) does not raise an exception, False otherwise
- Parameters:
is_type (
Any
) – The type to check againstvalue (
Any
) – Value to check
- Return type:
bool
- static _regex_match(regex, value)[source]
Helper function _regex_match matches a regular expression against the given value. Returns True when regex matches, False otherwise.
- Parameters:
regex (
str
) – The regular expression, for example:r'^[ -~]+$'
value (
str
) – Value to apply the regular expression to
- Return type:
bool
- property as3_schema_format_checkers: dict
Returns dict of AS3 formats: f5ip, f5ipv4, f5ipv6, f5label, f5long-id, f5remark, f5pointer, f5base64 Currently missing formats used in AS3:
date-time
uri
url
Module contents
AS3 Schema package.
Submodules
as3ninja.api module
AS3Ninja’s REST API
- class as3ninja.api.AS3Declare(**data)[source]
Bases:
BaseModel
Model for an inline AS3 Declaration
- _abc_impl = <_abc._abc_data object>
-
declaration_template:
str
-
template_configuration:
Union
[List
[dict
],dict
]
- class as3ninja.api.AS3DeclareGit(**data)[source]
Bases:
BaseModel
Model for an AS3 Declaration from a Git repository
- _abc_impl = <_abc._abc_data object>
-
branch:
Optional
[str
]
-
commit:
Optional
[str
]
-
declaration_template:
Optional
[str
]
-
depth:
int
-
repository:
str
-
template_configuration:
Union
[List
[Union
[dict
,str
]],dict
,str
,None
]
- class as3ninja.api.AS3ValidationResult(**data)[source]
Bases:
BaseModel
AS3 declaration Schema validation result
- _abc_impl = <_abc._abc_data object>
-
error:
Optional
[str
]
-
valid:
bool
- class as3ninja.api.Error(**data)[source]
Bases:
BaseModel
Generic Error Model
- _abc_impl = <_abc._abc_data object>
-
code:
int
-
message:
str
- class as3ninja.api.LatestVersion(**data)[source]
Bases:
BaseModel
AS3 /schema/latest_version response
- _abc_impl = <_abc._abc_data object>
-
latest_version:
str
- async as3ninja.api._schema_validate(declaration, version=Query(latest))[source]
Validate declaration in POST payload against AS3 Schema of
version
(Default: latest)
- async as3ninja.api.get_schema_schema_version(version=Query(latest))[source]
Returns AS3 Schema of
version
- async as3ninja.api.get_schema_versions()[source]
Returns array of version numbers for all known AS3 Schemas
- async as3ninja.api.post_declaration_git_transform(as3d)[source]
Transforms an AS3 declaration template, see
AS3DeclareGit
for details on the expected input. Returns the AS3 Declaration.
as3ninja.cli module
AS3 Ninja CLI module
as3ninja.declaration module
The AS3Declaration module. Represents an AS3 Declaration as a python class.
- class as3ninja.declaration.AS3Declaration(template_configuration, declaration_template=None, jinja2_searchpath='.')[source]
Bases:
object
Creates an AS3Declaration instance representing the AS3 declaration.
The AS3 declaration is created using the given template configuration, which can be either a dict or list of dicts. If a list is provided, the member dicts will be merged using
_dict_deep_update()
.Optionally a jinja2 declaration_template can be provided, otherwise it is read from the configuration. The template file reference is expected to be at as3ninja.declaration_template within the configuration. An explicitly specified declaration_template takes precedence over any included template.
- Parameters:
template_configuration (
Dict
) – AS3 Template Configuration asdict
orlist
declaration_template (
Optional
[str
]) – Optional Declaration Template asstr
(Default value = ````)jinja2_searchpath (
str
) – The jinja2 search path for the FileSystemLoader. Important for jinja2 includes. (Default value ="."
)
- _jinja2_render()[source]
Renders the declaration using jinja2. Raises relevant exceptions which need to be handled by the caller.
- Return type:
str
- _transform()[source]
Transforms the declaration_template using the template_configuration to an AS3 declaration. On error raises: :rtype:
None
AS3TemplateSyntaxError on jinja2 template syntax errors
AS3UndefinedError for undefined variables in the declaration template
AS3JSONDecodeError in case the rendered declaration is not valid JSON
- property declaration_template: str
Property contains the declaration template loaded or provided during instantiation
as3ninja.exceptions module
All AS3 Ninja exceptions.
- exception as3ninja.exceptions.AS3JSONDecodeError(message='', original_exception=None)[source]
Bases:
ValueError
Raised when the produced JSON cannot be decoded
- exception as3ninja.exceptions.AS3SchemaError(message='', original_exception=None)[source]
Bases:
SchemaError
Raised when AS3 Schema is erroneous, eg. does not adhere to jsonschema standards.
- exception as3ninja.exceptions.AS3SchemaVersionError[source]
Bases:
ValueError
AS3 Schema Version Error, version is likely invalid or unknown.
- exception as3ninja.exceptions.AS3TemplateConfigurationError[source]
Bases:
ValueError
Raised when a problem occurs during building the Template Configuration.
- exception as3ninja.exceptions.AS3TemplateSyntaxError(message, declaration_template, original_exception=None)[source]
Bases:
Exception
Raised to tell the user that there is a problem with the AS3 declaration template.
- exception as3ninja.exceptions.AS3UndefinedError(message, original_exception=None)[source]
Bases:
UndefinedError
Raised if a AS3 declaration template tries to operate on
Undefined
.
as3ninja.gitget module
Gitget provides a minimal interface to ‘git’ to clone a repository with a specific branch, tag or commit id.
- class as3ninja.gitget.Gitget(repository, depth=None, branch=None, commit=None, repodir=None, force=False)[source]
Bases:
object
Gitget context manager clones a git repository. Raises GitgetException on failure. Exports: info dict property with information about the cloned repository repodir str property with the filesystem path to the temporary directory Gitget creates a shall clone of the specified repository using the specified and optional depth. A branch can be selected, if not specified the git server default branch is used (usually master).
A specific commit id in long format can be selected, depth can be used to reach back into the past in case the commit id isn’t available through a shallow clone.
- _checkout_commit()[source]
Private Method: checks out specific commit id
Note: short ID (example: 2b54d17) is not allowed, must be the long commit id Note: The referenced commit id must be in the cloned repository or within a depth of 20
- static _datetime_format(epoch)[source]
Private Method: returns a human readable UTC format (%Y-%m-%dT%H:%M:%SZ) of the unix epoch
- Parameters:
epoch (
Union
[int
,str
]) – Unix epoch- Return type:
str
- _gitcmd = ('git', '-c', 'http.sslVerify=True', '-c', 'http.proxy=')
- _run_command(cmd)[source]
Private Method: runs a shell command and handles/raises exceptions based on the command return code
- Parameters:
cmd (
tuple
) – list of command + arguments- Return type:
str
- static _sh_quote(arg)[source]
Private Method: returns a shell escaped version of arg, where arg can by any type convertible to str. uses shlex.quote
- Parameters:
arg – Argument to pass to shlex.quote
- Return type:
str
- property info: dict
Property: returns dict with git log information
- property repodir: str
Property: returns the (temporary) directory of the repository
as3ninja.settings module
AS3 Ninja global configuration parameters.
- class as3ninja.settings.NinjaSettings(_env_file='<object object>', _env_file_encoding=None, _env_nested_delimiter=None, _secrets_dir=None, **values)[source]
Bases:
BaseSettings
AS3 Ninja Settings class.
Holds the default configuration for AS3 Ninja.
Reads from $CWD/as3ninja.settings.json if it exists, otherwise from $HOME/.as3ninja/as3ninja.settings.json. If none of the configuration files exist, it creates $HOME/.as3ninja/as3ninja.settings.json and writes the current configuration (default + settings overwritten by ENV vars).
Any setting can be overwritten using environment variables. The ENV variable has a prefix of AS3N_ + name of the setting. The environment variables take precedence over any setting in the configuration file.
- class Config[source]
Bases:
object
Configuration for NinjaSettings BaseSettings class
- case_sensitive = True
- env_prefix = 'AS3N_'
- extra = 'forbid'
-
GITGET_PROXY:
str
-
GITGET_SSL_VERIFY:
bool
-
GITGET_TIMEOUT:
int
-
SCHEMA_BASE_PATH:
str
-
SCHEMA_GITHUB_REPO:
str
-
VAULT_SSL_VERIFY:
bool
- _abc_impl = <_abc._abc_data object>
- class as3ninja.settings.NinjaSettingsLoader[source]
Bases:
object
The NinjaSettingsLoader class is an utility class which will return a callable instance which in fact returns an instance of NinjaSettings. NinjaSettingsLoader contains utility functions to detect the config file and the SCHEMA_BASE_PATH, it will also create the config file if it does not yet exist.
- AS3NINJA_CONFIGFILE_NAME = 'as3ninja.settings.json'
- AS3_SCHEMA_DIRECTORY = '/f5-appsvcs-extension'
- RUNTIME_CONFIG = ['SCHEMA_BASE_PATH']
- classmethod _detect_config_file()[source]
Detect if/where the AS3 Ninja config file (as3ninja.settings.json) is located.
First checks for existence of as3ninja.settings.json and uses this file if found. Alternatively Path.home()/.as3ninja/as3ninja.settings.json is used and created if it doesn’t exist.
- Return type:
Optional
[str
]
- classmethod _detect_schema_base_path()[source]
Detect where AS3 JSON Schema files are stored.
First checks for existence of Path.cwd()/f5-appsvcs-extension and uses this path if found. Alternatively Path.home()/.as3ninja/f5-appsvcs-extension is used and created if it doesn’t exist.
- Return type:
str
- _save_config()[source]
Saves the current settings as JSON to the configuration file in ~/.as3ninja/. It removes any RUNTIME_CONFIG keys before saving.
- Return type:
None
-
_settings:
NinjaSettings
= NinjaSettings(GITGET_TIMEOUT=120, GITGET_SSL_VERIFY=True, GITGET_PROXY='', SCHEMA_BASE_PATH='', SCHEMA_GITHUB_REPO='https://github.com/F5Networks/f5-appsvcs-extension', VAULT_SSL_VERIFY=True)
as3ninja.templateconfiguration module
The AS3TemplateConfiguration module allows to compose AS3 Template Configurations from YAML, JSON or dict(s).
- class as3ninja.templateconfiguration.AS3TemplateConfiguration(template_configuration=None, base_path='', overlay=None)[source]
Bases:
DictLike
The AS3TemplateConfiguration module. Allows to build an AS3 Template Configuration from YAML, JSON or dict. Creates a AS3TemplateConfiguration instance for use with AS3Declaration.
The Template Configuration can be created from one or more files or dicts. Globbing based on pathlib Path glob is supported to load multiple files. De-serialization for files is automatically performed, YAML and JSON is supported. If a file is included multiple times, it is only loaded once on first occurrence. AS3TemplateConfigurationError exception is raised when a file is not found or not readable.
Files can be included using the as3ninja.include
Union[str, List[str]]
namespace in every specified configuration file. Files included through this namespace will not be checked for as3ninja.include and therefore cannot include further files.The as3ninja.include namespace is updated with entries of all as3ninja.include entries, globbing will be expanded. This helps during troubleshooting.
If a list of inputs is provided, the input will be merged using
_dict_deep_update()
.If template_configuration is
None
, AS3TemplateConfiguration will look for the first default configuration file it finds in the current working directory (files are in order: ninja.json, ninja.yaml, ninja.yml).- Parameters:
Example usage:
from as3ninja.templateconfiguration import AS3TemplateConfiguration as3tc = AS3TemplateConfiguration([ {"inlineConfig": True}, "./config.yaml", "./config.json", "./includes/*.yaml" ]) as3tc.dict() as3tc.json() as3tc_dict = dict(as3tc)
- class TemplateConfigurationValidator(**data)[source]
Bases:
BaseModel
Data Model validation and de-serialization for as3ninja.include namespace.
- _abc_impl = <_abc._abc_data object>
-
template_configuration:
Union
[List
[Union
[dict
,str
]],dict
,str
]
- _deserialize_includes(includes, register=True)[source]
Iterates and expands over the list of includes and yields the deseriealized data.
- Parameters:
includes (
List
[str
]) – List of include filesregister (
bool
) – Register include file to avoid double inclusion (Default:True
)
- Return type:
Generator
- _dict_deep_update(dict_to_update, update)[source]
Similar to dict.update() but with full depth.
- Parameters:
dict_to_update (
Dict
) – dict to update (will be mutated)update (
Dict
) – dict: dict to use for updating dict_to_update
- Return type:
Dict
Example:
dict.update: { 'a': {'b':1, 'c':2} }.update({'a': {'d':3} }) -> { 'a': {'d':3} } _dict_deep_update: { 'a': {'b':1, 'c':2} } with _dict_deep_update({'a': {'d':3} }) -> { 'a': {'b':1, 'c':2, 'd':3} }
- _import_includes(defferred=False)[source]
Iterates the list of Template Configurations and imports all includes in order.
- Parameters:
defferred (
bool
) – Include defferred includes instead of user specified as3ninja.include
- _ninja_default_configfile()[source]
Identify first config file which exists:ninja.json, ninja.yaml or ninja.yml. Raise AS3TemplateConfigurationError on error.
- Return type:
str
- _path_glob(pattern)[source]
Path(self._base_path).glob(pattern) with support for an absolute pattern.
- Return type:
Generator
[Path
,None
,None
]
- _tidy_as3ninja_namespace()[source]
Tidy as3ninja. namespace in the configuration. Removes:
__deserialize_file
removes entire as3ninja namespace if empty
as3ninja.types module
AS3 Ninja types.
- class as3ninja.types.BaseF5IP[source]
Bases:
str
F5IP base class. Accepts IPv4 and IPv6 IP addresses in F5 notation.
- class as3ninja.types.BaseF5IPv4[source]
Bases:
BaseF5IP
F5IPv4 base class. Accepts IPv4 addresses in F5 notation.
- class as3ninja.types.BaseF5IPv6[source]
Bases:
BaseF5IP
F5IPv6 base class. Accepts IPv6 addresses in F5 notation.
- class as3ninja.types.F5IP(f5ip)[source]
Bases:
BaseModel
Accepts and validates IPv6 and IPv4 addresses in F5 notation.
- _abc_impl = <_abc._abc_data object>
-
addr:
str
-
mask:
Optional
[Any
]
-
rdid:
Optional
[Any
]
as3ninja.utils module
Various utils and helpes used by AS3 Ninja
- class as3ninja.utils.DictLike[source]
Bases:
object
Makes objects feel like a dict.
Implements required dunder and common methods used to access dict data.
-
_dict:
dict
= {}
-
_dict:
- exception as3ninja.utils.PathAccessError(exc, seg, path)[source]
Bases:
KeyError
,IndexError
,TypeError
An amalgamation of KeyError, IndexError, and TypeError, representing what can occur when looking up a path in a nested object.
- class as3ninja.utils.YamlConstructor[source]
Bases:
object
Organizes functions to implement a custom PyYAML constructor
- INCLUDE_TAG = '!include'
- classmethod _include_constructor(_, node)[source]
The PyYAML constructor for the INCLUDE_TAG (!include). This method should not be called directly, it is passed to PyYAML as a constructor function.
- Parameters:
node – The yaml node to be inspected
- Return type:
Union
[List
,Dict
]
- static _path_glob(value)[source]
A Path().glob() helper function, checks if value actually contains a globbing pattern and either returns value or the result of the globbing.
- Parameters:
value (
str
) – String to check for globbing pattern and, if pattern found, to feed to Path().glob()- Return type:
List
[str
]
- as3ninja.utils.deserialize(datasource)[source]
deserialize de-serializes JSON or YAML from a file to a python dict.
A ValueError exception is raised if JSON and YAML de-serialization fails. A FileNotFoundError is raised when an included file is not found.
- Parameters:
datasource (
str
) – The filename (including path) to deserialize- Return type:
Dict
- as3ninja.utils.dict_filter(dict_to_filter, filter=None)[source]
Filters a dict based on the provided filter.
dict_filter will walk the dict keys based on the filter and will return the value of the last key. If filter is empty, dict_to_filter will be returned.
Example: assert dict_filter({ ‘a’: { ‘b’: [1,2,3] } }, filter=”a.b”) == [1,2,3]
- Parameters:
dict_to_filter (
dict
) – Python dict to filterfilter (
Union
[tuple
,str
,None
]) – Filter to apply to the dict. Filter can be astr
(will be split on .) or atuple
.
- Return type:
Any
- as3ninja.utils.escape_split(string_to_split, seperator='.')[source]
Splits a string based on the provided seperator.
escape_split supports escaping the seperator by prepending a backslash.
- Parameters:
string_to_split (
str
) – String to splitseperator (
str
) – Seperator to use for splitting (Default: “.”)
- Return type:
tuple
as3ninja.vault module
HashiCorp Vault integration
- class as3ninja.vault.VaultClient(addr, token=None, verify=True)[source]
Bases:
object
Vault Client object, returns a hvac.v1.Client object.
- Parameters:
addr (
str
) – Vault Address (url, eg. https://myvault:8200/)token (
Optional
[str
]) – Vault Token to use for authenticationverify (
Union
[str
,bool
]) – If True Verify TLS Certificate of Vault (Default: True)
- Client()[source]
Returns hvac.client callable based on VaultClient() initialization parameters.
- Return type:
Client
- _defaultClient = None
- classmethod defaultClient(ctx)[source]
Returns a hvac.v1.Client based on system/environment settings.
This is method is not intended to be used directly.
First checks for existing authentication based on vault cli. If authenticated no further action is performed.
Then check the Jinja2 Context for the namespace
ninja.as3ninja.vault
and useaddr
,token
andssl_verify
to establish a Vault connection. For any of the above variables that doesn’t exist the respective environment variable will be used as a fallback:addr
=VAULT_ADDR
token
= VAULT_TOKENssl_verify
=VAULT_SKIP_VERIFY
If
VAULT_SKIP_VERIFY
does not existVAULT_SSL_VERIFY
from the AS3 Ninja configuration file (as3ninja.settings.json) is used.- Parameters:
ctx (
Context
) – Context: Jinja2 Context- Return type:
Client
- class as3ninja.vault.VaultSecret(*args, path: str, mount_point: str, engine: Union[str, VaultSecretsEngines] = VaultSecretsEngines.kv2, filter: Optional[str] = None, version: int = 0)[source]
Bases:
BaseModel
Vault Secret configuration BaseModel.
- Parameters:
path – The secret path. If mount_point is not specified the first path element is assumed to be the mount_point.
mount_point – The secrets engine path. Optional.
engine – The secrets engine. Optional.
filter – Optional Filter to select specific data from the secret, e.g. “data.privateKey”. Filter automatically prepends “data.” for kv2 to replicate the same behaviour for kv1 and kv2.
version – The version of the secret. Only relevant for KV2 Secrets Engine. Optional. Default: 0 (latest secret version)
- _abc_impl = <_abc._abc_data object>
- static _split_mount_point_path(path)[source]
Splits mount_point from path. The first path element is treated as the mount_point.
- Parameters:
path (
str
) – path parameter- Return type:
tuple
-
engine:
Union
[str
,VaultSecretsEngines
]
-
filter:
Optional
[str
]
-
mount_point:
str
-
path:
str
- classmethod validate_pathlike(value)[source]
Basic secrets path validation using pathlib.Path. This should work for most vault secrets paths.
-
version:
int
- class as3ninja.vault.VaultSecretsEngines(value)[source]
Bases:
Enum
Supported Vault Secret Engines
- default = 'kv2'
- kv = 'kv1'
- kv1 = 'kv1'
- kv2 = 'kv2'
Aliases
- as3ninja.vault.vault(ctx, secret, client=None, filter=None, version=None)[source]
Vault filter to retrieve a secret. The secret is returned as a dict.
- Parameters:
ctx (
Context
) – Context: Jinja2 Context (automatically provided by jinja2)secret (
Dict
) – secret configuration statement, automatically passed by “piping” to the vault filterclient (
Optional
[VaultClient
]) – Optional Vault clientfilter (
Optional
[str
]) – Optional Filter to select specific data from the secret, e.g. “data.privateKey”. Filter automatically prepends “data.” for kv2 to replicate the same behaviour for kv1 and kv2.version (
Optional
[int
]) – Optional secret version (overrides version provided by secret configuration statement)
- Return type:
Dict
Module contents
Top-level package for AS3 Ninja.
AS3 Ninja Settings
as3ninja.settings.json
holds the default configuration settings for AS3 Ninja.
AS3 Ninja looks for as3ninja.settings.json
in the following files and uses the first it finds:
$CWD/as3ninja.settings.json
$HOME/.as3ninja/as3ninja.settings.json
If none of the configuration files exist, it creates $HOME/.as3ninja/as3ninja.settings.json
and writes the current configuration (default + settings overwritten by ENV vars).
Any setting can be overwritten using environment variables. The ENV variable has to be prefixed by AS3N_
.
The environment variables take precedence over any setting in the configuration file.
For specific settings and its meaning check the source of as3ninja.settings.NinjaSettings
.
Contributing
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
You can contribute in many ways:
Types of Contributions
Report Bugs
Report bugs at https://github.com/simonkowallik/as3ninja/issues.
If you are reporting a bug, please include:
Your operating system name and version, python version and as3ninja version.
Any details about your local setup that might be helpful in troubleshooting.
Detailed steps to reproduce the bug.
Fix Bugs
Look through the GitHub issues for bugs. Anything tagged with “bug” and “help wanted” is open to whoever wants to implement it.
Implement Features
Look through the GitHub issues for features. Anything tagged with “enhancement” and “help wanted” is open to whoever wants to implement it.
Write Documentation
AS3 Ninja could always use more documentation, whether as part of the official AS3 Ninja docs, in docstrings, or even on the web in blog posts, articles, and such.
Submit Feedback
The best way to send feedback is to file an issue at https://github.com/simonkowallik/as3ninja/issues.
If you are proposing a feature:
Explain in detail how it would work.
Keep the scope as narrow as possible, to make it easier to implement.
Remember that this is a volunteer-driven project, and that contributions are welcome :)
Get Started!
Ready to contribute? Here’s how to set up as3ninja for local development.
Fork the as3ninja repo on GitHub.
Clone your fork locally:
$ git clone --branch edge git@github.com:your_name_here/as3ninja.git
Install your local copy into a virtualenv. Assuming you use poetry:
$ cd as3ninja/
$ poetry shell
$ poetry install
Create a branch for local development:
$ git checkout -b (bugfix|feature|enhancement)/name-of-your-bugfix-or-feature
Now you can make your changes locally.
When you’re done making changes, check that your changes comply to code formatting and pass the tests:
$ make lint
$ make code-format
$ make test
Commit your changes and push your branch to GitHub:
$ git add .
$ git commit
$ git push origin (bugfix|feature|enhancement)/name-of-your-bugfix-or-feature
Submit a pull request through GitHub.
Pull Request Guidelines
Before you submit a pull request, check that it meets these guidelines:
The pull request should include tests.
If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring and update the relevant documentation.
The pull request should work for Python 3.8 and up. Check https://github.com/simonkowallik/as3ninja/actions and make sure that the tests pass for all supported Python versions.
Support
AS3 Ninja is an open source project started by a single individual and released under the ISC license.
By using AS3 Ninja you confirm to understand, agree and adhere to the license.
Warning
There is no commercial/technical support nor any other form of support for AS3 Ninja. It comes without SLAs or any form of warranty.
You are welcome to contribute, for example by opening a GitHub issue to report a problem.
Credits
Contributors
None yet. Why not be the first?
History
For a detailed history please head to GitHub releases.
Open Doc Tasks
This is the list of outstanding documentation tasks.
Todo
more cli examples
(The original entry is located in /home/docs/checkouts/readthedocs.org/user_builds/as3ninja/checkouts/edge/docs/usage.rst, line 79.)
Todo
Postman collection for API calls
(The original entry is located in /home/docs/checkouts/readthedocs.org/user_builds/as3ninja/checkouts/edge/docs/usage.rst, line 121.)
Todo
Update usage as a module
(The original entry is located in /home/docs/checkouts/readthedocs.org/user_builds/as3ninja/checkouts/edge/docs/usage.rst, line 182.)