Skip to content

Starting With Kapitan

Kapitan is a tool generating configuration files (and more), handling extension of configs and overriding of values. It is great when you need to generate complex configurations or config files that slightly differ by being used in slightly different scenarios. For example staging vs production, or client A vs client B. It is supporting multiple templating approaches, my favourite being jsonnet, which is the only we will cover here. It can be used to generated Kubernetes configurations, but not only. I’m also using it to generate Docker Swarm configurations.

As much as I like the project, I still find it hard to get started when I didn’t interact with it for some time. Maybe it’s me, but I find the documentation hard to digest and understand. This post is mainly a reminder for me in the future, but it should help anyone getting started.

We’ll run kapitan with docker, so ensure you have docker available, and create this alias:

alias kapitan='docker run -t --rm -v $(pwd):/src:delegated kapicorp/kapitan'

Be sure to use single quote around the docker command (otherwise pwd will be called at alias definition time and be static), and you should be able to call the command kapitan:

$ kapitan --version
0.30.0

Kapitan expects a specific hierarchy of directories, which you can create with kapitan init:

$ kapitan init --directory kapitan
Populated kapitan with:
kapitan
kapitan/templates
kapitan/templates/docs
	 my_readme.md
kapitan/components
kapitan/components/other_component
	 __init__.py

There’s more than the output says:

$ tree kapitan/
kapitan/
├── components
│   ├── my_component
│   │   └── my_component.jsonnet
│   └── other_component
│       ├── __init__.py
│       └── __pycache__
│           └── __init__.cpython-37.pyc
├── inventory
│   ├── classes
│   │   ├── common.yml
│   │   └── my_component.yml
│   └── targets
│       └── my_target.yml
└── templates
    ├── docs
    │   └── my_readme.md
    └── scripts
        └── my_script.sh

10 directories, 8 files

Though I find it confusing that we have a class called my_component as well as a component my_component, we’ll use these names. In your projects, names will probably be more explicit. We’ll concentrate on the inventory’s classes and targets, as well as the jsonnet components. Extending this understanding to templates and python component is left as an exercise. Those 3 are interrelated, and the best description I found is this:

  • a target includes the classes it wants to be used to generate files. As such, the target defines which files will be generated.
  • the class defines its output files based on the input file in components which are jsonnet files generating json content.
  • in the jsonnet components, the top level key of the resulting json is the filename to generate.
  • the parameters values are mainly set in the class file but can be set in the target too.

Now we know what they are, let’s take a look at these different files.

Targets

$ cat kapitan/inventory/targets/my_target.yml
classes:
  - common
  - my_component

parameters:
  target_name: my_target

It includes the classes common and my_component. It also sets the value parameters.target_name.

Classes

The class common.yml

$ cat kapitan/inventory/classes/common.yml
parameters:
  kapitan:
    vars:
      target: ${target_name}

This simply set the target name under the key parameters.kapitan.vars.target. It does not define files to be generated. This makes sense as common is usually included in all targets.

The class my_component.yml

$ cat kapitan/inventory/classes/my_component.yml
parameters:
  your_component:
    some_parameter: true

  kapitan:

    compile:
      - output_path: jsonnet_output
        input_type: jsonnet
        output_type: yaml
        input_paths:
          - components/my_component/my_component.jsonnet

      - output_path: scripts
        input_type: jinja2
        input_paths:
          - templates/scripts

      - output_path: .
        output_type: yaml
        input_type: jinja2
        input_paths:
          - templates/docs

      - output_path: kadet_output
        input_type: kadet
        output_type: yaml
        input_paths:
          - components/other_component/

It also defines a value available under the key parameters.your_component.some_parameter. It additionally defines the generation of output under parameters.kapitan.compile. The output_path is the path under the directory compiled created when compiling a target. The input_path is the location of generator. We’ll only look at the first compilation output, which is of input_type jsonnet and is generating its output based on the result of the compilation of the jsonnet file components/my_component/my_component.jsonnet.

Components

As mentioned, we will only look at the jsonnet component:

$ cat kapitan/components/my_component/my_component.jsonnet
local kap = import "lib/kapitan.libjsonnet";
local inventory = kap.inventory();

{
  example: {
    test: inventory.parameters.your_component.some_parameter
  }
}

It starts by importing the kapitan jsonnet library, which subsequently gives access to the inventory, and more specifically to the parameters dictionary we saw being defined in the classes files. The generated JSON has one top level field named example. The top level fields are the names of the files to be generated, meaning we will have a file named example.yaml (because the class using this file set the output_type to yaml). The content of that generated file is the object under the example field. In this case it is an object with one field named test which has its value extracted from the repository under the key parameters.your_component.some_parameter, which we saw has the value true as set in the class my_component.yml.

Compilation

We can now compile all targets:

$ kapitan compile
Rendered inventory (0.02s)
Compiled my_target (0.06s)

and look at the files generated under compiled:

$ tree compiled/
compiled/
└── my_target
    ├── jsonnet_output
    │   └── example.yaml
    ├── kadet_output
    │   ├── labels.yaml
    │   └── my_kadet_component.yaml
    ├── my_readme.md
    └── scripts
        └── my_script.sh

4 directories, 5 files

the directory compiled has been created, with one subdirectory for each target compiled. Under my_target' we have the directory jsonnet_outputas specified asoutput_pathin the class. There is one generated file namedexample.yaml` as expected, and its content is

$ cat kapitan/compiled/my_target/jsonnet_output/example.yaml
test: true

Important to note is that you have access to the whole inventory, eg inventory.target_name which is set in our target, or inventory.parameters.kapitan.compile which lists all outputs to be generated. Also note that the list of files can be dynamic (jsonnet’s object comprehension).

This is only an intro to kapitan, but I hope it will help you get started with this very powerful tool!