Skip to content

feat: template sub-resources, hosted resources, and project/server actions#32

Open
MartaKar wants to merge 2 commits into
mainfrom
feat/api-coverage-hosted-resources-templates
Open

feat: template sub-resources, hosted resources, and project/server actions#32
MartaKar wants to merge 2 commits into
mainfrom
feat/api-coverage-hosted-resources-templates

Conversation

@MartaKar

@MartaKar MartaKar commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

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.

Area Commands
Project actions dhq projects regenerate-key|undeployed-changes|ai-overview
Server actions dhq servers from-global|metrics
Global links dhq config-files link-global|unlink-global, dhq ssh-commands link-global|unlink-global
Managed hosting dhq hosted-resources list|show|sync|retry-provision, dhq managed-hosting regions|sizes
Template sub-resources dhq 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>)
Long-tail dhq ip-ranges, dhq plans, dhq invoices list|download, dhq detect, dhq beta enroll

Notes

  • Contracts were derived from the Rails backend (the bundled OpenAPI spec is stale), so flat-vs-wrapped request bodies, identifier-vs-id keying, and PATCH-vs-PUT usage match the real API.
  • Public endpoints (ip-ranges, plans) return data without authentication. Adds an sdk.NewPublic(account, …) client path so these work without credentials (account still required for the subdomain URL).
  • servers metrics models the real point-in-time snapshot: uptime is a nested object, disk an array of partition objects, plus status/lastDeploy/sharedHosting/hostname — all nine keys the collector returns.
  • invoices download streams a binary PDF (SupportsJSON: false); managed-hosting rewrites the previously-incorrect region/size SDK structs to match the live {grouped_regions}/{sizes} envelopes.
  • Reuses existing SDK methods where present (detectDetectFramework, beta enrollEnrollBeta, PDF via doRaw).

Review

Two rounds of multi-model review (Claude + Codex); findings fixed and re-verified — including a servers metrics decode bug, the public-endpoint auth path, and JSON-output/metadata gaps on mutation commands.

Testing

  • Full unit-test suite green (go test ./...), go vet clean, golangci-lint 0 issues.
  • Every new command verified end-to-end against a live backend (CRUD lifecycles, --json/--quiet/table output, 403/404/422 error paths, real PDF download, framework detection) — except projects regenerate-key, which is destructive and is covered by unit tests.

Summary by CodeRabbit

  • New Features
    • Added commands for managed hosting, hosted resources, beta enrollment, invoices (list + PDF download), IP ranges, pricing plans (plans/pricing), and framework detection.
    • Expanded project tools with SSH deploy-key regeneration, undeployed changes summaries, and AI deployment overviews.
    • Added global linking for configuration files and SSH commands, plus server creation from global templates and server metrics (beta).
    • Expanded template sub-resources with config/excluded files/integrations/commands, build tooling, and server/server-group operations.
  • Bug Fixes
    • Improved managed VPS pricing/labeling, region selection, and hosted resource display accuracy; updated tests and output formatting to match current API shapes.

…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.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds 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.

Changes

Managed resources and SDK APIs

Layer / File(s) Summary
Client contracts and managed hosting models
internal/cli/context.go, pkg/sdk/client.go, pkg/sdk/managed_hosting.go, pkg/sdk/types.go
Shares SDK client options, enables unauthenticated public clients, and models grouped managed-hosting regions and wrapped size responses.
Resource and account APIs
pkg/sdk/projects.go, pkg/sdk/servers.go, pkg/sdk/hosted_resources.go, pkg/sdk/invoices.go, pkg/sdk/ip_ranges.go, pkg/sdk/packages.go, pkg/sdk/config_files.go, pkg/sdk/ssh_commands.go
Adds project actions, server metrics, hosted-resource lifecycle methods, billing and IP-range APIs, public plans, and global resource linking.
Template subresources
pkg/sdk/template_subresources.go, internal/commands/templates_subresources.go, pkg/sdk/template_subresources_test.go
Adds template-scoped CRUD and specialized operations for configuration, build, integration, server, and server-group resources.
CLI command groups
internal/commands/{projects,servers,hosted_resources,managed_hosting,beta,invoices,ip_ranges,packages,detect}.go
Exposes the new SDK operations through Cobra commands with JSON, quiet, table, validation, and status output modes.
Managed VPS launch behavior
internal/commands/launch.go, internal/commands/launch_test.go
Uses revised monthly-cost, label, and region fields when ranking, displaying, and provisioning managed VPS sizes.
Assistant command exposure
internal/assist/prompt.go, internal/commands/agent_metadata.go
Documents and classifies the expanded command surface for assistant use.

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
Loading
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main scope of the PR: template sub-resources, hosted resources, and project/server actions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/api-coverage-hosted-resources-templates

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 00822d6 and fed4ba0.

