The Mixer API
How to make the most of your MIxer subclasses
Mixers are your templating orchestration layer. Inside a mixer, you load various types of dict-yielding things, like YAML/ERB files, Helm charts, or other mixers, then manipulate their output if need be, and submit their final output.
This page is about the Kerbi::Mixer
which is a class. Find the complete documentation here.
When you subclass a Kerbi::Mixer
, you have to call mix
and push
if you want to do anything useful. The mix
method is what the engine invokes at runtime. Inside your mix
method, you call push
to say "include this dict or these dicts in the final output".
class TrivialMixer < Kerbi::Mixer
def mix
push { hello: "Mister Kerbi" }
push [{ hola: "Señor Kerbi", bonjour: "Monsieur Kerbi" }]
end
end
Kerbi::Globals.mixers << TrivialMixer
Some Observations:
mix()
is the method for all your mixer's logic.
push(dicts: Hash | Array<Hash>)
adds the dict(s) you give it to the mixer's final output.
Attributes: values
and release_name
values
and release_name
Mixers are instantiated with two important attributes: values
and release_name
.
values: Hash
is an immutable dict containing the values compiled by Kerbi at start time (gathered from values.yaml
, extra values files, and inline --set x=y
assignments).
release_name: String
holds the release_name
value, which is the second argument you pass in the CLI in the template
command.
Accessing values
and release_name
is straightforward:
class HelloMixer < Kerbi::Mixer
def mix
push { x: values[:x] }
push { x: release_name }
end
end
Kerbi::Globals.mixers << HelloMixer
It is recommended you use the release_name
value for the namespace
in you Kubernetes resource descriptors, however it is entirely up to you.
The Dict-Loading Methods
This is the meat of Mixers. The following functions let you load different types of files, and get the result back as a normalized, sanitized list of dicts (i.e Array<Hash>
).
class MeddlingMixer < Kerbi::Mixer
def have_fun!
extracted = file("yaml")
puts "I'm just an #{extracted.class} of #{extracted[0].class}!"
puts "Containing: #{just_dicts}"
end
end
Testing:
$ kerbi console
irb(kerbi):001:0> mixer = MeddlingMixer.new
irb(kerbi):002:0> mixer.have_fun!
=> I'm just an Array of Hash!
=> Containing: [{key: value, more_key: more_value}, {key: value}]
The core dict-loading method, called by every other dict loading method (file()
etc...). Has two purposes:
Sanitizing its inputs, turning a single
Hash
, into anArray<Hash>
, transforming non-symbol keys into symbols, raising errors if its inputs are not Hash-like, etc...Performing post processing according to the options it receives, covered below.
Use it anytime you want to push dicts that did not come directly from another dict loading method (file()
etc...). Not doing so and pushing dicts directly can lead to errors.
class DictMixer < Kerbi::Mixer
def mix
push dict({"weird_key" => "fixed!"})
end
end
The file()
method
file()
methodLoads one YAML, JSON, or ERB file containing one or many descriptors that can be turned into dicts.
You can omit the file name extensions, e.g file-one.json
can be referred to as "file-one"
. In general, an extension-less name will trigger a search for:
<name>.yaml
<name>.json
<name>.yaml.erb
<name>.json.erb
class FileMixer < Kerbi::Mixer
def mix
push file("file-one")
push file("dir/file-two")
end
end
Kerbi::Globals.mixers << FileMixer
The dir()
method
dir()
methodLoads all YAML, JSON, or ERB files in a given directory. Scans for the following file extensions:
*.yaml
*.json
*.yaml.erb
*.json.erb
class DirMixer < Kerbi::Mixer
def mix
push dir("foo-dir")
end
end
Kerbi::Globals.mixers << DirMixer
The mixer()
method
mixer()
methodInstantiates the given mixer, runs it, and returns its output as an Array<Hash>
.
require_relative 'other_mixer'
module MultiMixing
class MixerOne < Kerbi::Mixer
def mix
push(mixer_says: "MixerOne #{values}")
end
end
class OuterMixer < Kerbi::Mixer
def mix
push mixer_says: "OuterMixer #{values}"
push mixer(MultiMixing::MixerOne)
push mixer(MultiMixing::MixerTwo, values: values[:x])
end
end
end
Kerbi::Globals.mixers << MultiMixing::OuterMixer
Kerbi::Globals.revision = "1.0.0"
Observations:
require_relative
imports the other mixer in plain Ruby, no magic**
mixer(MultiMixing::MixerOne)
** takes a class, not an instancevalues: values[:x]
lets us customize the values the inner mixer gets
The helm_chart()
method
helm_chart()
methodInvokes Helm's template command, i.e helm template [NAME] [CHART]
and returns the output as a standard Array<Hash>
.
Here is an example using JetStack's cert-manager chart
class HelmExample < Kerbi::Mixer
def mix
push cert_manager_resources
end
def cert_manager_resources
helm_chart(
'jetstack/cert-manager',
release: release_name,
values: values.dig(:cert_manager)
)
end
end
Kerbi::Globals.mixers << HelmExample
Your local Helm installation must be ready to accept this command, meaning:
The
repo
must be available to Helm (see helm repo add)Your helm executable must be available (see Global Configuration)
Post Processing
The patched_with()
method
patched_with()
methodAs a convenience, you can have dicts patched onto the dicts that you emit. This is a common pattern for things like annotations and labels on Kubernetes resources.
Only affects dicts processed by dict-loading methods, i.e callers of dict()
, so file()
, dir()
, helm_chart()
, and mixer()
. If you push()
a raw Hash
or Array<Hash>
, it will not get patched. You can also escape patching in dict-loaders with no_patch: true.
class SimplePatch < Kerbi::Mixer
def mix
datas = { x: { y: "z" } }
patch = { x: { y2: "y2" } }
patched_with patch do
push dict(datas)
push datas
end
end
end
Output:
$ kerbi template demo .
x:
y: "z"
y2: "y2"
--
x:
y: "z"
Avoid patching your patches!
You can have nested patches, but make sure that the inner patch itself is not patched with the outer patch. To do this, pass no_patch: true
to any dict-loading method you use to load the patch contents:
class SimplePatch < Kerbi::Mixer
def mix
patched_with(x: {new_y: "new-z"}) do
patched_with file("inner-patch", no_patch: true) do
push business: "as_usual"
end
end
end
end
Filtering Resource Dicts
You can filter the outputs any dict loader method seen above by using the only
and except
options. Each accepts an Array<Hash>
where each Hash
should follow the schema:
kind: String | nil # compared to <resource>.kind
name: String | nil # compared to <resource>.metadata.name
class FilteringExample < Kerbi::Mixer
ONLY = [{kind: "PersistentVolume.*"}]
EXCEPT = [{name: "unwanted"}]
def mix
push file('resources', only: ONLY, except: EXCEPT)
end
end
Kerbi::Globals.mixers << FilteringExample
Last updated