Add mass environment preview for per-PR preview environments#236
Conversation
|
There is a HUUUUUGE PR about to merge that puts all of this on the SDK. Can this wait? |
| } | ||
|
|
||
| fmt.Printf("✅ Preview environment `%s` converged\n", env.ID) | ||
| fmt.Printf("🔗 %s\n", mdClient.URLs.Helper(ctx).EnvironmentURL(env.ID)) |
There was a problem hiding this comment.
We should include an option to watch for deployments and tail prefixing each line with the instance.id for grepability since some may run in parallel
There was a problem hiding this comment.
Done in 0c78eb8 — added --follow to both environment preview (here) and environment deploy (in #238, since that's where the deploy command lives). Output looks like:
[ecomm-pr42-db] applying db schema
[ecomm-pr42-app] starting container
[ecomm-pr42-db] migrations done
Each tail goroutine line-buffers and serializes through a mutex'd sink, so parallel deployments don't tear lines.
One implementation note worth flagging: I went with polling rather than subscribing to environmentEvents / instanceEvents as you suggested. The SDK's Absinthe/Phoenix-channels machinery is in internal/absinthe, so doing real subscriptions from the CLI would mean re-porting that layer just for this flag. Polling per-instance Deployments.List every ~2s has a few seconds of discovery latency per new deployment, which seemed acceptable next to per-instance deploy times measured in minutes. Happy to switch to the subscription path the moment the SDK exposes event subscriptions publicly — at that point it's a one-file swap inside internal/commands/environment/follow.go.
There was a problem hiding this comment.
Filed the SDK ask: massdriver-cloud/massdriver-sdk-go#21 — request to expose environmentEvents / instanceEvents as typed Subscribe methods on the existing services. Once that ships, the swap inside follow.go is one file.
|
@claude review |
|
Claude finished @coryodaniel's task in 2m 38s —— View job Code Review
SummarySolid, well-structured implementation. The interface-based Issues1.
|
Converges a preview environment from a YAML config in four idempotent
steps: forkEnvironment, setEnvironmentDefault, copyInstance per
instance (params/version/secrets), and deployEnvironment.
Config terminology follows V2:
- `project` (no "slug")
- `baseEnvironment`
- `environmentDefaults` use `resourceType` + `resourceId`
- `instances` (V2 term)
Supports:
- ABAC `attributes` (config and `-a` flag; flag overrides config).
- `${VAR}` / `$VAR` expansion across the YAML for CI metadata.
- Fork-level macros (copyEnvironmentDefaults / copySecrets /
copyRemoteReferences).
- Per-instance overrides for version, params (via copyInstance with
overrides for deep-merge), and secrets.
Wired through the SDK's environments / instances services. Per-instance
`remoteReferences:` overrides aren't supported yet — the SDK's
instances service doesn't expose setRemoteReference; the fork-level
copyRemoteReferences macro handles the bulk case until that lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revive's var-naming rule rejects `environment.EnvironmentDefaultEntry` for stuttering with the package name. `environment.DefaultEntry` reads cleaner at the call site too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tails every deployment that fires during a preview rollout, prefixing
each line with the instance ID so the interleaved output from parallel
deployments stays grep-friendly:
[ecomm-pr42-db] applying db schema
[ecomm-pr42-app] starting container
[ecomm-pr42-db] migrations done
`FollowEnvironment` polls per-instance deployment lists every 2s,
spawns a Deployments.TailLogs goroutine for each new deployment it
sees, and exits after a quiet window with all observed deployments
terminal. The platform deploys in dependency-ordered waves, so the
quiet window has to outlast the gap between waves — set to 30s.
Polling instead of subscribing: the SDK's Absinthe/Phoenix-channels
machinery is in an internal package, so writing real
`environmentEvents` / `instanceEvents` subscriptions from the CLI
would mean re-porting that layer. Polling has a few seconds of
discovery latency per deployment, acceptable next to per-instance
deploy times measured in minutes. The subscription path is cleaner
and should land once the SDK exposes event subscriptions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The SDK now exposes `Environments.StreamEvents` and `Instances.StreamEvents` (massdriver-cloud/massdriver-sdk-go#22), so FollowEnvironment no longer needs to spin per-instance Deployments.List polling loops to discover new deployments. The new flow: - Subscribe to `environmentEvents(envID)` over WebSocket. - On each `DeploymentEvent` for a deployment we haven't seen, fetch the deployment once (the event payload trims `instance.id` for bandwidth) and kick off a `Deployments.TailLogs` goroutine into a prefix writer keyed on the instance ID. - Track which deployments are still active by transitioning them off the active set on terminal status events. - Exit when no active deployments remain and the quiet window has elapsed. Net effect from a user's POV: deployment discovery is now sub-second instead of polling-interval bound, and the CLI makes one round-trip per new deployment instead of one per (instance × poll interval). Pinned to the events-streaming branch HEAD until v0.2.2 is tagged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- unconvert: drop string() cast around Deployment.Status (it's already string) - reassign: tag the FollowQuietWindow overrides with the same nolint pattern deploy_test.go uses for DeploymentStatusSleep - goimports: reformat the errorAPI stub block
Multi-instance tails were jittery because each line's prefix was sized
to its own instance id — `[fancy-claude-pg]` and `[fancy-claude-mysql]`
gave you two different column offsets for the actual log text.
List instances up front, take the longest id as the column width, and
right-pad every prefix to that width. New env-deploys create every
instance during fork before any deployment fires, so the up-front list
is the full set.
[fancy-claude-pg] applying schema
[fancy-claude-mysql] starting mysqld
[fancy-claude-pg] migrations done
errorAPI is only invoked through StreamEnvironmentEvents which already
errors, so the other methods are dead code in practice — but `nilnil`
flags any `return nil, nil` from a non-error path. Return empty
`*types.Deployment{}` and empty slice instead.
Summary
Adds
mass environment preview <ID> -f preview.yaml— a single command that converges a preview environment for a PR / branch / feature ramp. Re-running the command resets the env back to whatever the YAML declares.Under the hood it composes four idempotent V2 mutations:
forkEnvironmentfrom the base env in the config (copyEnvironmentDefaults/copySecrets/copyRemoteReferencesmacros pass through).setEnvironmentDefaultperenvironmentDefaultsentry.copyInstancefor params,updateInstancefor version/release strategy,setInstanceSecretper secret,setRemoteReferenceper ref.deployEnvironmentto fan a provision wave across all instances.The companion V2 API changes (
copySecrets/copyRemoteReferenceson fork,deployEnvironmentmutation) are on the platform side in massdriver-cloud/massdriver#3246.Config schema
Follows V2 terminology — no "slug", no "artifact", no
massdriver/scoping prefix.Full schema doc + example:
docs/helpdocs/environment/preview.mdandpreview.example.yaml.Test plan
go build ./...go test ./...— preview unit tests cover load, validation, happy-path call sequence, and fork-failure propagation🤖 Generated with Claude Code