Skip to content

feat(cli): publish workloads on a URL with --http-port - #289

Open
scotwells wants to merge 6 commits into
feat/location-native-computefrom
feat/workload-urls
Open

feat(cli): publish workloads on a URL with --http-port#289
scotwells wants to merge 6 commits into
feat/location-native-computefrom
feat/workload-urls

Conversation

@scotwells

@scotwells scotwells commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Deploying a workload with an HTTP port now ends with a URL you can open. Previously --port opened a port and gave the developer nothing to point a browser at — the flag's own help text said "expose" and nothing was exposed.

This plugin publishes a URL and stops there. Custom hostnames, path routing, certificates, and the rest of proxy configuration belong to dedicated ALB tooling, so there are no new commands here — just a flag, and the URL showing up where a developer already looks.

The experience

Deploy and get a URL, in one command:

$ datumctl compute deploy api --image=ghcr.io/acme/api:1.4.2 --location=us-east-1,eu-west-1 --min=2 --http-port=8080

Resolving workload "api" in project acme-prod...
  Placement "default": locations=[us-east-1, eu-west-1], min=2
  HTTP service:        port 8080 → Datum-managed URL

Apply? (Y/n): y
  workload/api created

Waiting for rollout. Ctrl-C to detach (rollout continues in background).

  PLACEMENT  LOCATION     UPDATED  READY  OLD  PHASE
  default    us-east-1          2      2    0  Done
  default    eu-west-1          2      2    0  Done

Rollout complete in 47s.

Publishing...
  Backends     4 healthy across us-east-1, eu-west-1
  Edge         programmed
  Certificate  issued

  https://a1b2c3d4.datumproxy.net

Deploy without an HTTP port and it says so, rather than leaving you guessing the way --port did:

  No HTTP port declared — this workload is not reachable from the internet.
  To publish it:  datumctl compute deploy api --http-port 8080

Find it again in the list, or pull it out of -o json for a script:

$ datumctl compute workloads

  NAME     LOCATIONS              READY  IMAGE                     URL
  api      us-east-1, eu-west-1   4/4    ghcr.io/acme/api:1.4.2    https://api.example.com
  worker   us-east-1              1/1    ghcr.io/acme/worker:2.0   —

workloads describe gains the URL with per-location backend health. This is the view that makes multi-location serving visible, which it previously was not — a developer could deploy to two locations and have no way to see that only one was taking traffic:

$ datumctl compute workloads describe api

URL          https://api.example.com
Backend      port 8080/tcp

Serving      Degraded — 2 of 4 backends healthy

             LOCATION   BACKENDS  HEALTHY  SERVING
             us-east-1         2        2  yes
             eu-west-1         2        0  no

  eu-west-1: no healthy backends — instances are running but not passing health checks.
             Traffic is being served from us-east-1 only.

  Next steps:
    Check instances:  datumctl compute instances --workload=api --location=eu-west-1

--no-http removes the URL, and destroy names what it is about to take down before asking:

URLs:          https://api.example.com, https://a1b2c3d4.datumproxy.net

This will delete the workload, all its instances, and its URLs. Continue? (y/N):

Breaking change: --port--http-port

--port said what is listening, not who can reach it. Every comparable platform makes the declared role decide exposure instead: Heroku routes the web: process, Render makes you pick Web Service or Private Service, Fly's port setting lives inside an [http_service] block. --http-port carries that meaning in the name, so it needs no second flag to confirm you meant it. --no-http removes it.

--port is removed and errors rather than quietly becoming --http-port, because quietly aliasing it would put every existing workload on the public internet the next time someone upgraded:

$ datumctl compute deploy api --image=x --location=us-east-1 --port=8080
Error: --port has been replaced by --http-port, which publishes the workload on a public HTTPS URL. Use --http-port 8080 to publish, or --no-http to keep it internal

Redeploying is safe and boring: an unchanged redeploy changes nothing, leaving --http-port off keeps the port the workload already has, and the URL is never reissued — so anything already pointing at it keeps working, including hostnames configured through ALB tooling that this plugin never set up itself.

Product rationale and the scope boundaries are in docs/enhancements/datumctl-compute-urls.md.

Stacked on #264 — review that first; this branch targets it, not main.

@scotwells

