deploy: PATCH /api/v1/deployments/:id to edit private + allowed_ips in-place#46
Merged
Merged
Conversation
…n-place Dashboard PrivacyPanel (PR #44) needed a way to edit access-control on an existing deploy. POST /deploy/new with private/allowed_ips shipped in PR #45 but no PATCH surface — every PrivacyPanel save hit 404. What lands: - PATCH /api/v1/deployments/:id accepts {private?, allowed_ips?} JSON. Body fields are optional pointers so "omit" and "set to zero" are distinguishable. Sending only allowed_ips preserves the current private state; sending private:false clears the allow-list regardless of allowed_ips in the same body (preserves the public-deploy invariant). - Validation reuses PR #45's parsePrivateDeployFields rule-set by factoring out validatePrivateDeployFields — the tier gate, non-empty IPs check, cap, and per-entry parse are shared with POST. The U3 reviewer audits the rules in one place. - compute.Provider gains UpdateAccessControl(ctx, appID, private, allowedIPs). K8s implementation Get/Update's the existing Ingress and rewrites the whitelist-source-range annotation via a new shared helper buildIngressAccessAnnotations — same function the create path now uses, so create and update can't drift on the annotation key. Noop logs+returns nil. Local-dev (no DEPLOY_DOMAIN) is a no-op with a warn breadcrumb. - models.UpdateDeploymentAccessControl writes the private + allowed_ips columns. Single-row update — no caching/aggregation concerns. - Semantics decision documented inline: allowed_ips on PATCH REPLACES the current list (not appends). Matches REST collection-field conventions and is what PrivacyPanel renders/submits. Tests: 8 new in deploy_private_patch_test.go, all pass against the noop compute provider. Pro flips public→private, replace semantics, private:false clears the list, hobby 402 with reused AgentActionPrivateDeployRequiresPro, invalid IP surfaces verbatim, 404 / 403 / empty-body 400. TestAgentActionContract still passes (no new strings added). make test-unit: all packages green (handlers 23.6s). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PATCH /api/v1/deployments/:idaccepts{private?, allowed_ips?}to edit access-control on an existing deploy without rebuilding the image. Unblocks the dashboard PrivacyPanel (PR plans: annual pricing tiers (hobby_yearly / pro_yearly / team_yearly) + billing.go plan_frequency (P2) #44) which was hitting 404.validatePrivateDeployFieldshelper — tier gate, required-IPs check, cap, per-entry parse are byte-identical toPOST /deploy/newand audited in one place.buildIngressAccessAnnotationsso the create path (applyIngressForDeploy) and the new update path (K8sProvider.UpdateAccessControl) share a single source of truth on thenginx.ingress.kubernetes.io/whitelist-source-rangekey.Semantics
allowed_ipson PATCH REPLACES (not appends). Documented inline inpatchAccessControlBody. Matches REST conventions for collection fields and matches what PrivacyPanel renders/submits. Append semantics would silently grow the allow-list across multiple PATCHes.private: falseclears the allow-list regardless ofallowed_ipsin the same body — preserves the "public deploy has no whitelist annotation" invariant.allowed_ipskeeps the currentprivateflag.missing_fields(not a silent no-op).Test plan
make test-unitgreen (all packages, ~24s on handlers)deploy_private_patch_test.goall pass:allowed_ipsREPLACES the existing list (explicit anti-append assertion)private: falseclears the allow-list to empty[]AgentActionPrivateDeployRequiresPromissing_fieldsTestAgentActionContractstill passes — no new agent-action strings addedTestDeployNew_Private_*tests still pass (validation refactor preserves behaviour)🤖 Generated with Claude Code