feat: template sub-resources, hosted resources, and project/server actions#32
feat: template sub-resources, hosted resources, and project/server actions#32MartaKar wants to merge 2 commits into
Conversation
…r actions
Extend CLI coverage of the DeployHQ API (DHQ-639, phases 4-7):
- project/server actions: regenerate-key, undeployed-changes, ai-overview,
servers from-global, servers metrics, config-files/ssh-commands link-global
- hosted resources & managed hosting: dhq hosted-resources, dhq managed-hosting
- template sub-resources: dhq templates {config-files,servers,build-*,...}
- long-tail: dhq ip-ranges, dhq plans, dhq invoices, dhq detect, dhq beta enroll
Adds an unauthenticated SDK client path (NewPublic) so the public ip-ranges and
plans endpoints work without credentials. Each resource ships an SDK client,
cobra command, agent metadata, assist allowlist entry, and unit tests. Verified
end-to-end against a live backend.
WalkthroughAdds SDK and CLI support for managed hosting, hosted resources, template subresources, project/server actions, global resource linking, billing utilities, IP ranges, framework detection, beta enrollment, and revised managed VPS data models. ChangesManaged resources and SDK APIs
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant CLI
participant SDK
participant DeployHQAPI
CLI->>SDK: Execute managed-resource or template operation
SDK->>DeployHQAPI: Send authenticated or public HTTP request
DeployHQAPI-->>SDK: Return resource, catalog, status, or error
SDK-->>CLI: Decode response
CLI-->>CLI: Render JSON, quiet, table, or status output
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 18
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/assist/prompt.go`:
- Line 133: Update the templates command help text in the prompt definition to
list valid actions separately for each sub-resource, rather than using one
generic <action> placeholder; reflect that config-files supports show,
build-known-hosts supports list/create/delete, and excluded-files has no show
action, along with the corresponding metadata for all listed resources.
- Line 145: The CLI help text should document invoice listing and downloading as
separate command syntaxes rather than combining them with “list|download
<number>”. Update the relevant help text in the prompt definition to show `dhq
invoices list` without an argument and `dhq invoices download <number>` with the
invoice number.
In `@internal/commands/hosted_resources.go`:
- Line 84: Update the table formatting in the hosted resource rendering logic to
avoid hardcoding "$" for HostedResource.MonthlyCost, which uses the account
billing currency. Use the API-provided currency when available, or render the
amount without a currency symbol as a fallback.
In `@internal/commands/invoices.go`:
- Line 94: Update the invoice download write logic in the command’s file-writing
path to use mode 0o600 instead of 0o644, and ensure existing output files are
also restricted to owner-only permissions when overwritten.
In `@internal/commands/launch_test.go`:
- Around line 502-507: The managed-hosting mocks in the launch tests still use
the legacy response schema. Update the adjacent /managed_hosting
httptest.NewServer payloads to the revised grouped-region shape with label and
monthly_cost fields, then add a matching live JSON golden fixture and validation
in integration_test.go so the new SDK contract is covered.
In `@internal/commands/launch.go`:
- Line 895: Update the size-resolution logic in the launch command so
monthlyCost is populated for both default and explicitly requested sizes. Ensure
the API lookup runs for the requested size slug when size is provided, while
preserving the default-size path, then format the resolved MonthlyCost
consistently as "$%.2f" before producing the dry-run output.
- Line 1448: The price formatting at the affected size-label and
provisioning-confirmation paths hardcodes "$" despite MonthlyCost using the
account currency. Add a centralized price-formatting helper that accepts the
amount and SDK-provided Currency ISO code, use it at the code around the
existing fmt.Sprintf calls (including all referenced paths), and add a non-USD
test verifying the displayed currency.
- Around line 1480-1484: Update the fallback in the managed-hosting region
selection logic around ListManagedHostingRegions instead of blindly using
regions[0]. Preserve or expose the API’s RegionGroup.Default slug through the
SDK, then locate the matching region by slug and assign it to region and
selectedRegion before provisioning, with an explicit fallback only if no default
match exists.
In `@internal/commands/packages.go`:
- Around line 29-44: Check the quiet-mode branch before rendering output in the
package plans command: when the envelope indicates quiet mode, emit only each
plan’s identifier (such as Permalink), one per line, and return without calling
WriteTable or Status. Preserve the existing JSON and normal table behavior,
using the command’s envelope/output helpers consistently.
In `@internal/commands/templates_subresources.go`:
- Around line 184-215: Update all affected update command handlers, including
tcfUpdateCmd and the corresponding handlers at the referenced sections, to
reject requests when none of their mutable resource flags were supplied. Before
constructing the update request, check cmd.Flags().Changed(...) for each
relevant resource flag and return a validation error if none changed; do not use
value checks so explicitly provided empty strings remain valid.
In `@pkg/sdk/client_test.go`:
- Around line 85-116: Update TestPublicClient_NoAuth to serve captured JSON
response literals instead of encoding IPRanges and Package structs, preserving
the expected API field names and values in the raw responses while retaining the
existing assertions.
In `@pkg/sdk/hosted_resources.go`:
- Around line 86-88: Hosted resource IDs are inserted into request paths without
escaping, allowing reserved characters to alter the route. Add a shared helper
using url.PathEscape and use it when constructing paths in GetHostedResource,
SyncHostedResource, and RetryProvisionHostedResource, ensuring all three methods
pass escaped IDs to c.get/c.post.
In `@pkg/sdk/invoices_test.go`:
- Around line 19-26: Replace the handler’s json.Encoder serialization of Invoice
values with a recorded raw JSON fixture/literal matching the expected external
payload, while preserving the same invoice data; update the test setup around
the HTTP handler to serve this payload directly and validate decoding against
the Invoice model.
In `@pkg/sdk/ip_ranges_test.go`:
- Around line 18-27: Replace the json.Encoder call in the test handler with a
raw recorded JSON payload matching the API response shape, rather than encoding
IPRanges and related SDK structs. Preserve the same values while explicitly
writing field names and nested objects so the test independently validates JSON
decoding.
In `@pkg/sdk/managed_hosting.go`:
- Around line 83-94: Make region flattening deterministic in the managed-hosting
response conversion: update the logic around GroupedRegions to collect and sort
group keys before iterating, then flatten each group's Regions in that sorted
group order. Preserve each group's region order unless an explicit sort is
required, and add or update tests with an ordering assertion covering multiple
groups.
In `@pkg/sdk/packages_test.go`:
- Around line 18-25: Replace the json.NewEncoder response in the test handler
with a literal raw JSON fixture matching a recorded DeployHQ API response,
including its exact field names and value types. Keep the fixture focused on the
package data used by the test so decoding is validated independently of the
Package struct’s JSON tags.
In `@pkg/sdk/projects_test.go`:
- Around line 212-215: Close the inline httptest server in
TestGenerateAIDeploymentOverview_RequiresEndRef by storing it in a variable and
registering server.Close with t.Cleanup immediately after creation.
In `@pkg/sdk/template_subresources.go`:
- Around line 116-135: Update CreateTemplateIntegration and
UpdateTemplateIntegration to return a response envelope/union preserving either
the Integration resource or external-auth fields such as auth_required and
auth_url, instead of decoding directly into Integration. Define or reuse the
appropriate response type, ensure both methods decode and return it, and update
all CLI callers to handle the authorization response and continue the auth flow
while retaining normal integration handling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 61da37d9-5aaf-44cc-988e-768537613939
📒 Files selected for processing (44)
internal/assist/prompt.gointernal/cli/context.gointernal/commands/agent_metadata.gointernal/commands/beta.gointernal/commands/config_files.gointernal/commands/detect.gointernal/commands/hosted_resources.gointernal/commands/hosted_resources_test.gointernal/commands/invoices.gointernal/commands/ip_ranges.gointernal/commands/launch.gointernal/commands/launch_test.gointernal/commands/managed_hosting.gointernal/commands/managed_hosting_test.gointernal/commands/packages.gointernal/commands/projects.gointernal/commands/root.gointernal/commands/servers.gointernal/commands/ssh_commands.gointernal/commands/templates.gointernal/commands/templates_subresources.gopkg/sdk/client.gopkg/sdk/client_test.gopkg/sdk/config_files.gopkg/sdk/config_files_test.gopkg/sdk/hosted_resources.gopkg/sdk/hosted_resources_test.gopkg/sdk/invoices.gopkg/sdk/invoices_test.gopkg/sdk/ip_ranges.gopkg/sdk/ip_ranges_test.gopkg/sdk/managed_hosting.gopkg/sdk/managed_hosting_test.gopkg/sdk/packages.gopkg/sdk/packages_test.gopkg/sdk/projects.gopkg/sdk/projects_test.gopkg/sdk/servers.gopkg/sdk/servers_test.gopkg/sdk/ssh_commands.gopkg/sdk/ssh_commands_test.gopkg/sdk/template_subresources.gopkg/sdk/template_subresources_test.gopkg/sdk/types.go
…mmands - launch: resolve monthly cost for explicit --size; format prices with the account currency instead of hardcoded $; select the API default region rather than an arbitrary one - managed-hosting SDK: sort flattened regions deterministically - invoices download: write PDF with 0600 permissions - hosted-resources: drop the false $ prefix (API returns no currency) - plans: honor --quiet - templates: reject update commands with no changed flags - refresh managed-hosting test fixtures to the current API shape
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/commands/templates_subresources.go`:
- Around line 209-214: Replace sdk.ConfigFileCreateRequest in the template
update flow with a dedicated update request DTO using pointer fields for path,
body, and description. In the command handler around requireUpdateFlags and
client.UpdateTemplateConfigFile, populate each pointer only when its
corresponding flag was explicitly provided, so description-only updates omit
path and body.
- Around line 510-513: The template integration update currently sends an empty
hook_type via sdk.IntegrationCreateRequest in the UpdateTemplateIntegration
command. Replace it with a dedicated update request containing only name, or
populate HookType from the existing integration while keeping the immutable hook
type unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: abafba74-d5f8-4686-9827-439ca2810f3b
📒 Files selected for processing (10)
internal/assist/prompt.gointernal/commands/hosted_resources.gointernal/commands/invoices.gointernal/commands/launch.gointernal/commands/launch_test.gointernal/commands/packages.gointernal/commands/templates_subresources.gopkg/sdk/managed_hosting.gopkg/sdk/managed_hosting_test.gopkg/sdk/projects_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
- internal/commands/packages.go
- internal/commands/invoices.go
- internal/commands/hosted_resources.go
- internal/assist/prompt.go
- pkg/sdk/managed_hosting.go
- pkg/sdk/managed_hosting_test.go
- pkg/sdk/projects_test.go
| if err := requireUpdateFlags(cmd, "path", "body", "description"); err != nil { | ||
| return err | ||
| } | ||
| f, err := client.UpdateTemplateConfigFile(cliCtx.Background(), permalink, args[0], sdk.ConfigFileCreateRequest{ | ||
| Path: path, Body: body, Description: description, | ||
| }) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Use an update-specific request type for config files.
requireUpdateFlags now permits --description alone, but sdk.ConfigFileCreateRequest serializes path and body without omitempty. This sends empty required fields for partial updates, potentially rejecting the request or clearing existing values. Use pointer fields in a dedicated update DTO and populate only explicitly changed flags.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/commands/templates_subresources.go` around lines 209 - 214, Replace
sdk.ConfigFileCreateRequest in the template update flow with a dedicated update
request DTO using pointer fields for path, body, and description. In the command
handler around requireUpdateFlags and client.UpdateTemplateConfigFile, populate
each pointer only when its corresponding flag was explicitly provided, so
description-only updates omit path and body.
| if err := requireUpdateFlags(cmd, "name"); err != nil { | ||
| return err | ||
| } | ||
| in, err := client.UpdateTemplateIntegration(cliCtx.Background(), permalink, args[0], sdk.IntegrationCreateRequest{Name: name}) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Do not send an empty hook_type during integration updates.
sdk.IntegrationCreateRequest{Name: name} leaves HookType empty, but its json:"hook_type" field is not optional. The update therefore sends "hook_type": "" despite the command declaring it immutable. Use a dedicated update request containing only name, or preserve the existing hook type.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/commands/templates_subresources.go` around lines 510 - 513, The
template integration update currently sends an empty hook_type via
sdk.IntegrationCreateRequest in the UpdateTemplateIntegration command. Replace
it with a dedicated update request containing only name, or populate HookType
from the existing integration while keeping the immutable hook type unchanged.
Summary
Continues CLI coverage of the DeployHQ API (DHQ-639, phases 4–7). Adds project/server actions, the full template sub-resource tree, managed-hosting resources, and a set of long-tail commands.
dhq projects regenerate-key|undeployed-changes|ai-overviewdhq servers from-global|metricsdhq config-files link-global|unlink-global,dhq ssh-commands link-global|unlink-globaldhq hosted-resources list|show|sync|retry-provision,dhq managed-hosting regions|sizesdhq templates {config-files, excluded-files, integrations, commands, build-commands, build-cache-files, build-known-hosts, build-languages, build-configuration, servers, server-groups}(each takes-t <template>)dhq ip-ranges,dhq plans,dhq invoices list|download,dhq detect,dhq beta enrollNotes
identifier-vs-idkeying, and PATCH-vs-PUT usage match the real API.ip-ranges,plans) return data without authentication. Adds ansdk.NewPublic(account, …)client path so these work without credentials (account still required for the subdomain URL).servers metricsmodels the real point-in-time snapshot:uptimeis a nested object,diskan array of partition objects, plusstatus/lastDeploy/sharedHosting/hostname— all nine keys the collector returns.invoices downloadstreams a binary PDF (SupportsJSON: false);managed-hostingrewrites the previously-incorrect region/size SDK structs to match the live{grouped_regions}/{sizes}envelopes.detect→DetectFramework,beta enroll→EnrollBeta, PDF viadoRaw).Review
Two rounds of multi-model review (Claude + Codex); findings fixed and re-verified — including a
servers metricsdecode bug, the public-endpoint auth path, and JSON-output/metadata gaps on mutation commands.Testing
go test ./...),go vetclean,golangci-lint0 issues.--json/--quiet/table output, 403/404/422 error paths, real PDF download, framework detection) — exceptprojects regenerate-key, which is destructive and is covered by unit tests.Summary by CodeRabbit