Copy link
Copy Markdown
Contributor Author

Implementation notes, kept out of the description:

This should not merge before network-services-operator#411. go.mod points network-services-operator at that PR's head, which is where the NetworkService API and the networkService HTTPProxy backend come from. #411 is a draft and currently conflicts with main; a force-push or branch deletion on it breaks go mod download here.

Two platform-side prerequisites #411 flags, neither addressed here:

  • Multi-city services do not yet bind a VRF, so a URL fronting more than one city may not serve until feat: take a VPC identifier from the network cloud#16 lands.
  • networkservices write permissions currently sit only on networking-admin, so a project member holding compute roles alone gets a 403 from --http-port.

Verification: go build ./..., go vet, and gofmt clean. go test ./internal/cmd/... passes, including under -race. Tests are weighted to the failure paths — unpublished and nonexistent workloads, a control plane without the CRDs, unbounded waits, partial deletes, redeploy idempotence, and hostname preservation. The three internal/controller envtest failures on this branch are pre-existing and unrelated; they need a setup-envtest etcd binary that is absent locally.

ecv
ecv previously approved these changes Sep 10, 2026
scotwells and others added 3 commits September 9, 2026 19:33
Deploying a workload with an HTTP port now ends with a URL. Previously
--port opened a port and gave the developer nothing to point a browser at.

--http-port declares the workload an HTTP service and publishes it on a
Datum-managed HTTPS URL, backed by a NetworkService and an HTTPProxy that
load-balance across every city the workload runs in. --no-http removes it.

--port is removed and hard-errors rather than aliasing to --http-port:
silently aliasing would publish every existing workload on the next
upgrade.

Adds `compute open` (heroku/fly/railway convention) and a `compute
domains` group for listing domains, showing one workload's URL in detail
with per-city backend health, and attaching custom hostnames. Surfaces
the URL in `compute workloads` and includes it in `compute destroy`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
deploy calls Declare on every run, so the common case is a workload that
already has a URL. Pin that a redeploy neither recreates it nor churns it
when nothing changed, that a changed port is written through, and that
the canonical hostname survives — reissuing it would silently break every
reference a developer has already shared.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Advanced proxy configuration — custom hostnames, DNS verification, path
routing, certificates — belongs to dedicated ALB tooling, not the compute
plugin. This plugin publishes a URL and stops there.

Removes `compute domains` and `compute open`. The per-city backend health
view moves into `workloads describe`, which already exists and is where a
developer debugging their own workload looks; its health row is labelled
Serving so it reads distinctly from the workload's own Health line.

deploy still reads and carries forward the custom hostnames already on the
proxy, and still fails closed if it cannot read them. That matters more now,
not less: hostnames are configured out of band, so a redeploy that clobbered
them would break something this plugin never saw being set up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@scotwells
scotwells changed the base branch from main to feat/location-native-compute September 10, 2026 00:33
scotwells and others added 3 commits September 9, 2026 19:36
Stacking on the locations work leaves the URL views speaking the old
vocabulary. The per-location backend table said CITY, and its next-steps
line told users to run 'compute instances --city=', a flag that no longer
exists — the suggested command would have failed.

Renames url.Location.City to Location, the column to LOCATION, and the
suggested flag to --location, so every place a URL reports where it is
serving from uses one word for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI's golangci-lint run fails on this branch: deployFromFlags crossed the
gocyclo limit at 32 once the locations validation and the HTTP service
handling both landed in it, and two test fixtures wrote "http" where the
httpPortName constant already exists.

Lifts the three mutually exclusive placement flags into
resolveLocationSelector, which is pure and now has the coverage the inline
version never could — everything else in deployFromFlags needs a control
plane behind the activation gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@kevwilliams kevwilliams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Flag wiring is correct: --http-port validates 1-65535, rejects combination with --no-http and with -f, and --port hard-errors instead of silently aliasing. The already-published case is handled well — declare/apply reads back existing custom hostnames and reuses the managed hostname from status so a redeploy never clobbers or reissues a URL (verified by dedicated idempotence/redeploy tests), and destroy tears down the NetworkService/HTTPProxy with a leftover-cleanup path for partial failures. Help text, examples, and commit messages are clear and describe the migration rationale. No hold.

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.

3 participants