harvester-cli

command module
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 24, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

README

Go Report Card Lint Go Code

Harvester CLI

A fast, kubectl-style command-line tool for managing Harvester HCI clusters — create and control VMs, images, volumes, networks, and hosts without leaving your terminal.

Built and tested against Harvester v1.9.0 (KubeVirt 1.8.x). Earlier 1.x clusters keep working, minus the two 1.9.0-only features: image create --backend cdi and import --source-cluster-type ova.


Table of Contents


Features

Area Capabilities
Virtual Machines List, create, delete, start, stop, restart, live-migrate
VM Images List (with StorageClass and backend), upload from URL or file, delete, Longhorn backing-image or CDI backend
Image Catalog Curated list of cloud-init-enabled Linux images (Fedora, CentOS Stream, Debian, AlmaLinux, Rocky, Ubuntu, openSUSE); interactive picker, scriptable create, works offline via embedded JSON + catalog init cache
Networks List NADs with VLAN info, including trunk ranges
Volumes List PVCs with live Longhorn usage and StorageClass; create and delete PVCs
Hosts List nodes with real-time CPU % and memory usage from the metrics API
Templates List and inspect VM templates
SSH Keypairs List registered public keys
Shell Direct SSH into a running VM
VM Import Enable the vm-import-controller and import guests from VMware, OpenStack or an OVA server
Config Login to Rancher and auto-download the Harvester kubeconfig
Dry-run Print the Kubernetes YAML for any create command without applying it — ideal for GitOps workflows

Installation

brew install abonillabeeche/tap/harvester

Homebrew handles the macOS quarantine flag automatically, so Gatekeeper will not block the binary.

First-time setup: add the tap once with brew tap abonillabeeche/tap, then install as above.


Install script (macOS and Linux)
curl -fsSL https://raw.githubusercontent.com/abonillabeeche/harvester-cli/main/install.sh | sh

The script auto-detects your OS and architecture, downloads the right binary from the latest release, and removes the macOS quarantine flag. To install to a custom location:

INSTALL_DIR=~/.local/bin sh <(curl -fsSL https://raw.githubusercontent.com/abonillabeeche/harvester-cli/main/install.sh)

Windows

Download the .exe for your architecture from the latest release and place it somewhere on your %PATH%.


Build from source

Requirements: Go 1.21+

git clone https://github.com/abonillabeeche/harvester-cli.git
cd harvester-cli
go build -o harvester .
sudo mv harvester /usr/local/bin/
harvester --version

Configuration

harvester needs a kubeconfig that points to your Harvester cluster's Kubernetes API. There are three ways to provide it, in order of precedence:

Place the kubeconfig at ~/.harvester/config. This is the path the CLI uses automatically with no extra flags.

mkdir -p ~/.harvester
cp /path/to/your/harvester-kubeconfig ~/.harvester/config
2. Environment variable
export HARVESTER_CONFIG=/path/to/kubeconfig
harvester vm list
3. Inline flag
harvester --harvester-config /path/to/kubeconfig vm list
Automatic download via Rancher

If your Harvester cluster is imported into Rancher, you can download the kubeconfig automatically using a Rancher API token:

# Log in to your Rancher server
harvester login https://<RANCHER_URL> -t <RANCHER_API_TOKEN>

# Download the kubeconfig for the target Harvester cluster
harvester get-config <HARVESTER_CLUSTER_NAME>
# Saved to ~/.harvester/config automatically

Commands

All commands accept -n <namespace> (or --namespace) to target a specific namespace, mirroring kubectl behavior. The default namespace is default.


VM Management
harvester vm list [-n NAMESPACE]

Lists all VMs in the cluster with their state, CPU, memory, and IP address.


harvester vm create [FLAGS...] VM_NAME

Creates a VM. Important: all flags must come before the VM name.

Flag Short Description
--namespace -n Target namespace (default: default)
--cpus -c Number of vCPUs (default: 1)
--memory -m RAM size, e.g. 4Gi (default: 1Gi)
--disk-size -d Root disk size, e.g. 20Gi (default: 10Gi)
--vm-image-id Harvester image ID to boot from
--ssh-keyname -i SSH keypair name (use namespace/name for cross-namespace)
--network Network attachment, e.g. default/vlan1
--template VM template in name:version format
--count Create N identical VMs named basename-1…basename-N
--user-data-filepath Path to a cloud-init user-data YAML file
--dry-run Print the KubeVirt YAML without creating the VM

Examples:

# Create a VM using a specific image and network
harvester vm create --cpus 2 --memory 4Gi --vm-image-id default/ubuntu-noble --network default/vlan1 --ssh-keyname default/mykey my-vm

# Create 3 VMs from a template
harvester vm create --template ubuntu-base:1 --count 3 test-vm

# Create a VM in a non-default namespace
harvester vm create -n dev --cpus 4 --memory 8Gi dev-vm

# Preview the manifest without applying it
harvester vm create --dry-run --vm-image-id default/ubuntu-noble --network default/vlan1 my-vm

harvester vm stop    VM_NAME [-n NAMESPACE]
harvester vm start   VM_NAME [-n NAMESPACE]
harvester vm restart VM_NAME [-n NAMESPACE]
harvester vm delete  VM_NAME [-n NAMESPACE]

harvester vm migrate [--node TARGET_NODE] VM_NAME [-n NAMESPACE]

Live-migrates a running VM to another host without downtime. Omit --node to let the scheduler choose the best target automatically.

# Migrate to a specific host
harvester vm migrate --namespace default --node gr6-2 my-vm

# Let the scheduler pick the target
harvester vm migrate -n default my-vm

Images
harvester image list [-n NAMESPACE]

Lists VM images with their source type, storage backend, StorageClass, and URL.

NAME              ID                     SOURCE TYPE   BACKEND        STORAGE CLASS   URL
ubuntu-noble      default/ubuntu-noble   download      backingimage   tworeplicas     https://cloud-images.ubuntu.com/...
leap-16           default/leap-16        download      cdi            ceph-rbd        https://download.opensuse.org/...

harvester image create --source URL [--storage-class CLASS] [--backend BACKEND] [-n NAMESPACE] IMAGE_NAME

Uploads a VM image from an HTTP/HTTPS URL (or local file path). Prints the image ID on success.

--backend picks how Harvester stores the image:

Backend Meaning
backingimage Longhorn v1 backing image — the Harvester default
cdi Imported through CDI, the only backend that can place an image on a third-party CSI StorageClass (Ceph RBD, LINSTOR, Longhorn v2, …)

Leave it out and the CLI infers it from --storage-class: anything that is not Longhorn v1 gets cdi, because a backing image cannot live on a third-party StorageClass and Harvester would silently move the image back to a Longhorn class.

CDI caveat: the CDI importer needs the source server to return a Content-Length header. Some mirrors (notably cloud-images.ubuntu.com with chunked transfer encoding) do not, and the import fails with failed to fetch image size. Use a mirror that sends the header, or the backingimage backend.

# Upload Ubuntu Noble using the tworeplicas storage class
harvester image create \
  --source https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img \
  --storage-class tworeplicas \
  ubuntu-noble
# Image created: default/ubuntu-noble

# Put an image on Ceph RBD -- the cdi backend is inferred from the StorageClass
harvester image create \
  --source https://download.opensuse.org/distribution/leap/16.0/appliances/Leap-16.0-Minimal-VM.x86_64-Cloud.qcow2 \
  --storage-class ceph-rbd \
  leap-16

# ...or ask for it explicitly
harvester image create --backend cdi \
  --source https://download.opensuse.org/distribution/leap/16.0/appliances/Leap-16.0-Minimal-VM.x86_64-Cloud.qcow2 \
  --storage-class ceph-rbd \
  leap-16

# Preview the manifest without applying it
harvester image create --dry-run \
  --source https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img \
  ubuntu-noble

--storage-class is also written to the harvesterhci.io/storageClassName annotation, which is where Harvester's backing-image mutator actually reads it from. Without the annotation the flag is ignored on the backingimage path, and on a cluster whose default StorageClass is marked only with the deprecated storageclass.beta.kubernetes.io/is-default-class annotation the create is rejected outright with no default storageClass found for backingImage.


harvester image delete [-n NAMESPACE] IMAGE_NAME [IMAGE_NAME...]

Deletes one or more images. An image can be named either by the display name image list shows or by its resource name (the part after the / in the ID column). Unlike vm create --vm-image-id, the lookup never leaves the namespace — a delete should not reach across the cluster for something that merely looks close enough.

harvester image delete ubuntu-noble
# VM image ubuntu-noble (default/image-zzkzm) deleted successfully

harvester image delete -n staging leap-16 image-ccxcx

Harvester refuses the delete while a VM or volume still depends on the image, and the webhook's error is passed through unchanged.


Image Catalog

The catalog is a curated list of publicly-available, cloud-init-enabled Linux images (Fedora, CentOS Stream, Debian, AlmaLinux, Rocky Linux, Ubuntu, openSUSE — see image-metadata.json). Instead of hunting for the right download URL for every cluster, you pick from the catalog and the CLI submits the image to Harvester.

The catalog metadata lives in image-metadata.json at the root of this repo. That same file is bundled into every CLI binary via //go:embed, and it can be cached to disk with catalog init — so the catalog works even when your workstation has no internet, as long as the Harvester servers themselves can reach the image URLs.

Where the catalog comes from

Every catalog subcommand resolves its metadata source in this order:

  1. --metadata-url flag or HARVESTER_CATALOG_METADATA env var, if explicitly set (accepts https://…, file://…, or a plain filesystem path).
  2. ~/.harvester/image-metadata.json if it exists (the local cache written by catalog init).
  3. Default remote URL (https://raw.githubusercontent.com/abonillabeeche/harvester-cli/main/image-metadata.json). If the HTTP fetch fails, the CLI transparently falls back to the copy embedded in the binary (with a warning), so catalog still works offline out of the box.

The interactive catalog command prints the resolved source at the top of its output so you can always tell where the list came from:

$ harvester image catalog
Image catalog source: /Users/you/.harvester/image-metadata.json
catalog init — cache the catalog for offline use
harvester image catalog init [--metadata-url URL] [--force]

Downloads the metadata JSON and writes it to ~/.harvester/image-metadata.json (alongside the harvester CLI config). Subsequent catalog commands prefer this file automatically.

# First-time setup while online:
harvester image catalog init
# Downloading catalog from: https://raw.githubusercontent.com/abonillabeeche/harvester-cli/main/image-metadata.json
# Saving to:                /Users/you/.harvester/image-metadata.json
# Cached catalog with 10 OS group(s). Future 'image catalog' runs will use this file automatically.

# Refresh when new distros are added upstream:
harvester image catalog init --force

# Cache from a private mirror instead of GitHub:
harvester image catalog init --metadata-url https://mirror.internal/harvester/image-metadata.json --force
catalog — interactive picker
harvester image catalog [--namespace NS] [--storage-class SC] [--metadata-url URL]

Walks you through picking an OS group, then an image, then the namespace, then the StorageClass. If the caller has cluster-list permissions, namespace and StorageClass are shown as numbered pickers; otherwise the CLI falls back to a free-form text prompt (useful for Rancher-proxied Harvester kubeconfigs, which typically only grant namespaced reads).

$ harvester image catalog
Image catalog source: /Users/you/.harvester/image-metadata.json

NUMBER  NAME                  KEY                   NUMBER OF IMAGES
1       AlmaLinux             almalinux             3
2       CentOS Stream         centos-stream         2
3       Debian                debian                2
4       Fedora                fedora                3
5       openSUSE Leap         opensuse-leap         4
6       openSUSE MicroOS      opensuse-microos      1
7       openSUSE Tumbleweed   opensuse-tumbleweed   1
8       Rocky Linux           rocky-linux           5
9       Ubuntu                ubuntu                6
Insert a number to select the image OS:
4

Here are the images available for Fedora

NUMBER  NAME              VERSION  BUILD  URL
1       Fedora Cloud 44   44       1.7    https://download.fedoraproject.org/.../Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2
2       Fedora Cloud 43   43       1.6    https://download.fedoraproject.org/.../Fedora-Cloud-Base-Generic-43-1.6.x86_64.qcow2
3       Fedora Cloud 42   42       1.1    https://download.fedoraproject.org/.../Fedora-Cloud-Base-Generic-42-1.1.x86_64.qcow2

Insert a number to select an image to download:
1

Your image URL is : https://download.fedoraproject.org/.../Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2

Enter the namespace to create the image in [default]: my-project

Enter the StorageClass name (empty = cluster default): tworeplicas

INFO Creating image "Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2" in namespace "my-project" with StorageClass "tworeplicas"
INFO Image was created in Harvester with display name Fedora-Cloud-Base-Generic-44-1.7.x86_64.qcow2 and id image-a9b2f

Pass --namespace or --storage-class to skip the matching prompt. The KEY column shows the shell-friendly identifier to use with catalog list <key> and catalog create <key>/<version>.

catalog list — non-interactive listing
harvester image catalog list [OS] [--metadata-url URL]

Prints catalog entries without prompting. The full listing shows both the pretty NAME (label) and the OS key (used by scripts). Pass an OS key to filter to a single group.

# Full catalog
harvester image catalog list
# NAME             OS               VERSION  BUILD    SHORT NAME        URL
# AlmaLinux        almalinux        10       latest   AlmaLinux 10      https://...
# ...
# openSUSE Leap    opensuse-leap    16.0     latest   openSUSE Leap 16.0  https://...
# ...

# Just Fedora
harvester image catalog list fedora
# Images for Fedora (fedora):
#
# VERSION  BUILD  SHORT NAME       URL
# 44       1.7    Fedora Cloud 44  https://...
# 43       1.6    Fedora Cloud 43  https://...
# 42       1.1    Fedora Cloud 42  https://...
catalog create — scripted image creation
harvester image catalog create <OS>/<VERSION> \
  [--namespace NS] [--storage-class SC] [--display-name NAME] \
  [--description TEXT] [--dry-run] [--metadata-url URL]

Creates a VM image from a catalog entry by <os>/<version> selector (e.g. fedora/44, opensuse-leap/16.0, rocky-linux/9.5). No prompts — flag values are used as-is. Empty --storage-class means "use the cluster default StorageClass". When multiple builds share the same version, the last entry in the catalog array wins (arrays are ordered oldest → newest) and the choice is logged.

# Create with cluster default StorageClass
harvester image catalog create fedora/44

# Explicit namespace + StorageClass + friendly display name
harvester image catalog create --namespace prod --storage-class tworeplicas \
  --display-name ubuntu-noble-base ubuntu/24.04

# Preview the YAML manifest without creating (GitOps-friendly)
harvester image catalog create --dry-run --storage-class tworeplicas debian/12

# openSUSE Leap 16 / Rocky 10 examples
harvester image catalog create --storage-class tworeplicas opensuse-leap/16.0
harvester image catalog create --storage-class tworeplicas rocky-linux/10

# Use a private mirror as the source
harvester image catalog create --metadata-url https://mirror.internal/catalog.json centos-stream/9

Error messages point you to what's available if you get the selector wrong:

$ harvester image catalog create ubuntu/99
FATA no image with version "99" for ubuntu. Available versions: 25.10, 25.04, 24.10, 24.04, 22.04, 20.04

$ harvester image catalog create nonexistent/1.0
FATA unknown OS "nonexistent". Available: almalinux, centos-stream, debian, fedora, opensuse-leap, opensuse-microos, opensuse-tumbleweed, rocky-linux, ubuntu

Flag ordering: because of a urfave/cli v2 quirk, flags must appear before the positional selector: create --dry-run ubuntu/24.04 — not create ubuntu/24.04 --dry-run.


Networks
harvester network list [-n NAMESPACE]

Lists NetworkAttachmentDefinitions with their CNI type and VLAN ID. A trunk network carries a set of ranges rather than a single ID, and is shown as trunk <ranges>.

NAME         NAMESPACE   TYPE       VLAN ID
vlan1        default     bridge     1
vlan10       default     bridge     10
multi-vlan   default     bridge     trunk 1000-1010,2001
public-nat   default     kube-ovn

Volumes
harvester volume list [-n NAMESPACE]

Lists PersistentVolumeClaims cross-referenced with Longhorn to show actual used space.

NAME         NAMESPACE   STATE    CAPACITY   USED       STORAGE CLASS
my-vm-disk   default     Healthy  20.0 GiB   3.2 GiB    tworeplicas
data-vol     default     Healthy  100.0 GiB  45.1 GiB   harvester-longhorn

harvester volume create --storage-class CLASS --size SIZE [-n NAMESPACE] [--dry-run] VOLUME_NAME

Creates a PersistentVolumeClaim backed by the specified StorageClass. Use binary suffixes for size (Gi, Mi).

Flag Short Description
--storage-class --sc StorageClass for the volume (see harvester volume list-storageclass)
--size -s Volume size, e.g. 10Gi, 500Mi
--namespace -n Target namespace (default: default)
--dry-run Print the PVC YAML without creating it
harvester volume create --sc tworeplicas --size 20Gi my-data-vol
# Volume created: default/my-data-vol

harvester volume delete [-n NAMESPACE] [--force] VOLUME_NAME [VOLUME_NAME...]

Deletes PersistentVolumeClaims. A volume that a VM still lists as a disk is refused by name, because deleting it would leave the PVC in Terminating until the VM itself is gone — which looks like a hang. Pass --force to skip the check.

harvester volume delete my-data-vol
# Volume deleted: default/my-data-vol

harvester volume delete my-vm-disk
# FATA volume default/my-vm-disk is still used by VM my-vm, delete the VM first or pass --force

harvester volume list-storageclass

Lists all StorageClasses in the cluster — equivalent to kubectl get sc, plus a DEFAULT column.

NAME                  DEFAULT   PROVISIONER             RECLAIM POLICY   BINDING MODE    ALLOW EXPANSION
harvester-longhorn    *         driver.longhorn.io      Delete           Immediate       true
tworeplicas                     driver.longhorn.io      Delete           Immediate       true

Harvester honours only storageclass.kubernetes.io/is-default-class. A class marked default with nothing but the deprecated storageclass.beta.kubernetes.io/is-default-class looks default to kubectl while Harvester acts as if the cluster has none — which shows up much later as no default storageClass found for backingImage. That case is named rather than left blank:

NAME                  DEFAULT                             PROVISIONER          RECLAIM POLICY   BINDING MODE   ALLOW EXPANSION
harvester-longhorn    (beta only, Harvester ignores it)   driver.longhorn.io   Delete           Immediate      true

Hosts
harvester host list

Lists all cluster nodes with real-time CPU and memory usage pulled from the Kubernetes Metrics API. Falls back gracefully to <unknown> if the metrics server is unavailable.

NAME    STATUS  ROLES                      AGE    CPU(cores)  CPU%  MEM USE    MEM%  MEM TOTAL
gr6-1   Ready   etcd,control-plane,master  769d   1918m       12%   26.0 GiB   41%   62.2 GiB
gr6-2   Ready   etcd                       769d   1126m       10%   3.0 GiB    10%   28.3 GiB
gr6-3   Ready   etcd,master,control-plane  769d   2480m       16%   26.5 GiB   42%   62.2 GiB
  • CPU(cores): live usage in millicores (e.g. 1918m = 1.918 cores)
  • CPU%: usage relative to allocatable CPU on that node
  • MEM USE: live memory usage from the metrics API
  • MEM%: usage relative to allocatable memory on that node
  • MEM TOTAL: total installed RAM (node.Status.Capacity)

Templates
harvester template list [-n NAMESPACE]

# Show a specific template version
harvester template show NAME:VERSION [-n NAMESPACE]
harvester template show ubuntu-base:1

SSH Keypairs
harvester keypair list [-n NAMESPACE]

Lists SSH public keys registered in Harvester, which can be referenced by name when creating VMs.


Shell Access
harvester shell [--ssh-user USER] [--ssh-key PATH] VM_NAME

Opens an interactive SSH session directly into a running VM. Requires ssh to be available on the local system.

Flag Default Description
--ssh-user ubuntu Username to connect as
--ssh-key ~/.ssh/id_rsa Path to private key
--ssh-port 22 SSH port
harvester shell --ssh-user ubuntu --ssh-key ~/.ssh/mykey my-vm

VM Import

Drives Harvester's vm-import-controller addon, which migrates guests from another platform into Harvester. Three source types are supported: vmware, openstack, and — new in Harvester v1.9.0 — ova, which pulls OVA files straight off an HTTP server.

harvester import enable

Enables the vm-import-controller addon. Safe to re-run; it is a no-op when already enabled.

Until it is enabled the migration CRDs do not exist, and every other import subcommand fails with a 404 from the API server. The CLI adds the missing half of that message:

FATA failed to list VM imports: the server could not find the requested resource
     (get virtualmachineimports.migration.harvesterhci.io). The vm-import-controller addon looks
     disabled, run 'harvester import enable' first

harvester import source-add \
  --source-cluster-type TYPE \
  --source-cluster-namespace NAMESPACE \
  --endpoint ENDPOINT \
  [--credentials-secret NS/SECRET] \
  [--dc DATACENTER] [--region REGION] [--http-timeout SECONDS] \
  SOURCE_NAME

Registers a source to import from. Which flags apply depends on the type:

Type --endpoint --dc --region --credentials-secret --http-timeout
vmware vCenter URL required — required —
openstack Keystone URL — required required —
ova URL the OVA files are served from — — optional optional, defaults to 600

For ova, the referenced secret may hold username, password and ca.crt keys; omit it entirely for an unauthenticated server.

# VMware vCenter
harvester import source-add \
  --source-cluster-type vmware \
  --source-cluster-namespace harvester-system \
  --endpoint https://vcenter.example.com/sdk \
  --dc DC1 \
  --credentials-secret harvester-system/vcenter-creds \
  my-vcenter

# OVA files on a plain HTTP server, 2-minute download timeout
harvester import source-add \
  --source-cluster-type ova \
  --source-cluster-namespace harvester-system \
  --endpoint http://images.example.com/ovas/ \
  --http-timeout 120 \
  my-ovas

harvester import create \
  --vm-name GUEST \
  --source-cluster SOURCE_NAME \
  --source-cluster-type TYPE \
  --source-cluster-namespace NAMESPACE \
  [--network-mapping SRC:DST ...] \
  VM_IMPORT_NAME

Starts an import. --network-mapping (alias --net-map) may be repeated to map each source network onto a Harvester network; for ova, --vm-name is the OVA file name relative to --endpoint.

harvester import create \
  --vm-name my-guest.ova \
  --source-cluster my-ovas \
  --source-cluster-type ova \
  --source-cluster-namespace harvester-system \
  --net-map "VM Network:default/vlan1" \
  import-my-guest

harvester import list [-n NAMESPACE]
harvester import delete [-n NAMESPACE] VM_IMPORT_NAME
harvester import source-delete [-n NAMESPACE] [--type TYPE] SOURCE_NAME

An import lives in the same namespace as its source, which is not necessarily harvester-system, so import list covers every namespace unless -n narrows it.

NAME             NAMESPACE          VM NAME        STATUS    SOURCE_CLUSTER   CLUSTER_TYPE
import-my-guest  harvester-system   my-guest.ova   Running   my-ovas          OvaSource

--source-cluster-namespace and -n/--namespace are accepted interchangeably across every import subcommand.


Dry-run and GitOps

Every create subcommand accepts a --dry-run flag. Instead of calling the Kubernetes API, the CLI prints the fully-rendered YAML manifest to stdout and exits. This is useful for:

  • GitOps workflows — generate manifests locally, commit them to a Git repo, and let Fleet, Flux or ArgoCD apply them to the cluster.
  • Reviewing changes before applying — inspect the exact object the CLI would create before committing to it.
  • Piping into kubectl apply — run harvester vm create --dry-run ... | kubectl apply -f - for one-shot creation using the same flags as your normal workflow.
Volume
harvester volume create --dry-run --sc tworeplicas --size 5Gi testvol1
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
    creationTimestamp: null
    name: testvol1
    namespace: default
spec:
    accessModes:
        - ReadWriteMany
    resources:
        requests:
            storage: 5Gi
    storageClassName: tworeplicas
    volumeMode: Block
status: {}
Image
harvester image create --dry-run \
  --source https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img \
  --storage-class tworeplicas \
  ubuntu-noble
---
apiVersion: harvesterhci.io/v1beta1
kind: VirtualMachineImage
metadata:
    creationTimestamp: null
    generateName: image-
    namespace: default
spec:
    displayName: ubuntu-noble
    sourceType: download
    storageClassParameters: {}
    targetStorageClassName: tworeplicas
    url: https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
status:
    progress: 0
VM
harvester vm create --dry-run \
  --vm-image-id default/image-abc12 \
  --network default/vlan1 \
  --cpus 2 --memory 4Gi --disk-size 20Gi \
  my-vm
---
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
    annotations:
        harvesterhci.io/volumeClaimTemplates: '[{"metadata":{"name":"my-vm-disk-0-...","annotations":{"harvesterhci.io/imageId":"default/image-abc12"}},"spec":{"accessModes":["ReadWriteMany"],"resources":{"requests":{"storage":"20Gi"}},"volumeMode":"Block","storageClassName":"longhorn-image-abc12"}}]'
        networks.harvesterhci.io/ips: '[]'
    labels:
        harvesterhci.io/creator: harvester
    name: my-vm
    namespace: default
spec:
    runStrategy: Always
    template:
        spec:
            domain:
                cpu:
                    cores: 2
                    sockets: 1
                    threads: 1
                devices:
                    disks:
                        - disk:
                            bus: virtio
                          name: disk-0
                        - disk:
                            bus: virtio
                          name: cloudinitdisk
                    interfaces:
                        - bridge: {}
                          model: virtio
                          name: nic-1
                resources:
                    limits:
                        cpu: "2"
                        memory: 4Gi
            networks:
                - multus:
                    networkName: default/vlan1
                  name: nic-1
            volumes:
                - name: disk-0
                  persistentVolumeClaim:
                    claimName: my-vm-disk-0-...
                - cloudInitNoCloud:
                    networkData: "..."
                    userData: "#cloud-config\n..."
                  name: cloudinitdisk
GitOps example
# Generate manifests for a new environment
harvester volume create --dry-run --sc tworeplicas --size 50Gi -n prod db-vol     > manifests/db-vol.yaml
harvester image create  --dry-run --source https://example.com/os.qcow2 prod-os   >> manifests/images.yaml
harvester vm create     --dry-run --vm-image-id default/prod-os \
                          --network default/vlan10 --cpus 4 --memory 8Gi -n prod \
                          db-server                                                > manifests/db-server.yaml

# Commit and push — let your GitOps controller apply them
git add manifests/ && git commit -m "add prod db-server" && git push

Tips and Gotchas

Flag ordering

Due to how Go's flag parser works with positional arguments, all flags must appear before the positional argument, on every command:

# Correct
harvester vm create --cpus 2 --memory 4Gi my-vm

# Rejected
harvester vm create my-vm --cpus 2 --memory 4Gi
# FATA flag "--cpus" was given after a positional argument and would be ignored,
#      put every flag before the argument

The CLI refuses to run rather than dropping the flags, which used to mean a stray harvester vm create my-vm --dry-run really created the VM.

Misspelled subcommands

Every top-level command lists when it is called with no subcommand, and urfave/cli falls through to that action when the subcommand does not exist — so harvester image delet foo used to print the image list and exit 0. It now names the mistake:

harvester image delet my-image
# FATA unknown subcommand "delet" for "image", expected one of: list, create, delete, catalog, help
Sizes need a unit

--memory 4 is 4 bytes, not 4 GiB, and Harvester rejects it much later with a confusing guest memory is under the minimum requirement (10 Mi). --memory and --disk-size are now checked before anything is sent:

harvester vm create -m 4 my-vm
# FATA invalid --memory "4", a bare number is read as bytes -- use a unit, such as 4Gi
Cross-namespace references

When your VM lives in namespace dev but the SSH key or network is in default, use the namespace/name format:

harvester vm create -n dev \
  --ssh-keyname default/mykey \
  --network default/vlan1 \
  dev-vm
Template version is required for template show

The show subcommand requires both name and version separated by a colon:

harvester template show ubuntu-base:1
Debug mode

Add --debug before any command to enable verbose logging:

harvester --debug vm list

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL