Skip to content

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

Merged
scotwells merged 6 commits into
feat/location-native-computefrom
feat/workload-urls
Sep 10, 2026
Merged

feat(cli): publish workloads on a URL with --http-port#289
scotwells merged 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.

@scotwells
scotwells merged commit 05d258f into feat/location-native-compute Sep 10, 2026
10 checks passed
@scotwells
scotwells deleted the feat/workload-urls branch September 10, 2026 13:01
scotwells added a commit that referenced this pull request Sep 10, 2026
Main moved placement from city codes to locations (#264), added runtime
classes (#259), published a shared instance-type catalog, and grew
`datumctl compute deploy` a URL-publishing path (#289). The assistant's
workload-creation surface was rebuilt on top of those rather than beside
them.

Semantic resolutions:

- Placements are locations. `workloadspec.Placement` drops `CityCodes` for
  `Locations []string` plus `LocationSelector`, exactly one of which must be
  set; render emits `spec.placements[].locations` or the selector and refuses
  an empty or malformed one, the way admission does. The render tool's schema,
  its notes, the diff summary, the workload-create skill, llms-full.txt and
  the README all say "location" and point the model at
  `compute_locations_list` for the names, with `locationSelector` named as the
  way to place in every location of a city or region.

- Location resolution keeps main's structure. `SourceServiceAvailability` is
  gone; `locations.ListAvailableLocations` is built on main's
  `AvailableLocations` and errors with `ErrAvailabilityNotServed` when the
  control plane serves no availability, so `compute_locations_list` still
  fails loud rather than reporting a project with nowhere to run. The
  manager's placement reads, which treat an unserved kind as no gate at all,
  are unchanged. The tool now reports `placeable` and `ready` alongside the
  name, city code and topology.

- Instance types read `pkg/instancetype`. `validation.SupportedInstanceTypes`
  and the duplicated sizing table in the agent are deleted, along with
  TODO(#137): the catalog is the single source now.

- Runtime classes: `workloadspec.Input` gains an optional `RuntimeClass`,
  passed through verbatim with no default, so the server picks its own.

- `internal/cmd/compute/deploy/deploy.go` is main's version wholesale, for
  `--location`, `--location-selector`, `--city` and `--http-port`. The CLI no
  longer renders through workloadspec, so the parity test that pinned the two
  together is deleted rather than left stale.

- Docs take main's triage-skill changes with this branch's `compute_` renames
  on top, and workload-create now says that a public HTTPS URL is published
  separately — today only by `datumctl compute deploy --http-port` — so
  `ports` makes a port reachable on the instance's address, not on a managed
  URL.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

4 participants