📒 Files selected for processing (44)
  • internal/assist/prompt.go
  • internal/cli/context.go
  • internal/commands/agent_metadata.go
  • internal/commands/beta.go
  • internal/commands/config_files.go
  • internal/commands/detect.go
  • internal/commands/hosted_resources.go
  • internal/commands/hosted_resources_test.go
  • internal/commands/invoices.go
  • internal/commands/ip_ranges.go
  • internal/commands/launch.go
  • internal/commands/launch_test.go
  • internal/commands/managed_hosting.go
  • internal/commands/managed_hosting_test.go
  • internal/commands/packages.go
  • internal/commands/projects.go
  • internal/commands/root.go
  • internal/commands/servers.go
  • internal/commands/ssh_commands.go
  • internal/commands/templates.go
  • internal/commands/templates_subresources.go
  • pkg/sdk/client.go
  • pkg/sdk/client_test.go
  • pkg/sdk/config_files.go
  • pkg/sdk/config_files_test.go
  • pkg/sdk/hosted_resources.go
  • pkg/sdk/hosted_resources_test.go
  • pkg/sdk/invoices.go
  • pkg/sdk/invoices_test.go
  • pkg/sdk/ip_ranges.go
  • pkg/sdk/ip_ranges_test.go
  • pkg/sdk/managed_hosting.go
  • pkg/sdk/managed_hosting_test.go
  • pkg/sdk/packages.go
  • pkg/sdk/packages_test.go
  • pkg/sdk/projects.go
  • pkg/sdk/projects_test.go
  • pkg/sdk/servers.go
  • pkg/sdk/servers_test.go
  • pkg/sdk/ssh_commands.go
  • pkg/sdk/ssh_commands_test.go
  • pkg/sdk/template_subresources.go
  • pkg/sdk/template_subresources_test.go
  • pkg/sdk/types.go

Comment thread internal/assist/prompt.go Outdated
Comment thread internal/assist/prompt.go Outdated
Comment thread internal/commands/hosted_resources.go Outdated
Comment thread internal/commands/invoices.go Outdated
Comment thread internal/commands/launch_test.go
Comment thread pkg/sdk/ip_ranges_test.go
Comment thread pkg/sdk/managed_hosting.go Outdated
Comment thread pkg/sdk/packages_test.go
Comment thread pkg/sdk/projects_test.go Outdated
Comment thread pkg/sdk/template_subresources.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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between fed4ba0 and 55d3ea0.

📒 Files selected for processing (10)
  • internal/assist/prompt.go
  • internal/commands/hosted_resources.go
  • internal/commands/invoices.go
  • internal/commands/launch.go
  • internal/commands/launch_test.go
  • internal/commands/packages.go
  • internal/commands/templates_subresources.go
  • pkg/sdk/managed_hosting.go
  • pkg/sdk/managed_hosting_test.go
  • pkg/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

Comment on lines +209 to +214
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,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Comment on lines +510 to +513
if err := requireUpdateFlags(cmd, "name"); err != nil {
return err
}
in, err := client.UpdateTemplateIntegration(cliCtx.Background(), permalink, args[0], sdk.IntegrationCreateRequest{Name: name})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant