This repository is now in read-only mode. If you're looking for a CUE based Kubernetes package manager, we recommend you to use timoni. We've moved our effort into timoni, helping its maintenance to avoid spreading efforts between very similar solutions. We believe timoni now covers everything cuebe was intended to be.
Kubernetes release manager powered by CUE.
Writting your Kubernetes manifests in CUE can drastically increase maintainability and reliability of your deployment workflow. Things that was previously reserved to source code can now be used for the whole lifecycle of an application. Things like strong typing, reducing boilerplate, unit tests, etc...
Cuebe helps you deploying such resources. It is the glue between the configuration and the k8s cluster. Akin what can kubectl do with YAML and JSON.
Cuebe builds a Context and collects every Kubernetes manifests in it. It then uses server-side apply to deploy those resources.
Download the latest release from GitHub.
You will need Go 1.17 or later installed.
go install github.com/loft-orbital/cuebe/cmd/cuebe@latest
This will install cuebe
in your $GOBIN
folder.
A Manifest is a specification of a Kubernetes object in CUE format.
A Manifest specifies the desired state of an object that Kubernetes will maintain when you apply it.
A CUE value is considered as a Manifest if it has both string properties apiVersion
and kind
.
foo: "bar"
namespace: {
apiVersion: "v1"
kind: "Namespace"
}
nested: configmap: {
apiVersion: "v1"
kind: "ConfigMap"
}
Here both namespace
and nested.configmap
are considered as Manifest by Cuebe.
Whereas foo just serves during build phase.
Every Manifest must eventually resolves to a JSON serializable object (concrete only values).
Other values can be non-concrete, as Cuebe will not try to render them in a concrete format (YAML, JSON).
Since Cuebe can collect any manifests in your Build, it's up to you to define boundaries. You can use it to deploy single Manifest, a bunch of unrelated Manifests or use the Instance concept.
An instance is a group of Manifests belonging to the same application. It's a way to group multiple manifests under the same lifecycle. If you're used to Helm, you can see a Cuebe Instance as a Helm Release.
A Manifest needs the "instance.cuebe.loftorbital.com/name": <instance-name>
label (in metadata.labels
) to be a member of the Instance.
It's up to you to set this label on your Manifests.
When deploying an Instance for the first time, Cuebe will create a Instance object. It's a cluster-scoped custom resource which will keep track of Instance members. It means an Instance must be unique to a cluster. You cannot have multiple Instances with the same name in a single cluster.
When deleting an Instance, even outside of Cuebe (e.g. with kubectl
) it will automatically deletes subresources.
The way those resources are deleted can be managed with the "instance.cuebe.loftorbital.com/deletion-policy"
annotation.
When this annotation is set to abandon
, the object will not be actually deleted, but its link to the instance removed (we call that an orphan Manifest).
When this annotations is not set, the object will be normally deleted.
A Build is the action of building a Context to Manifests, grouping them into Instances when required.
With cuebe
cli you can apply or export a Build.
You can tweak the Build phase by using a special set of CUE attributes. CUE introduced the concept of attibutes to associate metadata information with value. Cuebe leverage this mechanism to enhance your Build. In addition to native attibutes, Cuebe introduces the following (exhaustive) list:
The @ignore
attribute marks a value to be ignored by Cuebe.
It means Cuebe will not dive in this value.
Cuebe will hence ignore any Manifest under this value.
This can speed up the build process, avoiding unnecessary recursions.
@ignore()
foo: "bar"
namespace: {
apiVersion: "v1"
kind: "Namespace"
}
nested: {
// This manifest will be ignored
configmap: {
apiVersion: "v1"
kind: "ConfigMap"
}
} @ignore()
Although CUE itslef offers a way to inject value at runtime, it lacks some features. Especially with secrets injection where it can become hard to maintain and scale, and does not fit well in a GitOps flow. For that reason Cuebe introduces a way to inject values at runtime.
The @inject
attribute allows surgical injection of external values in your Cuebe release.
We recommend using this attribute with parsimony, as cue
itself will ignore it, making your Build hard to debug outside Cuebe.
One of our current use case is to inject sops encrypted values in the Build.
It allow us to keep a GitOps flow (no runtime config, everything commited) without leaking secrets.
Cuebe only supports local file injection as for now.
@inject(type=<type>, src=<src> [,path=<path>])
-
type: Injection type. Currerntly only supports
file
-
src: Injection source. For file injection, the path has to be relative to the Build Context. Supports cue, json or yaml plain or sops-enccrypted structured format, or any text file format when injecting unstructured (c.f. path).
-
path: [Optional] Path to extract the value from. For file injection, when the path is not provided Cuebe treats the file as unstructured and does a plain text injection.
injection.yaml
namespace:
name: potato
plaintext.md
# Best sauces
Ketchup Mayo
main.cue
namespace: {
apiVersion: "v1"
kind: "Namespace"
metadata: {
name: string @inject(type=file, src=injection.yaml, path=$.namespace.name)
}
}
configmap: {
apiVersion: "v1"
kind: "ConfigMap"
metadata: name: "sauces"
data: {
"README.md": string @inject(src=plaintext.md, type=file)
}
}
A Context is basically a filesystem that Cuebe uses to Build manifests and instances.
Currently Cuebe only supports local contexts (single file or directory).
Archives (tar.gz
) and remote Contexts (object storage, https endpoint, etc..) are in the pipe.
When sending multiple Contexts to Cuebe, they will be merged before build.
Think rsync -a /ContextA/ /ContextB/
.
With Cuebe cli you can pack a Context to upload it and reuse it during apply or export as soon as packs context (so-called cube) are supported.
You will find some examples in the example folder.
- Better injection system
- Release lifecycle management
- Remote Contexts
- Remote Injection
- More examples