The Omegaconf inventory backend
Overview
With version 0.33.0 we are introducing a new inventory backend as an alternative to reclass.
Warning
OmegaConf is currently in experimental mode. If you encounter unexpected errors or bugs, please let us know and create an issue.
Installation
The omegaconf Python package is an optional dependency of Kapitan.
You can install it as follows:
pip install kapitan[omegaconf]
Usage
To use the omegaconf inventory backend, you need to pass --inventory-backend=omegaconf on the command line.
If you want to permanently switch to the omegaconf inventory backend, you can select the inventory backend in the .kapitan config file:
global:
inventory-backend: omegaconf
Differences to reclass
Here are all of the differences to using reclass, new features and some instructions, how to migrate your inventory.
Supported:
- compose-node-name
- key overwrite prefix '
~' - interpolations
- relative class names
- init class
- nested interpolations
- escaped interpolations
Not (yet) supported
- exports
- inventory queries
- interpolation to yaml-keys containing '
.' (the delimiter itself)
Syntax changes
OmegaConf uses the default yaml-path notation using dots (.) as key delimiter.
New Functionalities
All of OmegaConf's native functionalities are supported.
General
- relative interpolation
- list accessing
- mandatory values
- Resolvers and Custom Resolvers
Resolvers
Resolvers are the main benefits of using OmegaConf. You can define any behavior with a python function that gets executed with the given input parameters.
We provide some basic resolvers:
- OmegaConf
oc.env: access a environment variableoc.select: provide a default value for an interpolationoc.dict.keys: get the keys of a dictionary object as a listoc.dict.values: get the values of dictionary object as a list- Utilities
key: get the name of the nodes keyfullkey: get the full name of the keyparentkey: get the name of the nodes parent keyrelpath: takes an absolute path and convert it to relativetag: creates an escaped interpolation- Casting and Merging
dict: cast a dict inside a list into the actual dictlist: put a dict inside a list with that dictmerge: merge objects
Usage
Using a resolver is as simple as an interpolation: yamlkey: ${resolver:input}
Custom Resolver
You can write your own resolvers and are able to achieve any behavior you want.
In your inventory you have to create a file resolvers.py.
You should define a function pass_resolvers() that returns a dictionary with the resolvers name and the respective function pointer.
Now you can start writing Python functions with your custom resolvers.
Example
# inventory/resolvers.py
def concat(input1, input2):
return input1 + input2
def add_ten(input):
assert isinstance(input, int)
return input + 10
def default_value():
return "DEFAULT"
def split_dots(input: str):
return input.split(".")
# required function
def pass_resolvers():
return {"concat": concat, "plus_ten": add_ten, "default": default_value, "split": split_dots}
If we render a file the result would be:
string: ${concat:Hello, World} # --> Hello World
int: ${plus_ten:90} # --> 100
default: ${default:} # --> DEFAULT
list: ${split:hello.world} # --> yaml list [hello, world]
Access the feature
To access the feature you have to use a kapitan version >=0.33.0.
If this is your first time running you have to specify --migrate to adapt to OmegaConfs syntax.
Danger
Please backup your inventory, if you're running --migrate or make sure you are able to revert the changes using source control.
Also check your inventory if it contains some yaml errors like duplicate keys or wrong yaml types. The command will not change anything if some errors occur.
The migration consists of the following steps:
* replacing the delimiter ':' with '.' in interpolations
* replacing meta interpolations '_reclass_' to '_kapitan_'
* replacing escaped interpolations \${content} to resolver ${tag:content}
Examples
One important usecase with this is the definition of default values and overwriting them with specific target/component values.
# inventory/classes/templates/deployment.yml
parameters:
# define default values using the 'relpath' resolver
deployment:
namespace: ${target_name}
component_name: \${parentkey:} # workaround: retrieves the component's name in this context
labels:
app.kubernetes.io/name: ${relpath:deployment.namespace} # gets resolved relatively
image: ??? # OmegaConf mandatory value (has to be set in target)
pull_policy: Always
image_pull_secrets:
- name: default-secret
service_port: 8080 # default value
service:
type: ClusterIP
selector:
app: ${target_name}
ports:
http:
service_port: ${relpath:deployment.service_port} # allows us to overwrite this in another key
# inventory/targets/example.yml
classes:
- templates.deployment
parameters:
target_name: ${_kapitan_.name.short}
components:
# merge each component with a deployment
backend: ${merge:${deployment}, ${backend}}
keycloak: ${merge:${deployment}, ${keycloak}}
keycloak-copy: ${merge:${keycloak}, ${keycloak-copy}} # merge with another component to specify even more
# components config (would be in their own classes)
# backend config
backend:
image: backend:latest
# keycloak config
keycloak:
namespace: example1
image: keycloak:latest
env:
[...]
# keycloak-copy config (inherits env and namespace from keycloak)
keycloak-copy:
namespace: example2
This would generate the following components definition:
components:
backend:
component_name: deployment
image: backend:latest
image_pull_secrets:
- name: default-secret
labels:
app.kubernetes.io/name: example
namespace: example
ports:
http:
service_port: 8080
pull_policy: Always
service:
selector:
app: example
type: ClusterIP
service_port: 8080
keycloak:
component_name: deployment
image: keycloak:latest
image_pull_secrets:
- name: default-secret
labels:
app.kubernetes.io/name: example1
namespace: example1
ports:
http:
service_port: 8080
pull_policy: Always
service:
selector:
app: example
type: ClusterIP
service_port: 8080
keycloak-copy:
component_name: deployment
image: keycloak:latest
image_pull_secrets:
- name: default-secret
labels:
app.kubernetes.io/name: example2
namespace: example2
ports:
http:
service_port: 8080
pull_policy: Always
service:
selector:
app: example
type: ClusterIP
service_port: 8080