Skip to content

Kadet

This introduces a new experimental input type called Kadet.

Kadet is essentially a Python module offering a set of classes and functions to define objects which will compile to JSON or YAML. A complete example is available in examples/kubernetes/components/nginx.

Author: @ramaro

Overview

BaseObj

BaseObj implements the basic object implementation that compiles into JSON or YAML. Setting keys in self.root means they will be in the compiled output. Keys can be set as an hierarchy of attributes (courtesy of addict) The self.body() method is reserved for setting self.root on instantiation:

The example below:

class MyApp(BaseObj):
 def body(self):
   self.root.name = "myapp"
   self.root.inner.foo = "bar"
   self.root.list = [1, 2, 3]

compiles into:

---
name: myapp
inner:
  foo: bar
list:
  - 1
  - 2
  - 3

The self.new() method can be used to define a basic constructor. self.need() checks if a key is set and errors if it isn't (with an optional custom error message). kwargs that are passed onto a new instance of BaseObj are always accessible via self.kwargs In this example, MyApp needs name and foo to be passed as kwargs.

class MyApp(BaseObj):
 def new(self):
   self.need("name")
   self.need("foo", msg="please provide a value for foo")

 def body(self):
   self.root.name = self.kwargs.name
   self.root.inner.foo = self.kwargs.foo
   self.root.list = [1, 2, 3]

obj = MyApp(name="myapp", foo="bar")

Setting a skeleton

Defining a large body with Python can be quite hard and repetitive to read and write. The self.update_root() method allows importing a YAML/JSON file to set the skeleton of self.root.

MyApp's skeleton can be set instead like this:

#skel.yml
---
name: myapp
inner:
  foo: bar
list:
  - 1
  - 2
  - 3
class MyApp(BaseObj):
 def new(self):
   self.need("name")
   self.need("foo", msg="please provide a value for foo")
   self.update_root("path/to/skel.yml")

Extending a skeleton'd MyApp is possible just by implementing self.body():

class MyApp(BaseObj):
 def new(self):
   self.need("name")
   self.need("foo", msg="please provide a value for foo")
   self.update_root("path/to/skel.yml")

 def body(self):
   self.set_replicas()
   self.root.metadata.labels = {"app": "mylabel"}

def set_replicas(self):
   self.root.spec.replicas = 5

Inheritance

Python inheritance will work as expected:

class MyOtherApp(MyApp):
  def new(self):
    super().new()  # MyApp's new()
    self.need("size")

def body(self):
   super().body()  #  we want to extend MyApp's body
   self.root.size = self.kwargs.size
   del self.root.list  # get rid of "list"

obj = MyOtherApp(name="otherapp1", foo="bar2", size=3)

compiles to:

---
name: otherapp1
inner:
  foo: bar2
replicas: 5
size: 3

Components

A component in Kadet is a python module that must implement a main() function returning an instance ofBaseObj. The inventory is also available via the inventory() function.

For example, a tinyapp component:

# components/tinyapp/__init__.py
from kapitan.inputs.kadet import BaseOBj, inventory
inv = inventory() # returns inventory for target being compiled

class TinyApp(BaseObj):
  def body(self):
    self.root.foo = "bar"
    self.root.replicas = inv.parameters.tinyapp.replicas

def main():
  obj = BaseOb()
  obj.root.deployment = TinyApp() # will compile into deployment.yml
  return obj

An inventory class must be created for tinyapp:

# inventory/classes/components/tinyapp.yml

parameters:
  tinyapp:
    replicas: 1
  kapitan:
    compile:
    - output_path: manifests
      input_type: kadet
      output_type: yaml
      input_paths:
        - components/tinyapp

Common components

A library in --search-paths (which now defaults to . and lib/) can also be a module that kadet components import. It is loaded using the load_from_search_paths():

kubelib = load_from_search_paths("kubelib") # lib/kubelib/__init__.py

def main():
  obj = BaseObj()
  obj.root.example_app_deployment = kubelib.Deployment(name="example-app")
  return obj