Skip to content
This repository has been archived by the owner on Jan 4, 2024. It is now read-only.

loft-orbital/cuebe

Repository files navigation

Cuebe

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.

Why?

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.

How?

Cuebe builds a Context and collects every Kubernetes manifests in it. It then uses server-side apply to deploy those resources.

Install

Release builds

Download the latest release from GitHub.

Install from Source

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.

Concepts

Manifest

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.

Instance

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.

Build

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:

@ignore

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.

Syntax
@ignore()
Example
foo: "bar"
namespace: {
  apiVersion: "v1"
  kind:       "Namespace"
}
nested: {
  // This manifest will be ignored
  configmap: {
    apiVersion: "v1"
    kind:       "ConfigMap"
  }
} @ignore()

@inject

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.

Syntax
@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.

Example

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)
	}
}

Context

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.

Examples

You will find some examples in the example folder.

Roadmap

  • Better injection system
  • Release lifecycle management
  • Remote Contexts
  • Remote Injection
  • More examples