How it works
BundleBee is a light Java package manager for Kubernetes applications. This page explains how to get started with it.
Overall architecture
One of the BundleBee goals is to inherit from the well known and supported Apache Maven infrastructure. In other words, the goal is to be able to reuse Maven registries (central, custom nexus, artifactory, …) to store application descriptors. It also leverage the well know java packaging (jar or zip) to ensure a strong storage reliability in these systems.
One direct benefit is that it becomes very easy to have an immutable system once deployed and therefore a better tracability of what is deployed.
Overall, BundleBee will manipulate what we call alveolus
which are basically just a set of descriptors.
An alveolus
is a zip or a jar which is structured this way:
.root
`- bundlebee (1)
|- manifest.json (2)
`- kubernetes (3)
|- my-descriptor-1.yaml
`- my-descriptor-2.yaml
-
A
bundlebee
folder contains all the descriptors -
A
manifest.json
contains the list of application or library this alveolus provides -
A
kubenetes
folder contains the list of descriptors used by alveoli
Manifest
Manifest is the central point of the system. It provides the unique identifier of your recipe (application/library) and what to do to set it up.
Here is its configuration specification:
Manifest Descriptor
BundleBee manifest descriptor.
Name | JSON Name | Type | Description |
---|---|---|---|
interpolateAlveoli |
|
bool |
Enables to consider all alveoli have their |
|
array of object |
List of described applications/libraries. |
|
|
array of object |
Ignored linting rule names when using |
|
|
array of object |
List of files referenced as other manifests. They are merged with this (main) manifest by appending requirements and alveoli. It is relative to this manifest location. Important: it is only about the same module references, external references are dependencies in an alveoli. It enables to split a huge |
|
|
array of object |
Pre manifest execution checks (bundlebee version typically). Avoids to install using a bundlebee version not compatible with the alveoli. Can be fully omitted. |
Manifest.alveoli
List of described applications/libraries.
Name | JSON Name | Type | Description |
---|---|---|---|
chainDependencies |
|
bool |
Should dependencies be installed one after the other or in parallel (default). It is useful when you install a namespace for example which must be awaited before next dependencies are installed. |
interpolateDescriptors |
|
bool |
Enables to consider all descriptors have their |
name |
|
string |
Name of the alveolus (recipe). It must be unique accross the whole classpath. Using maven style identifier, it is recommended to name it |
version |
|
string |
If name does not follow |
|
array of object |
Dependencies of this alveolus. It is a way to import transitively a set of descriptors. |
|
|
array of object |
List of descriptors to install for this alveolus. This is required even if an empty array. |
|
|
array of object |
List of descriptors to ignore for this alveolus (generally coming from dependencies). |
|
|
array of object |
Patches on descriptors. It enables to inject configuration in descriptors by patching (using JSON-Patch or plain interpolation with |
|
placeholders |
|
object |
Local placeholders for this particular alveolus and its dependencies. It is primarly intended to be able to create a template alveolus and inject the placeholders inline. |
Alveolus.dependencies
Dependencies of this alveolus. It is a way to import transitively a set of descriptors.
Name | JSON Name | Type | Description |
---|---|---|---|
location |
|
string |
Where to find the alveolus. Note it will ensure the jar is present on the local maven repository. |
name |
|
string |
Alveolus name. |
|
object |
Conditions to include this dependency. Enables for example to have an environment variable enabling part of the stack (ex: |
Alveolus.descriptors
List of descriptors to install for this alveolus. This is required even if an empty array.
Name | JSON Name | Type | Description |
---|---|---|---|
await |
|
bool |
If set to |
awaitOnDelete |
|
bool |
On delete we rarely want to check the resource exists before but in these rare case you can set this toggle to |
interpolate |
|
bool |
If set to |
location |
|
string |
Optional, if coming from another manifest, the dependency to download to get the alveolus. |
name |
|
string |
Name of the descriptor to install. For kubernetes descriptors you can omit the |
type |
|
string |
Type of this descriptor. For now only |
|
array of object |
Test to do on created/destroyed resources, enables to synchronize and await kubernetes actually starts some resource. For |
|
|
object |
Conditions to include this descriptor. |
Descriptor.includeIf
Conditions to include this descriptor.
Name | JSON Name | Type | Description |
---|---|---|---|
operator |
|
string |
Operator to combine the conditions. Potential values: |
|
array of object |
List of condition to match according |
Conditions.conditions
List of condition to match according operator
.
Name | JSON Name | Type | Description |
---|---|---|---|
key |
|
string |
Expected key. If empty/null condition is ignored. If read value is null it defaults to an empty string. |
negate |
|
bool |
Should the condition be reversed (ie "not in this case"). |
type |
|
string |
Type of condition. Potential values: |
value |
|
string |
Expected value. If empty/null, |
Descriptor.awaitConditions
Test to do on created/destroyed resources, enables to synchronize and await kubernetes actually starts some resource. For apply
and delete
commands, descriptorAwaitTimeout
is still applied. Note that if you use multiple array entries for the same command it will be evaluated with an AND
.
Name | JSON Name | Type | Description |
---|---|---|---|
command |
|
string |
Command to apply these conditions to, if not set it will be applied on |
operator |
|
string |
Operator to combine the conditions. Potential values: |
|
array of object |
List of condition to match according |
AwaitConditions.conditions
List of condition to match according operator
.
Name | JSON Name | Type | Description |
---|---|---|---|
conditionType |
|
string |
When condition type is |
operatorType |
|
string |
The operation to evaluate if this condition is true or not. (for |
pointer |
|
string |
JSON Pointer to read from the resource. It can for example be on |
type |
|
string |
Type of condition. Potential values: |
value |
|
string |
When condition type is |
Alveolus.excludedDescriptors
List of descriptors to ignore for this alveolus (generally coming from dependencies).
Name | JSON Name | Type | Description |
---|---|---|---|
location |
|
string |
The container of the descriptor (maven coordinates generally). |
name |
|
string |
Name of the descriptor (as declared, ie potentially without the extension). |
Alveolus.patches
Patches on descriptors. It enables to inject configuration in descriptors by patching (using JSON-Patch or plain interpolation with ${key}
values) their JSON representation. The key is the descriptor name and each time the descriptor is found it will be applied.
Name | JSON Name | Type | Description |
---|---|---|---|
descriptorName |
|
string |
The descriptor to patch. It can be any descriptor, including transitive ones. It can be |
interpolate |
|
bool |
If set to |
patch |
|
array of object |
JSON-Patch to apply on the JSON representation of the descriptor. It enables to inject configuration in descriptors for example, or changing some name/application. |
|
object |
Conditions to include this patch. Enables for example to have an environment variable enabling part of the stack (ex: |
Manifest.ignoredLintingRules
Ignored linting rule names when using lint
command.
Name | JSON Name | Type | Description |
---|---|---|---|
name |
|
string |
Name of the rule to ignore. |
Manifest.references
List of files referenced as other manifests. They are merged with this (main) manifest by appending requirements and alveoli. It is relative to this manifest location. Important: it is only about the same module references, external references are dependencies in an alveoli. It enables to split a huge manifest.json
for an easier maintenance.
Name | JSON Name | Type | Description |
---|---|---|---|
path |
|
string |
Relative or absolute - starting by a |
Manifest.requirements
Pre manifest execution checks (bundlebee version typically). Avoids to install using a bundlebee version not compatible with the alveoli. Can be fully omitted.
Name | JSON Name | Type | Description |
---|---|---|---|
maxBundlebeeVersion |
|
string |
Minimum bundlebee version, use `*`to replace any digit in a segment. Note that snapshot is ignored in the comparison for convenience. It is an inclusive comparison. |
minBundlebeeVersion |
|
string |
Minimum bundlebee version, use |
forbiddenVersions |
|
array of string |
List of forbidden version (due to a bug or equivalent). Here too snapshot suffix is ignored. |
Tip
|
manifest content can be wrapped in a bundlebee key (you wrap the whole content) which enables you to add other metadata in the manifest and still reuse BundleeBee JSON-Schema for the validation (by composition).
|
JSON-Schema
If you use JSON-Schema facilities in your preferred editor, here is the raw JSON-Schema for the manifest descriptor:
{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest",
"type":"object",
"title":"Manifest Descriptor",
"description":"BundleBee manifest descriptor.",
"definitions":{
"io_yupiik_bundlebee_core_descriptor_Manifest_Conditions":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_Conditions",
"type":"object",
"title":"Descriptor.includeIf",
"description":"Conditions to include this descriptor.",
"properties":{
"conditions":{
"type":"array",
"title":"Conditions.conditions",
"description":"List of condition to match according `operator`.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_Condition",
"type":"object",
"properties":{
"key":{
"type":"string",
"title":"Condition.key",
"description":"Expected key. If empty/null condition is ignored. If read value is null it defaults to an empty string."
},
"negate":{
"type":"boolean",
"title":"Condition.negate",
"description":"Should the condition be reversed (ie \"not in this case\")."
},
"type":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_ConditionType",
"type":"string",
"title":"Condition.type",
"description":"Type of condition.",
"enum":[
"ENV",
"SYSTEM_PROPERTY"
],
"nullable":true
},
"value":{
"type":"string",
"title":"Condition.value",
"description":"Expected value. If empty/null, `true` is assumed. Note that empty is allowed."
}
}
}
},
"operator":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_ConditionOperator",
"type":"string",
"title":"Conditions.operator",
"description":"Operator to combine the conditions.",
"enum":[
"ANY",
"ALL"
],
"nullable":true
}
}
}
},
"properties":{
"alveoli":{
"type":"array",
"title":"Manifest.alveoli",
"description":"List of described applications/libraries.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_Alveolus",
"type":"object",
"properties":{
"chainDependencies":{
"type":"boolean",
"title":"Alveolus.chainDependencies",
"description":"Should dependencies be installed one after the other or in parallel (default). It is useful when you install a namespace for example which must be awaited before next dependencies are installed."
},
"dependencies":{
"type":"array",
"title":"Alveolus.dependencies",
"description":"Dependencies of this alveolus. It is a way to import transitively a set of descriptors.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_AlveolusDependency",
"type":"object",
"properties":{
"includeIf":{
"$ref":"#/definitions/io_yupiik_bundlebee_core_descriptor_Manifest_Conditions",
"type":"object",
"title":"AlveolusDependency.includeIf",
"description":"Conditions to include this dependency. Enables for example to have an environment variable enabling part of the stack (ex: `MONITORING=true`)"
},
"location":{
"type":"string",
"title":"AlveolusDependency.location",
"description":"Where to find the alveolus. Note it will ensure the jar is present on the local maven repository."
},
"name":{
"type":"string",
"title":"AlveolusDependency.name",
"description":"Alveolus name."
}
}
}
},
"descriptors":{
"type":"array",
"title":"Alveolus.descriptors",
"description":"List of descriptors to install for this alveolus. This is required even if an empty array.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_Descriptor",
"type":"object",
"properties":{
"await":{
"type":"boolean",
"title":"Descriptor.await",
"description":"If set to `true`, apply/delete commands will await the actual creation of the resource (`GET /x` returns a HTTP 200) before continuing to process next resources. It is useful for namespaces for example to ensure applications can be created in the newly created namespace. It avoids to run and rerun apply command in practise. For more advanced tests, use `awaitConditions`."
},
"awaitConditions":{
"type":"array",
"title":"Descriptor.awaitConditions",
"description":"Test to do on created/destroyed resources, enables to synchronize and await kubernetes actually starts some resource. For `apply` and `delete` commands, `descriptorAwaitTimeout` is still applied. Note that if you use multiple array entries for the same command it will be evaluated with an `AND`.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_AwaitConditions",
"type":"object",
"properties":{
"command":{
"type":"string",
"title":"AwaitConditions.command",
"description":"Command to apply these conditions to, if not set it will be applied on `apply` command only. Note that for now only `apply` and `delete` commands are supported, others will be ignored."
},
"conditions":{
"type":"array",
"title":"AwaitConditions.conditions",
"description":"List of condition to match according `operator`.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_AwaitCondition",
"type":"object",
"properties":{
"conditionType":{
"type":"string",
"title":"AwaitCondition.conditionType",
"description":"When condition type is `STATUS_CONDITION` it is the expected type of the condition. This is ignored when condition type is `JSON_POINTER`."
},
"operatorType":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_JsonPointerOperator",
"type":"string",
"title":"AwaitCondition.operatorType",
"description":"The operation to evaluate if this condition is true or not. (for `type=JSON_POINTER`).",
"enum":[
"EXISTS",
"MISSING",
"EQUALS",
"NOT_EQUALS",
"EQUALS_IGNORE_CASE",
"NOT_EQUALS_IGNORE_CASE",
"CONTAINS"
],
"nullable":true
},
"pointer":{
"type":"string",
"title":"AwaitCondition.pointer",
"description":"JSON Pointer to read from the resource. It can for example be on `/status/phase` to await a namespace creation. (for `type=JSON_POINTER`)."
},
"type":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_AwaitConditionType",
"type":"string",
"title":"AwaitCondition.type",
"description":"Type of condition.",
"enum":[
"JSON_POINTER",
"STATUS_CONDITION"
],
"nullable":true
},
"value":{
"type":"string",
"title":"AwaitCondition.value",
"description":"When condition type is `JSON_POINTER` and `operatorType` needs a value (`EQUALS` for example), the related value. It can be `Active` if you test namespace `/status/phase` for example. When condition type is `STATUS_CONDITION` it is the expected status."
}
}
}
},
"operator":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_ConditionOperator",
"type":"string",
"title":"AwaitConditions.operator",
"description":"Operator to combine the conditions.",
"enum":[
"ANY",
"ALL"
],
"nullable":true
}
}
}
},
"awaitOnDelete":{
"type":"boolean",
"title":"Descriptor.awaitOnDelete",
"description":"On delete we rarely want to check the resource exists before but in these rare case you can set this toggle to `true`."
},
"includeIf":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_Conditions",
"type":"object",
"title":"Descriptor.includeIf",
"description":"Conditions to include this descriptor.",
"properties":{
"conditions":{
"type":"array",
"title":"Conditions.conditions",
"description":"List of condition to match according `operator`.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_Condition",
"type":"object",
"properties":{
"key":{
"type":"string",
"title":"Condition.key",
"description":"Expected key. If empty/null condition is ignored. If read value is null it defaults to an empty string."
},
"negate":{
"type":"boolean",
"title":"Condition.negate",
"description":"Should the condition be reversed (ie \"not in this case\")."
},
"type":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_ConditionType",
"type":"string",
"title":"Condition.type",
"description":"Type of condition.",
"enum":[
"ENV",
"SYSTEM_PROPERTY"
],
"nullable":true
},
"value":{
"type":"string",
"title":"Condition.value",
"description":"Expected value. If empty/null, `true` is assumed. Note that empty is allowed."
}
}
}
},
"operator":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_ConditionOperator",
"type":"string",
"title":"Conditions.operator",
"description":"Operator to combine the conditions.",
"enum":[
"ANY",
"ALL"
],
"nullable":true
}
}
},
"interpolate":{
"type":"boolean",
"title":"Descriptor.interpolate",
"description":"If set to `true`, it will interpolate the descriptor just before applying it - i.e. after it had been patched if needed. You can use `--<config-key> <value>` to inject bindings set as `{{config-key:-default value}}`. If not set, `interpolateDescriptors` flag from the alveolus will be used.",
"nullable":true
},
"location":{
"type":"string",
"title":"Descriptor.location",
"description":"Optional, if coming from another manifest, the dependency to download to get the alveolus."
},
"name":{
"type":"string",
"title":"Descriptor.name",
"description":"Name of the descriptor to install. For kubernetes descriptors you can omit the `.yaml` extension."
},
"type":{
"type":"string",
"title":"Descriptor.type",
"description":"Type of this descriptor. For now only `kubernetes` is supported. It also defines in which folder under `bundlebee` the descriptor(s) are looked for from its name."
}
}
}
},
"excludedDescriptors":{
"type":"array",
"title":"Alveolus.excludedDescriptors",
"description":"List of descriptors to ignore for this alveolus (generally coming from dependencies).",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_DescriptorRef",
"type":"object",
"properties":{
"location":{
"type":"string",
"title":"DescriptorRef.location",
"description":"The container of the descriptor (maven coordinates generally)."
},
"name":{
"type":"string",
"title":"DescriptorRef.name",
"description":"Name of the descriptor (as declared, ie potentially without the extension)."
}
}
}
},
"interpolateDescriptors":{
"type":"boolean",
"title":"Alveolus.interpolateDescriptors",
"description":"Enables to consider all descriptors have their `interpolate` descriptor set to `true`, you can still set it to `false` if you want to disable it for one. If not set, `interpolateAlveoli` flag from the manifest.",
"nullable":true
},
"name":{
"type":"string",
"title":"Alveolus.name",
"description":"Name of the alveolus (recipe). It must be unique accross the whole classpath. Using maven style identifier, it is recommended to name it `<groupId>:<artifactId>:<version>` using maven filtering but it is not enforced."
},
"patches":{
"type":"array",
"title":"Alveolus.patches",
"description":"Patches on descriptors. It enables to inject configuration in descriptors by patching (using JSON-Patch or plain interpolation with `${key}` values) their JSON representation. The key is the descriptor name and each time the descriptor is found it will be applied.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_Patch",
"type":"object",
"properties":{
"descriptorName":{
"type":"string",
"title":"Patch.descriptorName",
"description":"The descriptor to patch. It can be any descriptor, including transitive ones. It can be `*` to patch all descriptors (`/metadata/label/app` for example) or `regex:<java pattern>` to match descriptor names with a regex."
},
"includeIf":{
"$ref":"#/definitions/io_yupiik_bundlebee_core_descriptor_Manifest_Conditions",
"type":"object",
"title":"Patch.includeIf",
"description":"Conditions to include this patch. Enables for example to have an environment variable enabling part of the stack (ex: `MONITORING=true`)"
},
"interpolate":{
"type":"boolean",
"title":"Patch.interpolate",
"description":"If set to `true`, it will interpolate the patch from the execution configuration which means you can use `--<config-key> <value>` to inject bindings too. An interesting interpolation is the ability to extract the ip/host of the host machine (`minikube ip` equivalent) using the kubeconfig file. Syntax is the following one: `{{kubeconfig.cluster.minikube.ip}}` or more generally `{{kubeconfig.cluster.<cluster name>.ip}}`. You can also await for some secret with this syntax `{{kubernetes.<namespace>.serviceaccount.<account name>.secrets.<secret name prefix>.data.<entry name>[.<timeout in seconds, default to 2mn>]}}`. This is particular useful to access freshly created service account tokens for example."
},
"patch":{
"type":"array",
"title":"Patch.patch",
"description":"JSON-Patch to apply on the JSON representation of the descriptor. It enables to inject configuration in descriptors for example, or changing some name/application.",
"items":{
"type":"object",
"properties":{
}
}
}
}
}
},
"placeholders":{
"type":"object",
"title":"Alveolus.placeholders",
"description":"Local placeholders for this particular alveolus and its dependencies. It is primarly intended to be able to create a template alveolus and inject the placeholders inline.",
"properties":{
}
},
"version":{
"type":"string",
"title":"Alveolus.version",
"description":"If name does not follow `<groupId>:<artifactId>:<version>` naming (i.e. version can't be extracted from the name) then you can specify the version there. Note that if set, this is used in priority (explicit versus deduced)."
}
}
}
},
"ignoredLintingRules":{
"type":"array",
"title":"Manifest.ignoredLintingRules",
"description":"Ignored linting rule names when using `lint` command.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_IgnoredLintingRule",
"type":"object",
"properties":{
"name":{
"type":"string",
"title":"IgnoredLintingRule.name",
"description":"Name of the rule to ignore."
}
}
}
},
"interpolateAlveoli":{
"type":"boolean",
"title":"Manifest.interpolateAlveoli",
"description":"Enables to consider all alveoli have their `interpolateDescriptors` descriptor set to `true`, you can still set it to `false` if you want to disable it for one.",
"nullable":true
},
"references":{
"type":"array",
"title":"Manifest.references",
"description":"List of files referenced as other manifests. They are merged with this (main) manifest by *appending* _requirements_ and _alveoli_. It is relative to this manifest location. Important: it is only about the same module references, external references are dependencies in an alveoli. It enables to split a huge `manifest.json` for an easier maintenance.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_ManifestReference",
"type":"object",
"properties":{
"path":{
"type":"string",
"title":"ManifestReference.path",
"description":"Relative or absolute - starting by a `/` - location (referenced to the base directory of `manifest.json`). For example `my-manifest.json` will resolve to `/path/to/bundlebee/my-manifest.json` in a folder and `/bundlebee/my-manifest.json` in a jar. Important: for resources (jar/classpath), the classloader is used so ensure your name is unique accross your classpath (we recommend you to prefix it with the module name, ex :`/bundlebee/my-module.sub-manifest.json` or use a dedicated subfolder (`/bundlebee/my-module/sub.json`)."
}
}
}
},
"requirements":{
"type":"array",
"title":"Manifest.requirements",
"description":"Pre manifest execution checks (bundlebee version typically). Avoids to install using a bundlebee version not compatible with the alveoli. Can be fully omitted.",
"items":{
"$id":"io_yupiik_bundlebee_core_descriptor_Manifest_Requirement",
"type":"object",
"properties":{
"forbiddenVersions":{
"type":"array",
"title":"Requirement.forbiddenVersions",
"description":"List of forbidden version (due to a bug or equivalent). Here too snapshot suffix is ignored. `*` is usable there too to replace any digit in a segment (ex: `1.*.*`). Note that `1.*` would *NOT* match `1.*.*`, version are always 3 segments.",
"items":{
"type":"string"
}
},
"maxBundlebeeVersion":{
"type":"string",
"title":"Requirement.maxBundlebeeVersion",
"description":"Minimum bundlebee version, use `*`to replace any digit in a segment. Note that snapshot is ignored in the comparison for convenience. It is an inclusive comparison."
},
"minBundlebeeVersion":{
"type":"string",
"title":"Requirement.minBundlebeeVersion",
"description":"Minimum bundlebee version, use `*` to replace any digit in a segment. Note that snapshot is ignored in the comparison for convenience. It is an inclusive comparison."
}
}
}
}
}
}
Tip
|
you can also use last version from this link. |
Deployment and Dependency Management
A manifest can reference descriptors in other alveoli, it just requires to reference them to let bundlebee find them.
There are mainly two ways to deploy an alveolus:
-
Classpath mode: you put all your zip/jar in the classpath. This mode requires there is no conflict between descriptor names (which is the recommended practise) and it will never look for external descriptors. It is typically a recommended mode to avoid network I/O except with the Kubernetes cluster.
-
Fetch mode: in this mode, a missing descriptor in the classpath will look for
descriptor.location
and lookup the jar/zip referenced by this location (generally a maven coordinate in the formgroupId:artifactId:version
). It will first use your local Maven repository but can download the jar/zip if missing locally. Then the jar/zip is read and descriptors are looked up from there. This mode is nicer when depending on a lot of external alveoli but requires I/O to grab the dependencies.
Customize descriptors
Indeed, you can bundle all your stack and hardcode your configuration or create one alveolus per environment. This is one option which works well and enable to version its configuration.
However, for testing purposes, it is also neat to be able to patch descriptors on the fly.
For that you can use descriptor.patches
in the manifest which will transitively enable to patch the descriptors - even the ones you don’t own - using JSON-Patch and interpolations.
Here are some examples.
{
"alveoli": [
{
"name": "com.company.alveoli:my-app:${project.version}",
"descriptors": [
{
"name": "com.company.alveoli_my-app.configmap"
},
// ...
],
"patches": [ (1)
"descriptorName": "com.company.other_alveolus.configmap", (2)
"interpolate": true, (3)
"patch": [ (4)
{
"op": "replace",
"path": "data",
"value": {
"my.config.value": "{{my.arg:-defaultValue}}" (5)
}
}
]
]
}
]
}
-
We add to the alveolus a list of patches to apply on some descriptors
-
We reference the descriptor to patch
-
We enable interpolation from Microprofile Config (which include CLI args)
-
We define the JSON-Patch to use (optional, interpolation will also interpolate the descriptor if designed to be interpolated)
-
We use an interpolation in the configuration in the JSON-Patch
replace
operation which replaces the data of the referencedConfigMap
Tip
|
this mecanism is very useful for ConfigMap descriptors but also PersistenceVolume since you can now patch `/spec/hostPath `easily too.
|
Deploy a dependency (or descriptor) conditionally
In some case it can be neat to be able to not deploy part of the stack. It is typically the case when you wrote a monitoring-full alveolus (ie with some optional services functionally). In such a case you can condition dependencies and descriptor deployment:
{
"alveoli": [
{
"name": "com.company.alveoli:my-app:${project.version}",
"dependencies": [
{
"name": "io.yupiik.alveoli:monitoring-alveolus:${bundlebee.version}",
"location": "io.yupiik.alveoli:monitoring-alveolus:${bundlebee.version}",
"includeIf": {
"conditions": [{
"key": "MONITORING",
"value": "true"
}]
}
}
]
}
]
}
Tip
|
used on descriptors and using a complete set of conditions for descriptors you can handle environments this way (ENV=preprod for descriptor preprod.configmap for example).
|
Filter manifest at command time
In some case it can be needed to evaluate some values of the manifest when executing the command.
Even if not recommended it can be done using {{xxx}}
placeholders - read from environment variables and system properties.
{
"alveoli": [
{
"name": "com.company.alveoli:my-app:{{project.version}}",
"dependencies": [
{
"name": "io.yupiik.alveoli:monitoring-alveolus:{{bundlebee.version}}",
"location": "io.yupiik.alveoli:monitoring-alveolus:{{bundlebee.version}}"
}
]
}
]
}
With this manifest you can value project.version
and bundlebee.version
using the command line:
bundlebee \
[your command and options] \
--project.version 1.2.1 \
--bundlebee.version 1.0.1
More placeholders
Placeholders can use some keywords to get some particular values:
-
alveolus.name
: name of the alveolus the descriptor comes from, -
descriptor.name
: name of the descriptor, -
bundlebee-strip:<value>
: strips the provided value, -
bundlebee-strip-leading:<value>
: strips the provided value at the beginning, -
bundlebee-strip-trailing:<value>
: strips the provided value at the end, -
bundlebee-indent:<indent size>:<value>
: indents a value with the provided space size, it is generally combined with another interpolation (file ones in particular) as value, ex{{bundlebee-indent:8:{{bundlebee-inline-file:myfile.txt}}}}
, -
bundlebee-inline-file:<file path>
: load the file content as value, -
bundlebee-base64-file:<file path>
: converts the file in base64 (useful to writedata:xxxx,<base64>
values for ex keeping the raw file in the filesystem, very helpful for images), -
bundlebee-base64-decode-file:<file path>
: decode the file from a base64 content, -
bundlebee-base64:<text>
: encodes in base64 the text, -
bundlebee-base64-decode:<text>
: decode from base64 to text the valuetext
, -
bundlebee-digest:BASE64|HEX,<algorithm>,<text>
: computes the digest of the text encoded in base64 or hexa format (useful to read files likeConfigMap
orSecret
and inject their digest value in aDeployment
annotations to force its reload for example), -
bundlebee-quote-escaped-inline-file:<file path>
: load the file content as a quoted value, -
bundlebee-json-inline-file:<file path>
: load the file content as a JSON string value - without quotes, -
bundlebee-json-string:content
: escapes a string to be a JSON string (useful when you inject withbundlebee-json-inline-file
a string in another string like in JSONConfigMap
in a JSON configuration), -
bundlebee-maven-server-username:<server id>
: extract from your mavensettings.xml
a server username (see configuration for a custom settings.xml location), -
bundlebee-maven-server-password:<server id>
: extract from your mavensettings.xml
a server password (potentially deciphered), -
bundlebee-decipher
: use Maven ciphering logic (AES/CBC/PKCS5Padding
) to read a clear value. Syntax is as follow{{bundlebee-decipher:$masterKey,$cipheredValue}}
withmasterKey
another placeholder name which contains the master key andcipheredValue
the value to decipher. Indeed we recommend to reference the master key with another shared placeholder if you use a single one ({{bundlebee-decipher:{{myMasyterKey}},$cipheredValue}}
). Last, since the value will be surrounded by{
and}
, we tolerate spaces before/after to avoid any confusion with the mustable like syntax we use for placeholders:{{bundlebee-decipher:{{my.master.key}}, {…base64….} }}
, -
bundlebee-kubernetes-namespace
: the Kubernetes namespace defined in the HttpKubeClient, -
kubeconfig.cluster.<cluster name>.ip
: extract cluster IP from your kubeconfig, -
timestamp
: current time in milliseconds (since epoch), -
timestampSec
: current time in seconds (since epoch), -
now
:OffsetDateTime.now()
value, -
date:<pattern>
: formatOffsetDateTime.now()
value using the provided pattern, -
nowUTC
:OffsetDateTime.now()
value with UTCZoneId
, -
kubernetes.<namespace>.serviceaccount.<account name>.secrets.<secret name prefix>.data.<entry name>[.<timeout in seconds>]
: secret value looked up through Kubernetes API. -
jsr223:<script path as file or resource or inline script>
: executes a JSR223 script to get the result, assumes that the script engine implementation is available in the JVM (not by default). To find the engine it uses the file extension or a comment in the file (for inline script) containingbundlebee.language: <lang>
. The script contains the two functionslookupByName
andlookupByType
which can lookup a CDI bean to reuse BundleBee utilities (ex:const bean = lookupByType.apply(getType('io.yupiik.bundlebee.core.kube.KubeClient'))
). If no language is found it defaults onjs
. -
bundlebee-directory-json-key-value-pairs:/path/to/dir
: creates a JSON object from a directory. The path can be either a directory path of a directory path with a glob pattern at the end (/path/to/dir/*.txt
for example). The keys are the filenames (simple) and__
are converted to/
. The values are the content of the files, filtered. Note it only works with exploded folders, not jar resources as of today. -
bundlebee-directory-json-key-value-pairs-content:/path/to/dir
: same asbundlebee-directory-json-key-value-pairs
but dropping enclosing brackets to be able to embed the content in an existing object (likeannotations
). Ex:{{bundlebee-directory-json-key-value-pairs-content:resources/app/annotations/*.txt}}
.
If none of these placeholders match, then Microprofile Config is used to lookup the value (so system properties, environment variables and BundleBee args are used).
Tip
|
file placeholders can also read a resource from the classpath if the path does not match any existing file. |
Use Handlebars instead of plain placeholders
Important
|
this feature is still experimental and value interpolation can be refined. |
Sometimes it can be more convenient to use handlebars language to templatize the descriptors (not the manifest) rather than placeholders. It is the case when you have some logic depending some placeholder or value.
Important
|
it is also generally the case your template becomes complex so before jumping into handlebars templating please think if you should split your template in multiple imported files or just multiple files. |
By replacing the .json
or .yaml
extension of the descriptor in kubernetes/
(sub)folder(s) by .hb
or .handlebars
you can use Yupiik fusion-handlebars
templating running on java >= 17 and adding the dependency to the classpath (not available yet in native mode).
Then the descriptor, if interpolated
is set to true
in the manifest (as usual) will be interpolating using Handlerbars engine.
The main difference is that it gives you conditional blocks.
You can still use the plain placeholder interpolation since we try to keep a compatibility layer and default functions with some corner cases but most will work smoothly.
Tip
|
we still recommend to use JSON format instead of YAML since it is harder to break even using handlebars. |
Finally you can use alveolus.name
, descriptor.name
which will be correctly interpolated.
Windows and CLI
On windows, the usage of the Java mode is recommended and the most reliable, however, if you want to speed up a bit the execution you can run the native bundlebee executable in a docker image.
Tip
|
ensure to test this solution on your machine, on our test machine gain can be x2 but it depends the docker setup too. |
Once done you would be able to replace bundlebee
by docker run bundlebee -v $HOME/.kube/config:/kube/config -e KUBECONFIG=/kube/config --network host
(use an alias ;)).
Here are the step to use this solution:
-
Download bundlebee native binary, for that go on https://repo.maven.apache.org/maven2/io/yupiik/bundlebee-core/, select the version you want and download the file ending with
Linux-amd64
, -
Create this
Dockerfile
file next to the binary you just downloaded and renamedbundlebee
:FROM ubuntu:20.10 as builder RUN apt-get update && apt-get install -y zlib1g libgcc-s1 libstdc++6 FROM scratch ENV LD_LIBRARY_PATH /lib COPY --from=builder /lib/x86_64-linux-gnu/libc.so.6 /lib/libc.so.6 COPY --from=builder /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib/libgcc_s.so.1 COPY --from=builder /lib/x86_64-linux-gnu/libstdc++.so.6.0.28 /lib/libstdc++.so.6 COPY --from=builder /lib/x86_64-linux-gnu/libpthread.so.0 /lib/libpthread.so.0 COPY --from=builder /lib/x86_64-linux-gnu/libdl.so.2 /lib/libdl.so.2 COPY --from=builder /lib/x86_64-linux-gnu/libm.so.6 /lib/libm.so.6 COPY --from=builder /lib/x86_64-linux-gnu/libz.so.1 /lib/libz.so.1 COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib/ld-linux COPY bundlebee-core /bin/bundlebee-core CMD ["/lib/ld-linux", "/bin/bundlebee-core"]
-
Run
docker build -t bundlebee .
in the folder you putbundlebee
binary and previousDockerfile
Important
|
this dockerfile works as of today because binaries are build on Ubuntu 20.10, you can need to adjust a bit libraries if this changes - this is why Java mode is simpler. |
Linting
Linting is quite specific so it has its own documentation.