Skip to content

phctl v0.17.0

Choose a tag to compare

@andreeapid andreeapid released this 18 Aug 15:01
· 23 commits to main since this release

Added

  • compute server operations: usage, activity, boot-isos, rescue enter / rescue exit, retry-provision, and public-interface get / set / delete. rescue enter and rescue exit reboot the server, so they confirm first; public-interface delete costs the server its public connectivity, so it does too.

    rescue enter, rescue exit and retry-provision answer 200 with {"queued": false} or {"retry": false} when they decline the work, so each checks the flag rather than the status code. boot-isos is scoped to a server rather than listed globally because compatibility depends on that server's disk and RAM, and it pages through cmdutil.FetchAll; its min_ram is a decimal the API sends as a string, rendered as one.

    public-interface set is a POST that the SDK calls Create, but it updates firewall settings on an interface that already exists — the addresses and interface name are read-only server-side. Only --firewall, --policy-in and --policy-out are writable, an unknown policy is rejected before the request rather than silently ignored, and calling it with no flags is an error rather than a no-op round trip.

  • compute ipv6 reverse-dns: the IPv4 equivalent already existed; IPv6 PTR records could only be read or set from the panel.

  • phctl storage bucket: object storage had no CLI surface at all. list, get, create, delete, resize, visibility, and credentials reveal / credentials rotate. It is a top-level group rather than compute bucket because compute volume is block storage attached to a server and the two are easy to confuse.

    create and resize confirm first because they change what the account is billed; creating a public bucket also warns that uploaded objects will be internet-readable. delete confirms because it takes the objects with it; visibility --public confirms because it exposes every object to the internet, while --private only removes access and does not. resize and visibility check the bucket actually came back changed — the routes answer 200 with the current state, so trusting the status code alone would report a resize that never happened. Asking for the value a bucket already has is a no-op that succeeds, so a script can converge on a desired state without reading it first.

    credentials reveal prints an access key and secret, so it asks before doing so unless -f is passed. Both credential commands wrap failures with a redacting error wrapper that also removes SDK errors from the unwrap chain and distrusts response reason phrases. Those routes answer 200 with the secret in the body, and a body that fails to decode would otherwise put live credentials into an error string — and, for rotate, the new secret, after the old pair is already dead. A failed rotation tells the operator to reveal the current pair instead of retrying the non-idempotent operation.

    storage product list is deliberately absent: StorageProduct.price is a decimal, and pricing endpoints still go through internal/client's Raw* types.

  • compute server detach-ipv6: attach-ipv6 has existed for a while and detach-ipv4 was already there, but an IPv6 could only be attached, never removed, from the CLI.

  • compute server resize --package <id|slug>: change a server's package. Downgrades are only accepted between packages with the same disk size, and the server restarts, so it confirms first unless -f is passed.

Fixed

  • billing deposit create no longer mangles the amount it reports. The API sends decimals as JSON strings, and sdk-go mapped them to float64 up to v0.11.0 — so the command failed to decode the response after the deposit was created. On v0.12.2 the field is a string, and the %.2f it was rendered with printed %!f(string=12): the .2 precision silently truncated 125.00 to two characters. It renders as sent now.
  • API errors now keep the response body across every command. 85 call sites in account, billing, dedicated, domain, freedns, hosting, kubernetes and support wrapped the SDK error with fmt.Errorf, which discards the body — the only part that says what was actually wrong. A rejected ticket used to report creating ticket: 400 Bad Request; it now reports creating ticket: 400 Bad Request: department=This field is required. compute had already been converted; the rest had not. A contract test in cmd/apierror_contract_test.go keeps it that way.
  • -f/--force is now resolved the same way -o/--output is. It was read through Flags().GetBool, which only sees a root's persistent flags after Cobra merges them during Execute, so it returned false for any command driven directly through RunE.