Releases: zsoftly/zcp-cli
Release list
v0.0.23
zcp v0.0.23 Release Notes
instance --wait now reports the VM's real state
zcp instance create --wait (and start --wait / stop --wait) previously
polled the CMP's cached list/show endpoint, which can keep reporting Starting
for many minutes after a VM is actually Running. On affected deployments
--wait could hang until it timed out even though the VM was already up.
--wait now polls the live GET /virtual-machines/{slug}/meta endpoint, which
performs a real-time reconcile against the underlying platform (CloudStack/APC)
and returns the authoritative state. Verified live end to end: with --wait,
create returned Running from /meta while the plain instance list still
showed Starting.
This is a client-side workaround for a CMP background state-sync issue (the
platform's own reconciliation is unreliable; state only refreshes on demand).
The on-demand /meta sync is authoritative, so the CLI polls it.
instance delete --delete-public-ip help corrected — the flag is currently a no-op
The flag advertised that deleting a VM releases its auto-assigned public IP, but
that never worked against the live API: the DELETE endpoint ignores it, and
the IP-releasing PUT .../destroy endpoint currently rejects API-token auth (a
CMP bug, reported and under fix). Until that lands, the help text, confirmation
prompt, and command examples now state plainly that the IP is not released
automatically, and that you must free it manually:
zcp instance delete my-vm --yes
zcp ip release <ip-slug> --yesNo behavior change — this corrects misleading messaging only. The real fix
(routing instance delete through PUT .../destroy) is implemented and
verified at the request level, and is held until the API accepts token auth.
Installation and upgrade
The install script installs the latest release and upgrades an existing
installation in place.
Linux / macOS
curl -fsSL https://github.com/zsoftly/zcp-cli/releases/latest/download/install.sh | bashWindows (PowerShell)
irm https://github.com/zsoftly/zcp-cli/releases/latest/download/install.ps1 | iexManual download: grab your platform's binary from the
Releases page, chmod +x, and
place it on your PATH.
Verify:
zcp version # zcp version v0.0.23First-time setup after installing:
zcp profile add default --region yul-1 --project default-9 # prompts for bearer token
zcp auth validateFixed
instance --wait reflects the real state
# Create and wait: returns when the VM is actually Running (polled via /meta),
# even while `instance list` still reports Starting.
zcp instance create --name my-vm --project default-9 --region yul-1 \
--template ubuntu-2604-lts-1 --plan ca2m --billing-cycle hourly \
--network-plan pnet-yul --storage-category premium-ssd --waitSDK consumers get a new instance.Service.Meta(ctx, slug) method that returns
the live, hypervisor-synced view (authoritative state).
instance delete --delete-public-ip messaging
# The auto-assigned public IP is NOT released automatically yet (known CMP API
# bug, under fix). Free it manually after deleting:
zcp instance delete my-vm --yes && zcp ip release <ip-slug> --yesv0.0.22
zcp v0.0.22 Release Notes
DNS records now display and delete correctly
The live DNS backend (PowerDNS) models records as record sets addressed by
name and type. PowerDNS exposes no record IDs and returns values in a
contents array. The CLI previously decoded neither, so on PowerDNS-backed
deployments record tables printed blank ID and CONTENT columns, and
dns record-delete demanded a numeric --record-id those deployments never
expose. Record deletion was impossible there. Backends that do expose record
IDs keep the legacy --record-id path.
This release aligns the CLI with how the backend actually works, verified live
end to end (create → show → delete → confirm gone).
Highlights:
- Record content is visible again.
zcp dns showandrecord-create
tables show real values (multi-value sets joined, e.g.
ns1.zsoftly.ca., ns2.zsoftly.ca.), and the dead ID column is gone. dns record-deleteworks, by name and type.- Record names are relative. The backend appends the zone; the help text
now says so (passing an FQDN used to silently create
www.example.com.example.com.). egress createretries its lookup and reports honestly when the backend
silently drops an accepted rule (a platform-side issue found while testing).docs/commands.mdis now machine-validated: all 264 examples checked
against the built CLI. Six sections documented commands that did not exist
and are rewritten to the real trees.- L2 instances work, and
instance createexamples run as pasted thanks
to first-time contributor @cokerrd: a new--is-publicflag unblocks
--network-type L2, and the required--network-plan/--storage-category
flags are now in the examples and validated client-side.
Installation and upgrade
The install script installs the latest release and upgrades an existing
installation in place.
Linux / macOS
curl -fsSL https://github.com/zsoftly/zcp-cli/releases/latest/download/install.sh | bashWindows (PowerShell)
irm https://github.com/zsoftly/zcp-cli/releases/latest/download/install.ps1 | iexManual download: grab your platform's binary from the
Releases page, chmod +x, and
place it on your PATH.
Verify:
zcp version # zcp version v0.0.22First-time setup after installing:
zcp profile add default --region yul-1 --project default-9 # prompts for bearer token
zcp auth validateFixed
DNS record display and deletion
# Records show their content; sets are addressed by NAME + TYPE (no IDs)
zcp dns show example-com
# NAME TYPE CONTENT TTL
# www.example.com. A 192.0.2.50 3600
# example.com. NS ns1.zsoftly.ca., ns2.zsoftly.ca. 3600
# Create with a RELATIVE name (the backend appends the zone)
zcp dns record-create --domain example-com --name www --type A --content 192.0.2.50
# Delete by name and type (relative or fully qualified both work)
zcp dns record-delete --domain example-com --name www --type AThe legacy --record-id flag remains for deployments whose DNS backend exposes
record IDs. SDK consumers get DeleteRecordByName, CanonicalRecordFQDN, and
Record.Contents; the ID-based DeleteRecord is deprecated.
Egress rule creation reporting
The create endpoint returns no body, so the CLI resolves the new rule from the
rule list. It now retries that lookup (3 attempts over ~4s) before giving up,
and when the rule never appears (the API can return 200 yet create nothing on
some networks), the error says the backend may have dropped the rule, pointing
at the platform rather than the CLI.
L2 instance creation and complete create examples (community)
Contributed by @cokerrd, our first outside contributor. Two fixes to
instance create, both verified against the live API:
# L2 networks cannot carry a public IP. The new --is-public flag (default:
# true) unblocks them; the CLI rejects the invalid combination client-side.
zcp instance create --name my-l2-vm --template ubuntu-2604-lts-1 --plan ca2sl \
--billing-cycle hourly --network-plan l2net-yul --network-type L2 \
--storage-category premium-ssd --is-public=false \
--region yul-1 --project default-9
# --network-plan and --storage-category are required by the API and are now
# in every example, marked required in help, and validated client-side.
zcp instance create --name my-vm --template ubuntu-2604-lts-1 --plan ca2sl \
--billing-cycle hourly --network-plan pnet-yul --storage-category premium-ssd \
--region yul-1 --project default-9Command reference corrected and machine-validated
Six sections of docs/commands.md documented commands that do not exist
(monitoring create, vpn create --vpc, support close, dashboard status,
among others) or missed required flags (ip allocate without --plan/
--billing-cycle). All are rewritten to the real command trees, including
the previously undocumented kubernetes scale/get-config/upgrade-version/delete
and loadbalancer attach-vm/detach-vm/delete-rule. Every example in the
reference is now validated automatically against the CLI (command paths and
flags; 264 examples).
New Contributors
v0.0.21
zcp v0.0.21 Release Notes
Profile defaults now work everywhere, including create commands
zcp profile add default --region yul-1 --project default-9 stores your default scope
so you never repeat --region/--project. Until now that promise only held for
list/get commands: create and mutate commands (e.g. network create, instance create)
resolved their own scope from flags and environment variables only, so a fully
configured user still hit --region is required. That gap is closed: configure once,
and scoped commands pick the defaults up.
Highlights:
- Profile default region/project are honored by create/mutate commands. The root
scope gate now injects the resolved scope (flag > env > profile default, respecting
--profile) onto the command's flags. Verified end-to-end against the production API. - First-run setup points at the production defaults. The installers print
copy-paste setup forzcp profile add default --region yul-1 --project default-9;
every account's initial project isdefault-9(likeus-east-1on AWS). - All command examples use slugs verified against the live production catalog.
Broken template, backup, and virtual-router plan slugs are fixed.
Fixed
Profile default region/project honored by create/mutate commands
zcp profile add default --region yul-1 --project default-9
zcp auth validate
# Previously: Error: --region is required
# Now: creates the network in yul-1 / default-9 from your profile defaults
zcp network create --name my-net --network-plan inet-yul --billing-cycle hourlyExplicit --region/--project flags and ZCP_REGION/ZCP_PROJECT still take
precedence over the profile default, and --profile <name> selects which profile's
defaults apply. Two command groups manage their own scope by design and are
unaffected: dns create (fixed default region; still needs an explicit
--project) and object-storage create/list (object-storage os-* regions).
First-run examples point at the production defaults
The Unix and Windows installers now end with copy-paste setup commands
(zcp profile add default --region yul-1 --project default-9, zcp auth validate)
plus matching ZCP_REGION/ZCP_PROJECT examples for scripts. README,
configuration docs, and command examples consistently use yul-1 as the primary
compute region and YUL-compatible plan slugs.
Command examples verified against the live production catalog
Examples that referenced nonexistent slugs are fixed:
| Was | Now | Where |
|---|---|---|
ubuntu-2604-lts |
ubuntu-2604-lts-1 |
instance/autoscale --template (template slugs are region-specific; this is yul-1's) |
backup-1, backup-basic |
backup-yul |
backup create, vm-backup create --plan (backup plans are now enabled in the catalog) |
virtual-private-cloud-vpc |
virtual-private-cloud-vpc-1 |
virtual-router create --plan |
The docs Backup section was rewritten to show the real backup create flags
(--volume/--interval/--plan …) and to drop nonexistent backup get/backup restore subcommands. Verified live in yul-1: ca2sl/ca2sm/ca2sxs, b2g1,
pro-nvme, inet-yul, l2net-yul, k8s-la-yul-1, k8s-xla-yul-1,
vm-snapshot-yul, ipv4-yul, lb-yul, backup-yul, virtual-private-cloud-vpc-1,
ubuntu-2604-lts-1.
Upgrade notes
No breaking changes. If you have profile defaults configured (zcp profile add
with --region/--project), create/mutate commands that previously errored
without explicit flags now use those defaults automatically; pass --region/
--project (or set ZCP_REGION/ZCP_PROJECT) to override per invocation.
Installation
Linux / macOS / WSL (one-liner)
curl -fsSL https://github.com/zsoftly/zcp-cli/releases/latest/download/install.sh | bashInstalls zcp to /usr/local/bin (you may be prompted for sudo). Set INSTALL_DIR to
choose another location, e.g. INSTALL_DIR="$HOME/.local/bin".
Windows (PowerShell)
irm https://github.com/zsoftly/zcp-cli/releases/latest/download/install.ps1 | iexInstalls zcp.exe to %LOCALAPPDATA%\Programs\zcp.
Manual download
Grab the binary for your platform from the
Releases page, make it executable, and put it on
your PATH.
| OS | Arch | Asset |
|---|---|---|
| Linux | x86_64 | zcp-linux-amd64 |
| Linux | ARM64 | zcp-linux-arm64 |
| macOS | Intel | zcp-darwin-amd64 |
| macOS | Apple Silicon | zcp-darwin-arm64 |
| Windows | x86_64 | zcp-windows-amd64.exe |
| Windows | ARM64 | zcp-windows-arm64.exe |
# Linux amd64 example
curl -Lo zcp https://github.com/zsoftly/zcp-cli/releases/latest/download/zcp-linux-amd64
chmod +x zcp
sudo mv zcp /usr/local/bin/zcp# Windows amd64 example (PowerShell)
irm https://github.com/zsoftly/zcp-cli/releases/latest/download/zcp-windows-amd64.exe -OutFile zcp.exe
# then move zcp.exe to a directory on your PATHVerify
zcp version
zcp --helpv0.0.20
zcp v0.0.20 Release Notes
Deleting a VM now releases its auto-assigned public IP
When a VM is created public, the CMP auto-assigns it a 1:1 public IP. Until now,
deleting the VM left that IP Allocated — orphaned and still billing — until someone
released it by hand. zcp instance delete now releases it as part of the delete, matching
the "Delete auto-assigned public IPs when deleting VM" option in the portal.
Highlights:
zcp instance deletereleases the VM's auto-assigned public IP by default (sends
delete_public_ip=true).- Manually-acquired and source-NAT IPs are untouched — they release only when their
network/IP is removed. - Opt out with
--delete-public-ip=falseto keep the IP (e.g. when it's reused by NAT, a
load balancer, or a shared network).
Added
zcp instance delete releases the auto-assigned public IP
zcp instance delete my-vm --yes # VM gone, its auto-assigned IP released
zcp instance delete my-vm --force --yes # also expunge from the hypervisor immediately
zcp instance delete my-vm --delete-public-ip=false # VM gone, keep the IP allocated--delete-public-ip defaults to true. It only releases public IPs that the CMP assigned
when the VM was created — manually-acquired IPs and the network's source-NAT IP are never
touched by this flag (those release when you delete the network/IP). The interactive
confirmation prompt now says when the IP will be released:
WARNING: Delete "my-vm" is permanent and cannot be undone. Its auto-assigned public IP will also be released. [y/N]:
Changed
- Behavior change:
zcp instance deleteused to leave the VM's auto-assigned public IP
allocated (and billing) after the VM was deleted. It is now released by default. Add
--delete-public-ip=falseto keep the old behavior.
Upgrade notes
If you have scripts or automation that delete VMs and then reuse the freed public IP (for a
NAT rule, load balancer, or shared network), pass --delete-public-ip=false so the IP stays
allocated. Otherwise no changes are needed — the new default cleans up the leaked IPs you'd
previously have had to release manually with zcp ip release.
Installation
Linux / macOS / WSL (one-liner)
curl -fsSL https://github.com/zsoftly/zcp-cli/releases/latest/download/install.sh | bashInstalls zcp to /usr/local/bin (you may be prompted for sudo). Set INSTALL_DIR to
choose another location, e.g. INSTALL_DIR="$HOME/.local/bin".
Windows (PowerShell)
irm https://github.com/zsoftly/zcp-cli/releases/latest/download/install.ps1 | iexInstalls zcp.exe to %LOCALAPPDATA%\Programs\zcp.
Manual download
Grab the binary for your platform from the
Releases page, make it executable, and put it on
your PATH.
| OS | Arch | Asset |
|---|---|---|
| Linux | x86_64 | zcp-linux-amd64 |
| Linux | ARM64 | zcp-linux-arm64 |
| macOS | Intel | zcp-darwin-amd64 |
| macOS | Apple Silicon | zcp-darwin-arm64 |
| Windows | x86_64 | zcp-windows-amd64.exe |
| Windows | ARM64 | zcp-windows-arm64.exe |
# Linux amd64 example
curl -Lo zcp https://github.com/zsoftly/zcp-cli/releases/latest/download/zcp-linux-amd64
chmod +x zcp
sudo mv zcp /usr/local/bin/zcp# Windows amd64 example (PowerShell)
irm https://github.com/zsoftly/zcp-cli/releases/latest/download/zcp-windows-amd64.exe -OutFile zcp.exe
# then move zcp.exe to a directory on your PATHVerify
zcp version
zcp --helpv0.0.19
zcp v0.0.19 Release Notes
Load balancers create cleanly, and instances are addressable by ID or name
This release fixes loadbalancer create (it never worked — the API requires an initial
rule), and makes every instance command accept the VM's ID, name, or slug, not just its
slug. It also paginates the instance list so large accounts see all of their VMs.
Highlights:
zcp loadbalancer createworks — it now sends a required first rule
(--public-port/--private-port/--algorithm) and can attach back-ends with--vm.- Refer to an instance by ID, name, or slug in every
instancesubcommand, with a clear
error when a name is ambiguous. instance listshows theIDcolumn and returns all VMs (the list is now paginated).rebootrefuses a non-RunningVM instead of silently no-op'ing.- Manage account access control — new
zcp sub-user,zcp role, andzcp permission
commands for creating sub-users, defining roles from permissions, and blocking/unblocking access.
Fixed
zcp loadbalancer create always failed
The request sent an empty rules array, which the API rejects — a load balancer must be created
with at least one rule. Create now builds that first rule from new flags:
zcp loadbalancer create --name my-lb --network <network-slug> --ip <ip-slug> \
--billing-cycle hourly \
--public-port 80 --private-port 8080 --algorithm roundrobin \
--vm web-1 --vm web-2 # optional: attach back-ends
# add more rules later
zcp loadbalancer create-rule <lb-slug> --name api-rule \
--public-port 8443 --private-port 443 --protocol tcp --algorithm leastconn--public-port, --private-port, and --algorithm are required (the rule can't be formed
without them). --protocol defaults to tcp, --rule-name defaults to <lb-name>-rule, and
--sticky-method, --enable-tls, and --enable-proxy-protocol are optional.
instance list only returned the first page of VMs
The /virtual-machines endpoint is paginated, but the CLI fetched a single page — accounts with
more VMs than fit on one page silently lost the rest. The list now walks every page, which also
makes instance reference resolution (below) reliable.
Added
Address an instance by ID, name, or slug
Every instance subcommand — get, start, stop, reboot, reset, delete, logs, ssh,
tag-*, change-*, add-network, addons, purchase-addon — now accepts any unique reference
to the VM:
zcp instance reboot vm-1a2b3c # by ID (vm_id)
zcp instance reboot my-web-server # by name
zcp instance reboot my-web-server-1 # by slugMatch order is ID/vm_id, then name, then slug. If a name matches two VMs, the command lists the
matching IDs and asks you to pick one. Resolution checks your active region/project first and
falls back to an unscoped lookup when the reference isn't found there, so a globally-unique ID
or slug still works without --region.
ID column in instance output
zcp instance list and zcp instance get now show the instance ID (the value to copy for the
references above). -o json/-o yaml and --debug expand to the full set of columns.
Manage sub-users, roles, and permissions
Account access control is now scriptable. These are account-level commands — no --region/--project
needed.
# Permissions: the read-only catalog you build roles from
zcp permission list
zcp permission list --category "Virtual Machine"
# Roles: group permissions, then assign to sub-users
zcp role list
zcp role get service-administrator # shows its permissions + assigned users
zcp role create --name "VM Operator" \
--permission virtual-machine-read --permission virtual-machine-manage
zcp role update vm-operator --permission virtual-machine-read --permission dns-read
zcp role delete vm-operator
# Sub-users: additional users under your account (addressable by id OR email)
zcp sub-user create --name "Jane Doe" --email jane@yourco.com \
--password 'S3cret!pass' --role service-viewer --project default-9
zcp sub-user update jane@yourco.com --role service-administrator
zcp sub-user block jane@yourco.com # revoke access without deleting
zcp sub-user unblock jane@yourco.com
zcp sub-user delete jane@yourco.comNotes: --permission on a role replaces the role's full set (it isn't additive), and role update
preserves any flags you don't pass. The predefined owner, service-administrator, and
service-viewer roles can't be edited or deleted. Sub-user --email must be a company address,
--password needs 8+ chars with mixed case, a number, and a symbol, and newly created sub-users start
blocked until you unblock them.
Changed
zcp instance rebootrefuses a VM that isn'tRunning, e.g.
instance "my-vm" is Stopped; it must be Running before it can be rebooted, instead of issuing a
reboot the platform silently ignores.zcp loadbalancer listandzcp instance listemit full objects for-o json/-o yaml
rather than a flattened, all-string copy of the table — automation gets every field.zcp auth validatehonorsZCP_DEBUGlike every other command.- Documentation URL is now
https://docs.zcp.zsoftly.ca.
Upgrade notes
zcp loadbalancer create now requires --public-port, --private-port, and --algorithm.
Scripts that called create without them will need to add these flags (the command previously
failed at the API anyway). Everything else is additive — existing slug-based instance commands keep
working unchanged.
v0.0.18
zcp v0.0.18 Release Notes
Region- and project-correctness: nothing runs unscoped
This release makes the CLI region- and project-aware everywhere, so it can no longer show
catalog entries that don't exist in your region or deploy a resource into the wrong zone. It also
fixes the SSH-key import flow and surfaces real API validation messages.
Highlights:
--regionand--projectare now mandatory for every region/project-scoped command, with
per-profile defaults captured atzcp profile add(likeaws configure).- Every list filters by region and project — no more cross-region clutter or un-deployable
plans in the output. zcp ssh-key importworks (it requiredproject+regionall along).- 422 validation errors now show the field-level reason instead of a generic message.
Fixed
zcp ssh-key import always returned 500 … Attempt to read property "id" on null
The API derives the cloud provider from both project and region, and the CLI marked them
optional — so a call without them sent neither and the backend dereferenced a null. --project and
--region are now required (honoring ZCP_PROJECT/ZCP_REGION) and always sent. Verified
end-to-end: import → list → reference at VM create (the VM came back with the key attached) → delete.
--name is also validated client-side (≤ 20 chars) before the call.
API validation errors were swallowed
HTTP 422 responses returned only a generic Validation errors. This API puts the field-level
messages under data (not errors) and omits status, so they were dropped. They're now surfaced:
Error: ... API error 422: Validation errors — public_key: The public key has already been taken.
Error: ... API error 422: Validation errors — name: The name field must not be greater than 20 characters.
Catalog and resource listings returned every region's entries
zcp plan vm listed both YUL (ca*) and YOW (ci*) offerings; picking a wrong-region plan (an
Intel ci* plan in YUL) then failed to schedule ("no destination found") — the VM sat in
Starting, flipped to Error, and was cleaned up with no IP, which looked like a boot failure. The
CLI now sends filter[region]/filter[project] on every list, so you only ever see entries valid
for your region and project.
This does not fix the underlying CMP catalog, which still presents cross-region offerings as
selectable for a target region — that needs region-scoped offering filtering in the plan catalog.
Added
Region + project are required everywhere (with profile defaults)
Every region/project-scoped command now requires a region and a project. Satisfy them three ways:
# 1. Per-profile defaults (recommended) — captured at configure time, like `aws configure`
zcp profile add default # prompts for token, default region, and default project
# 2. Environment variables
export ZCP_REGION=yow-1 ZCP_PROJECT=default-9
# 3. Per command
zcp instance list --region yow-1 --project default-9Account-level commands have no region/project dimension and are exempt: dns, auth, profile,
region, project, cloud-provider, currency, billing-cycle, server, support, dashboard,
billing, product, store. (object-storage is scoped too; it uses the os-yul/os-yow
regions.)
Every list is now scoped to your region and project
Lists send filter[region]/filter[project] and return only what belongs to that region/project —
instances, networks, IPs, volumes, VPCs, Kubernetes clusters, load balancers, virtual routers,
autoscale groups, affinity groups, block-storage and VM snapshots, block-storage and VM backups,
object storage, and the full catalog (plan, template, iso, marketplace, storage-category).
For example, zcp plan vm --region yul-1 now lists only the ca* family YUL actually runs (the
Intel ci* plans appear only under yow-1).
zcp profile add captures a default region and project
Like aws configure, profile add now prompts for (and requires) a default region and a
default project, stored in the profile and used whenever --region/--project and
ZCP_REGION/ZCP_PROJECT are not set:
zcp profile add default \
--bearer-token <token> --region yow-1 --project default-9Changed
zcp instance create --ssh-key <name>now sendsauthMethod: "ssh-key"(and an empty
password) alongside the key name, matching the Web UI's VM-create payload — previously the
key name was sent without the auth-method flag, so SSH-key auth would not engage.- Docs/help clarified: SSH key names and the public-key material must be unique (re-importing
the same key, even under a new name, is rejected); and a VPC alone cannot host a VM — create
a network (tier) inside it (zcp network create --vpc …) and attach a VM to that tier
(zcp instance add-network), since a bare VPC has no usable subnet.
Upgrade notes
This release requires a region and project for scoped commands. If you have scripts that ran
zcp instance list, zcp plan vm, etc. without one, either re-run zcp profile add to store
defaults, export ZCP_REGION/ZCP_PROJECT, or add --region/--project. Account-level commands
(listed above) are unaffected.
v0.0.17
zcp v0.0.17 Release Notes
Clearer errors, and no backend technology in output
This release is a CLI-usability and information-hygiene pass:
- Actionable argument errors across every command that takes positional arguments
(128 subcommands) — what's missing, the usage line, and the command's own examples. - Unknown subcommands now error (non-zero exit) instead of silently printing help.
- Backend technology names are no longer exposed in any command output.
--cloud-provideris auto-detected and saved to your profile — you no longer pass it.
Added
object-storage object download
Objects can now be downloaded to a local file over the S3 protocol — previously the CLI
could upload, list, delete, and show metadata, but not fetch an object's contents.
zcp object-storage object download my-store my-bucket report.pdf # → ./report.pdf
zcp object-storage object download my-store my-bucket images/logo.png --dest ./logo.png--dest accepts a file path or a directory (writes the object's base name into it); it
defaults to the base name in the current directory.
Object versioning and raw bucket policies
zcp object-storage bucket versioning enable|suspend|status— S3 object versioning.zcp object-storage bucket policy get|set|delete— read/set/remove a bucket's raw S3
policy (set --file policy.json, or--file -for stdin) for fine-grained access.
Both verified live against Ceph RGW.
Tags, encryption, lifecycle, and a versioned-bucket fix
-
bucket tag/object tag— set/get/delete tags (--tag key=value). -
bucket encryption status|enable|disable— default SSE-S3 encryption. -
bucket lifecycle expire --days N [--prefix P](plusget/delete) — auto-expire objects. -
bucket emptyandbucket delete --purge— remove all objects and versions. This
fixes a real gap: a bucket that ever had versioning enabled couldn't be deleted, because
its object versions/delete-markers blocked the REST delete. -
bucket cors set --origin --method [--header --max-age](plusget/delete) —
cross-origin rules for browser apps.
RGW support for each was verified by probing the live endpoint with the S3 client before
building.
Versioning workflows, copy/move, stat, presigned upload, multipart cleanup
The object-storage S3 surface is now feature-complete (everything Ceph RGW supports
except object-lock, which needs a backend change at bucket creation). All of these
operations are CLI-only — they talk directly to the Ceph RADOS Gateway over the
S3 protocol and are not yet available via the ZCP REST API or the Web UI (only
instance and basic bucket CRUD are REST-backed and mirrored in the Web UI):
- Versioning is usable:
object versions,object download/delete --version-id,
andobject restore(undelete). object copy/object move— server-side, no round-trip.object stat(full S3 metadata via HEAD) andobject put --metadata key=value.object put-url— pre-signed upload URL (curl -T file "<url>").bucket uploads list|abort— reclaim storage from failed large uploads.bucket lifecycle expire --noncurrent-days / --abort-multipart-days— expire old
versions and clean up stalled uploads.policy/lifecycle/cors getnow honor-o yaml.
Public buckets, shareable object URLs, and plan discovery
zcp object-storage bucket set-acl --acl public-readmakes a bucket's objects
anonymously downloadable (via an S3 bucket policy);--acl privatereverts it.zcp object-storage object url <slug> <bucket> <key> [--expires 24h]mints a
pre-signed, time-limited link a client can use without credentials — even on a
private bucket (max 7 days).zcp plan object-storagelists the Object Storage plan slugs forcreate --plan.
These were verified end-to-end against live Ceph storage in YOW (create → bucket →
upload → make public → anonymous download → pre-signed URL → make private → delete).
Object-storage create is simpler
object-storage create --plan <slug> no longer needs --storage-category — the CLI
derives it from the plan (a mismatch previously failed with Invalid Storage Category).
Use an object-storage region (os-yul/os-yow) and a plan from zcp plan object-storage.
Changed
Helpful errors instead of accepts 1 arg(s), received 0
Before:
❯ zcp profile add
Error: accepts 1 arg(s), received 0
After:
❯ zcp profile add
Error: missing required argument: <name>
Usage:
zcp profile add <name> [flags]
Examples:
zcp profile add default
zcp profile add prod --bearer-token <token>
Multi-argument commands name each missing placeholder:
❯ zcp acl create-rule
Error: missing required arguments: <vpc-slug>, <acl-name-or-id>
Usage:
zcp acl create-rule <vpc-slug> <acl-name-or-id> [flags]
Examples:
zcp acl create-rule my-vpc web-acl --number 1 --protocol tcp --start-port 80 --end-port 80 --cidr 0.0.0.0/0
...
Supplying too many arguments now reports too many arguments: expected N, got M with the
same usage/examples block.
This is purely a messaging change: which commands accept how many arguments is unchanged,
and all existing behavior on the success path is identical.
Unknown subcommands error instead of silently printing help
Running a command group with a bad subcommand used to print that group's help and exit 0,
hiding the typo and letting scripts treat a mistake as success. It now errors to stderr and
exits non-zero, with a message that adapts to the group. A group with one subcommand points
straight at it:
❯ zcp region lists
Error: unknown subcommand "lists" for "zcp region"
Run this instead:
zcp region list List available regions
Example:
zcp region list
A group with several lists the valid subcommands and suggests the closest match:
❯ zcp profile shw
Error: unknown subcommand "shw" for "zcp profile"
Did you mean this?
show
Available commands:
add Add or update a profile
...
Run 'zcp profile --help' for usage and examples.
Running a group with no arguments still prints help and exits 0.
--cloud-provider is auto-detected
Customers no longer pass a cloud provider. zcp auth validate (and zcp profile add)
detect the account's compute provider — the one whose catalog includes "Virtual
Machine" (nimbo in production) — and save it to the profile; every create command
reads it automatically. Object storage and DNS default to their own providers (ceph
and dns) automatically. The flag is hidden from help but remains available as an
override (along with ZCP_CLOUD_PROVIDER).
❯ zcp auth validate
Credentials are valid.
Cloud provider detected and saved to profile "default": nimbo
If it can't be determined, create commands say so with guidance instead of the old
terse "--cloud-provider is required".
All three provider values were confirmed against the live API: each region maps to
exactly one provider (yow-1/yul-1→nimbo, default→dns, os-yow/os-yul→ceph),
and real existing instances/volumes store cloud_provider: nimbo. As part of this,
zcp dns create now defaults --region to default (the DNS provider's only region)
so it is fully hands-off, and object-storage examples use object-storage regions
(os-yul/os-yow) — the previous yow-1/yul-1 DNS and object-storage examples were
wrong and would have failed.
Removed
Backend technology is no longer exposed in output
Display-only columns that revealed the underlying platform ("Cloud Stack", "Ceph", "Dns")
have been removed:
region list— droppedPROVIDERandCOMING SOONbackup list,snapshot list,vm-backup list— droppedSERVICEinstance get— dropped theServicerowcloud-provider list— droppedDISPLAY NAMEdns list/dns show/dns create— dropped theDNS PROVIDERcolumn/row, and hid the--dns-providerflag (which named the backend, e.g.powerdns)
These were informational only. Resource creation uses the provider/region slug, which is
retained, so no workflow changes. Billing/dashboard/project "SERVICE" columns are untouched —
they name billing categories (e.g. "Virtual Machine"), not backend technology.
Internal
- New
internal/commands/args.goprovides drop-inexactArgs/minArgs/maxArgs/
rangeArgsvalidators that replacecobra.ExactArgs/cobra.MaximumNArgspackage-wide.
Missing-argument names are derived from each command'sUseline and examples from its
Examplefield, so no per-command wiring is needed and new commands get the behavior for
free by using the helpers. EnforceSubcommandErrors(same file) walks the command tree once — called fromroot.go
after all subcommands are registered — and installs the unknown-subcommand handler on every
command group, so the behavior applies uniformly and to future groups automatically.
v0.0.16
zcp v0.0.16 Release Notes
VPC Subnets, Network ACLs, and Plan Discovery
This release makes the full three-tier VPC workflow possible from the CLI: create a VPC,
create subnets (tiers) inside it, and attach custom network ACLs — none of which worked in
v0.0.15. All fixes were confirmed against the live API (a three-tier VPC was built end-to-end
in the YUL region with per-tier ACLs as the release verification).
Added
VPC subnet (tier) creation — zcp network create --vpc
zcp network create --name web-tier --vpc my-vpc --acl web-acl \
--gateway 10.30.1.1 --netmask 255.255.255.0 --billing-cycle hourly \
--cloud-provider nimbo --region yul-1 --project defaultThe API requires type=Vpc with that exact casing — any other value passes validation but
silently creates a detached isolated network. The CLI now always sends the correct type and
rejects conflicting flags, so this trap is no longer reachable.
--acl attaches a custom ACL right after creation (the API has no attach-at-create
parameter); names are resolved to ACL IDs automatically.
zcp network get <slug>
Shows provider-side state that no command exposed before: CIDR, gateway, netmask, state,
VPC membership, and the attached ACL.
zcp plan network
Lists Network plans (pNet/iNet/l2Net) with slugs — the values for the new
--network-plan flag, which isolated/L2 network creation requires. The old --category
flow never worked against the live API (the categories endpoint returns an empty list);
--category is kept as an optional legacy flag.
zcp acl delete <vpc> <acl>
The platform now supports ACL list deletion; the CLI exposes it with name resolution and a
confirmation prompt.
ACL rule management — zcp acl rules / create-rule / update-rule / delete-rule
zcp acl create-rule my-vpc web-acl --number 1 --protocol tcp \
--start-port 443 --end-port 443 --cidr 0.0.0.0/0 --action allow --traffic-type ingressThe rule endpoints live under .../network-acl-list/{id}/network-acl (singular). The order
of operations matters: create the ACL list first, then add rules one per request — an
embedded rules array on list creation is silently ignored. create-rule mirrors the live
validation (ports for tcp/udp, ICMP type/code for icmp) and resolves ACL names to IDs.
update-rule modifies a rule in place (rule ID preserved); --cidr accepts comma-separated
multi-CIDR lists on both create and update.
Fixed
acl replace/vpc acl-replacealways failed with 403 — body field wasaclSlug;
the API requiresacl_id(the ACL's ID). Both now send the right field, and--vpc
resolves ACL names to IDs.vpc get/vpc createshowed blank CIDR/Status/Zone — now read from
GET /vpcs/{slug}(CloudStackmetablock) with a list fallback for older deployments.- All plan tables missing the SLUG column —
vpc create --planneeds a slug, but
plan routeronly showed UUIDs. Every plan table now includes SLUG. - Create-response decode crash —
is_defaultarrives as0/1on create but
true/falseon list; the decoder accepts both. acl listblank columns — now shows ID / NAME / DESCRIPTION (matching the live API).- VPC network-address quirk — the API stores the address verbatim (
10.30.0.1/16);
the CLI warns when it isn't the canonical network base.
Known platform limitations (tracked in docs/roadmap.md)
- 3 subnets per VPC (CloudStack
vpc.max.networks) — the 4th create returns a generic 403. - VPC
descriptionis not persisted by the API.
v0.0.15
zcp v0.0.15 Release Notes
Network / VPC / VPN Bug Fixes
This release fixes a set of related bugs across the VPC, VPN, and network commands where the
CLI was sending or receiving incorrect JSON field names — causing commands to return blank
output, crash with a decode error, or silently ignore updates. All issues were confirmed against
the live API before release.
Fixed
VPC VPN gateway — fields always blank / crash on create
zcp vpc vpn-gateway create and list both returned empty rows.
Root cause: VPNGateway struct used wrong JSON tags (slug, publicIpAddress, vpcUuid,
vpcSlug, zoneName, status). The live API returns id, public_ip, vpc_id, vpc_name.
Additionally, the create endpoint returns {"data":null} instead of the created object.
Fix: Corrected all JSON tags; create now falls back to listing and returning the first
matching gateway when the API returns null data.
zcp vpc update — returns empty fields
PUT /vpcs/{slug} always responds with data:null. The command was unmarshaling null into
an empty struct and displaying blank values.
Fix: After any null PUT response, the command fetches the VPC via GET and returns the
real updated state.
zcp network update — crashes with JSON decode error
PUT /networks/{slug} returns data:[null] (an array with a null element). Attempting to
unmarshal that into a Network struct caused a hard error.
Fix: Same fallback-to-GET pattern; now handles both null and [null] responses without
crashing.
VPN customer gateway — all VPN config fields blank
zcp vpn customer-gateway create, update, and list all showed empty Gateway, IKE Policy,
CIDR, and other fields.
Root causes:
- Three JSON tags were wrong:
ipsec_preshared_key→ipsecpsk,force_encapsulation→
forceencap,dead_peer_detection→dpd SplitConnectionswas typed asboolbut the API returns it as a string- Create API returns a metadata-only response (no VPN config); a subsequent GET is now made
to the detail endpoint/vpn-customer-gateways/{slug}
Fix: All tags corrected; CustomerGatewayService.Get() added; create falls back gracefully
when CloudStack provisioning is still in progress.
VPN customer gateway — missing required API fields
The --ike-dh, --esp-dh, and --esp-pfs CLI flags were missing. The API rejects creates
without ike_dh and esp_pfs, so zcp vpn customer-gateway create could never succeed.
Fix: Three new flags added to both create and update.
VPN user list — Username column always blank
User.UserName had JSON tag userName; the API returns username.
Fix: Tag corrected; username now appears in zcp vpn user list.
zcp network update --description "" / zcp vpc update --description "" ignored
description in both network.UpdateRequest and vpc.UpdateRequest had omitempty.
Sending an empty string was silently dropped, making it impossible to clear a description.
Fix: omitempty removed from both structs.
zcp ip allocate — cannot assign to a project
No --project flag existed on zcp ip allocate, and the CreateRequest struct lacked the
project field entirely.
Fix: Field added to the struct; --project flag exposed on the command.
VPN customer gateway update — all fields blank + unusable without cloud context
zcp vpn customer-gateway update returned blank VPN config fields (gateway, IKE policy, CIDR,
etc.) because the PUT response is a metadata-only envelope — the VPN config fields are only
returned by the GET detail endpoint. Additionally, the update command was missing
--cloud-provider, --region, and --project flags, which the API requires on every PUT,
making the command return a server-side validation error in all cases.
Fix: Update() now always falls back to GET /vpn-customer-gateways/{slug} to retrieve
the full VPN configuration. The update command now accepts and validates --cloud-provider,
--region, and --project (resolving from env vars as with create).
New service methods (for Go library consumers)
| Method | Description |
|---|---|
vpn.CustomerGatewayService.Get(ctx, slug) |
Fetch full VPN config for a single customer gateway |
network.Service.Get(ctx, slug) |
Fetch a single network by slug |
Installation
macOS / Linux / WSL:
curl -fsSL https://github.com/zsoftly/zcp-cli/releases/latest/download/install.sh | bashWindows (PowerShell):
irm https://github.com/zsoftly/zcp-cli/releases/latest/download/install.ps1 | iexVerify:
zcp version
# zcp version v0.0.15Full Changelog
v0.0.14
zcp v0.0.14 Release Notes
⚠ Breaking Changes — API package paths changed
This release moves all API service packages and the HTTP client out of internal/
into pkg/ so that external Go modules (such as the ZCP Terraform provider) can
import them directly.
CLI end users are not affected. The binary is identical to v0.0.12.
Migration guide for Go library consumers
Run a global find-and-replace in your project:
# macOS / BSD sed
find . -name '*.go' | xargs sed -i '' \
's|github.com/zsoftly/zcp-cli/internal/httpclient|github.com/zsoftly/zcp-cli/pkg/httpclient|g'
find . -name '*.go' | xargs sed -i '' \
's|github.com/zsoftly/zcp-cli/internal/api/|github.com/zsoftly/zcp-cli/pkg/api/|g'
# Linux sed
find . -name '*.go' | xargs sed -i \
's|github.com/zsoftly/zcp-cli/internal/httpclient|github.com/zsoftly/zcp-cli/pkg/httpclient|g'
find . -name '*.go' | xargs sed -i \
's|github.com/zsoftly/zcp-cli/internal/api/|github.com/zsoftly/zcp-cli/pkg/api/|g'Then update your go.mod to reference v0.0.14:
go get github.com/zsoftly/zcp-cli@v0.0.14
go mod tidyWhat moved
| Old | New |
|---|---|
internal/httpclient |
pkg/httpclient |
internal/api/acl |
pkg/api/acl |
internal/api/affinitygroup |
pkg/api/affinitygroup |
internal/api/apierrors |
pkg/api/apierrors |
internal/api/autoscale |
pkg/api/autoscale |
internal/api/backup |
pkg/api/backup |
internal/api/billing |
pkg/api/billing |
internal/api/billingcycle |
pkg/api/billingcycle |
internal/api/cloudprovider |
pkg/api/cloudprovider |
internal/api/currency |
pkg/api/currency |
internal/api/dashboard |
pkg/api/dashboard |
internal/api/dns |
pkg/api/dns |
internal/api/egress |
pkg/api/egress |
internal/api/firewall |
pkg/api/firewall |
internal/api/instance |
pkg/api/instance |
internal/api/ipaddress |
pkg/api/ipaddress |
internal/api/iso |
pkg/api/iso |
internal/api/kubernetes |
pkg/api/kubernetes |
internal/api/loadbalancer |
pkg/api/loadbalancer |
internal/api/marketplace |
pkg/api/marketplace |
internal/api/monitoring |
pkg/api/monitoring |
internal/api/network |
pkg/api/network |
internal/api/objectstorage |
pkg/api/objectstorage |
internal/api/plan |
pkg/api/plan |
internal/api/portforward |
pkg/api/portforward |
internal/api/product |
pkg/api/product |
internal/api/project |
pkg/api/project |
internal/api/region |
pkg/api/region |
internal/api/response |
pkg/api/response |
internal/api/server |
pkg/api/server |
internal/api/snapshot |
pkg/api/snapshot |
internal/api/sshkey |
pkg/api/sshkey |
internal/api/storagecategory |
pkg/api/storagecategory |
internal/api/store |
pkg/api/store |
internal/api/support |
pkg/api/support |
internal/api/template |
pkg/api/template |
internal/api/userprofile |
pkg/api/userprofile |
internal/api/virtualrouter |
pkg/api/virtualrouter |
internal/api/vmbackup |
pkg/api/vmbackup |
internal/api/vmsnapshot |
pkg/api/vmsnapshot |
internal/api/volume |
pkg/api/volume |
internal/api/vpc |
pkg/api/vpc |
internal/api/vpn |
pkg/api/vpn |
Packages that remain internal
These are CLI-only and are not part of the public API:
internal/commandsinternal/configinternal/outputinternal/version
Note on release tag format
Starting with this release, tags use the v prefix (v0.0.14) to align with
Go module conventions and the Terraform Registry. Previous tags (0.0.1–0.0.12)
are preserved but the old format will not be used going forward.
Installation
macOS / Linux / WSL:
curl -fsSL https://github.com/zsoftly/zcp-cli/releases/latest/download/install.sh | bashWindows (PowerShell):
irm https://github.com/zsoftly/zcp-cli/releases/latest/download/install.ps1 | iexVerify:
zcp version
# zcp version v0.0.14