Modules
What is a module?
A module is a versioned directory that implements the cluster lifecycle operations for a specific cloud provider or infrastructure target. Modules contain shell scripts and workflow YAMLs for:
- create — provision a new cluster
- delete — deprovision a cluster
- status — query the current cluster status
- auth — merge the cluster kubeconfig into
~/.kube/config - scale (optional) — apply param changes to a live cluster without reprovisioning
- describe (optional) — report a live cluster’s params, for
hyve cluster adopt
Hyve does not embed any cloud SDKs. All cloud operations are delegated to modules, which in turn invoke whatever CLI tools are already installed in your environment (the Civo CLI, AWS CLI, gcloud, az, etc.).
Why modules?
The module system decouples Hyve’s core reconciliation logic from cloud-provider specifics:
- Any provider, any implementation — if you can produce the right
HYVE_KEY=valueoutputs, you can write a module for it, in whatever language or tool fits the provider best - Your credentials stay in your environment — modules read credentials from the same env vars and config files that your cloud CLIs use; Hyve never stores them
- Versioned and locked — modules are pinned in
hyve.lockwith a SHA256 content digest, ensuring reproducible reconciles across your team and CI/CD - Composable — modules can be combined with lifecycle workflows (
beforeCreate,onCreate,afterCreate,onDelete,afterDelete) for full pipeline automation
Module directory structure
<module>/ module.yaml — manifest: apiVersion, kind: Module, params, requirements create.yaml — kind: Workflow; provisions the cluster; emits HYVE_* outputs delete.yaml — kind: Workflow; deprovisions the cluster status.yaml — kind: Workflow; emits HYVE_CLUSTER_STATUS=<value> auth.yaml — kind: ClusterAuth; one or more named auth methods scale.yaml — kind: Workflow; optional; applies param changes describe.yaml — kind: Workflow; optional; used by `hyve cluster adopt` README.mdmodule.yaml
The manifest describes the module, declares params, and lists any requirements — environment variables and tools that must be in PATH:
apiVersion: v1kind: Modulemetadata: name: civo description: Provision Civo Kubernetes clusters version: v1.0.0spec: params: - name: node_size required: true description: Civo node size (e.g. g4s.kube.medium) - name: node_count required: false default: "1" - name: cluster_type required: false default: k3s choices: [k3s, talos] requirements: env: - name: CIVO_TOKEN description: "Civo API token. Alternative: run `civo apikey save` before reconciling." tools: - name: civo description: Civo CLI — https://github.com/civo/cli - name: kubectl description: Kubernetes CLI — required for authIn local mode, spec.requirements.tools is checked with a plain PATH lookup before every module operation — create, delete, status, scale, and auth — not just once at install time. A missing tool fails fast with a clear error naming every missing tool, before the operation’s script ever runs. (In cluster mode operations run in a Job container, so the tools must be in its image — see Running in cluster mode.)
module tool requirements not met: - required tool 'civo' not found in PATH (Civo CLI — https://github.com/civo/cli)spec.requirements.env is enforced in cluster mode: a missing variable fails the operation with a clear error before its script runs. It isn’t enforced in local mode, where a credential may legitimately come from somewhere else (a CLI’s own config file).
spec.params is documentation only — shown by hyve module info. default, required, and choices aren’t applied or enforced, so scripts must supply their own fallbacks (${HYVE_PARAM_NODE_COUNT:-1}).
Module sources
Modules are referenced by source string in templates:
| Source format | Example | When to use |
|---|---|---|
| Git repository | github.com/your-org/hyve-my-provider | A module in its own repository, on any git host |
| Git repository with subdirectory | github.com/your-org/modules//my-provider | A monorepo holding several modules |
Local path (./, ../, or absolute) | ../hyve-my-provider | Developing and testing a module, relative to the consuming repo’s root |
Versions: omitted or latest resolves to the newest semver tag (or HEAD if there are none), a semver constraint to the best matching tag, and anything else is used as a branch, tag, or commit. Private repositories work with the machine’s own git credentials, plus GITHUB_TOKEN for github.com.
Module params
Params declared in module.yaml map from cluster.spec.params to environment variables injected into every operation.
In the cluster YAML:
spec: driver: source: github.com/your-org/hyve-my-provider version: v1.0.0 params: node_size: g4s.kube.medium node_count: "3" cluster_type: k3sInjected as environment variables:
| cluster.spec.params key | Environment variable |
|---|---|
node_size | HYVE_PARAM_NODE_SIZE |
node_count | HYVE_PARAM_NODE_COUNT |
cluster_type | HYVE_PARAM_CLUSTER_TYPE |
The rule is: HYVE_PARAM_ + the key uppercased. Nothing else is converted, so use snake_case keys — a key like node-size produces an unusable variable name.
Standard environment variables
In addition to HYVE_PARAM_* variables, every module operation also receives:
| Variable | Value |
|---|---|
HYVE_CLUSTER_NAME | cluster.metadata.name |
HYVE_CLUSTER_REGION | cluster.spec.region |
every driverOutputs key | values create printed, re-injected as-is |
Scripts run with the working directory set to the consuming repository’s root, not the module’s own directory — so don’t source another file from your module by relative path.
Management clusters
Some modules don’t talk to a cloud directly but to another Kubernetes cluster that does the work — a Cluster API module creates Cluster objects on a CAPI management cluster. Name it on the template or cluster, and every operation gets a kubeconfig for it as HYVE_MGMT_KUBECONFIG:
apiVersion: hyve.io/v1alpha1kind: Templatemetadata: name: gkespec: mgmtCluster: capi-mgmt # a cluster hyve already manages driver: source: github.com/acme/hyve-capi-module version: v0.2.0hyve template create --mgmt-cluster capi-mgmt sets the same field; clusters created from the template inherit it, and a cluster can set its own. A module can also declare a default (spec.requirements.mgmtCluster in module.yaml), which spec.mgmtCluster overrides — but a module meant to be shared across installs should leave it unset, since it names one install’s cluster. With neither set, the module gets no HYVE_MGMT_KUBECONFIG and is expected to manage its own (say, a kind cluster it creates).
The name resolves like any cluster reference: in cluster mode a short name means that cluster in the same environment (capi-mgmt → default-capi-mgmt). Reconcile checks that it exists before running any operation. When the management cluster is hyve’s own host (access.method: primary), cluster mode mints a fresh cluster-admin kubeconfig for each operation; otherwise it’s the kubeconfig the management cluster’s own auth last wrote, so that cluster must have reconciled (or you must have run hyve cluster auth <name>) first. A cluster in another organization can name the host only if a superadmin allows it — see Multi-Tenancy.
Outputs protocol
Operations emit outputs by printing HYVE_KEY=value lines to stdout (anything else on stdout is ignored — log to stderr). Outputs from create are stored in the cluster’s driverOutputs; other operations’ outputs are only read for that run.
# Inside create.yaml — a shell step outputs values for later useecho "HYVE_CLUSTER_ID=${CLUSTER_ID}"echo "HYVE_CLUSTER_ENDPOINT=${ENDPOINT}"echo "HYVE_CLUSTER_STATUS=active"On every subsequent reconcile, all keys in cluster.spec.driverOutputs are re-injected verbatim as environment variables. This allows, for example, the create operation to output a VPC ID that the delete operation later references.
Status values
The status operation must emit HYVE_CLUSTER_STATUS=<value>, uppercase:
| Value | What the reconciler does |
|---|---|
NOT_FOUND | Runs create — or, if the cluster is marked delete: true, treats it as gone and removes its definition |
ACTIVE | Runs auth, scale on param drift, then applies spec.resources — or runs delete if marked for deletion |
FAILED | Retries create — or runs delete if marked for deletion |
CREATING / UPDATING / DELETING | Skips this cycle while the operation finishes |
Any other value, or none, is reported as an error. Only emit NOT_FOUND when certain — if the provider can’t be reached, exit non-zero instead; a false NOT_FOUND on a cluster marked for deletion drops its definition without running delete.
How auth works
The auth.yaml file has kind: ClusterAuth. Hyve runs it when hyve cluster auth <name> is called, and on every reconcile of an ACTIVE cluster (to apply spec.resources). With exports: KUBECONFIG, hyve sets $KUBECONFIG to a per-cluster file before the script runs; the script writes the kubeconfig there, and hyve cluster auth merges it into ~/.kube/config.
A module can declare one or more named auth methods under spec.methods. Hyve runs the first method by default; pass --method <name> to select a specific one.
apiVersion: v1kind: ClusterAuthmetadata: name: civo-authspec: methods: - name: local description: Auth via the Civo CLI. Requires the civo CLI and kubectl. deps: - civo - kubectl auth: script: | civo kubernetes config "$HYVE_CLUSTER_NAME" \ --region "$HYVE_CLUSTER_REGION" > "$KUBECONFIG" exports: KUBECONFIGModules that support more than one auth path — for example an interactive CLI flow and a non-interactive API-based flow — declare each as a separate named method:
spec: methods: - name: local description: Full auth flow via aws-cli. Supports SSO and MFA. deps: [aws-cli] auth: script: | aws eks update-kubeconfig \ --name "${HYVE_CLUSTER_NAME}" \ --region "${HYVE_CLUSTER_REGION}" exports: KUBECONFIG
- name: headless description: Non-interactive auth using the AWS STS HTTP API via curl. No aws-cli required. deps: [] auth: script: | # construct pre-signed STS URL, build kubeconfig with kubectl export KEEPER_KUBECONFIG="$(cat /tmp/kubeconfig-${HYVE_CLUSTER_NAME}.yaml)" exports: KEEPER_KUBECONFIGMethod fields
| Field | Description |
|---|---|
name | Unique identifier within the module. Used with --method. |
description | Human-readable explanation of this auth path. |
deps | Tools the script requires (informational; checked before execution). |
auth.script | Shell script that performs the auth. Receives all HYVE_* env vars. |
exports | KUBECONFIG (or KEEPER_KUBECONFIG) — hyve points $KUBECONFIG at a per-cluster file before running the script, and the script writes the kubeconfig there. Works unchanged in cluster mode, where hyve relays the file back from the Job. |
Backward compatibility
Single-method auth.yaml files using the legacy spec.bootstrap / spec.verify shape continue to work without modification. Hyve wraps them internally as a single method named "default".
hyve.lock — content addressing
hyve.lock maps each source@version reference to a SHA256 digest and download URL:
version: 1modules: github.com/your-org/hyve-my-provider@v1.0.0: source: github.com/your-org/hyve-my-provider@v1.0.0 resolved: https://github.com/your-org/hyve-my-provider/archive/refs/tags/v1.0.0.tar.gz sha256: "abc123..."When Hyve downloads a module, it verifies the digest. If the content has changed (e.g. a tag was force-pushed), Hyve refuses to use the module and reports an integrity error.
Keep hyve.lock in your context directory and share it however you share the rest of that directory (committed to git, if you’re using it — see Contexts). This ensures:
- Every team member uses the exact same module version
- CI/CD pipelines reproduce the same reconcile behavior
- Supply-chain tampering is detected
driverOutputs
cluster.spec.driverOutputs is a key-value map populated by the reconciler from a successful create’s HYVE_KEY=value lines, plus hyve’s own HYVE_LAST_PARAMS_HASH. It’s written to a reconciler-owned sidecar file (cluster-state/<name>.state.yaml, a sibling of the clusters/ directory the desired-state YAML lives in), not inline in it — see Reconciler state file. If your context directory is git-backed, that sidecar file gets committed like any other change; hyve itself never commits it for you.
spec: driverOutputs: HYVE_CLUSTER_ID: abc-123 HYVE_CLUSTER_ENDPOINT: https://abc-123.k8s.civo.com HYVE_LAST_PARAMS_HASH: sha256:def456...Writing a custom module
See Writing a Module for a step-by-step guide — scaffolding with hyve module init, writing each operation, testing from a local path, and publishing.
Auth-only modules
Every clusters/*.yaml requires spec.driver — there is no “driverless” cluster type. But not every cluster needs to be provisionable by Hyve. For a cluster that already exists and was never meant to be created or destroyed by Hyve — a local k3d cluster, or any pre-existing cluster you just want to track and hand out kubeconfig access for — declare the module metadata.type: authOnly and implement only auth.yaml (plus, optionally, status.yaml if you want Hyve to detect the cluster disappearing).
apiVersion: v1kind: Modulemetadata: name: k3d-auth version: 0.1.0 description: Kubeconfig access for a local k3d cluster type: authOnly# auth.yaml — merges the k3d cluster's kubeconfigapiVersion: v1kind: ClusterAuthmetadata: name: k3d-authspec: methods: - name: local auth: script: | k3d kubeconfig merge "$HYVE_CLUSTER_NAME" --kubeconfig-merge-default exports: KUBECONFIGScaffold this shape directly with hyve module init <name> --auth-only, which writes only module.yaml (with type: authOnly set) and auth.sh — no create/delete/status/scale stubs.
What changes for an authOnly module:
- The reconciler treats a missing (or empty)
HYVE_CLUSTER_STATUSasACTIVEinstead of reporting an unrecognized status — sohyve reconcileruns the auth operation and appliesspec.resourcesevery cycle without needing astatus.yamlthat reportsACTIVE. spec.delete: truestill works: it runs anyonDeleteworkflows, calls the (absent) delete operation as a no-op, and removes the cluster’s YAML from the repository — deregistering it from Hyve without touching the real cluster.hyve module validaterequiresauth.yaml/auth.shto be present specifically forauthOnlymodules, rather than accepting any single operation file.
If a module also declares spec.requirements.tools (e.g. k3d), Hyve checks each tool is on PATH before running any operation and fails with a clear error if one is missing — useful for surfacing “install k3d first” instead of an opaque shell failure.