Skip to content

fix: disallow lifecycle endpoints for prebuilt workspaces #19264

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 14, 2025

Conversation

ssncferreira
Copy link
Contributor

@ssncferreira ssncferreira commented Aug 8, 2025

Description

This PR updates the API to prevent lifecycle configuration endpoints from being used on prebuilt workspaces. Since prebuilds are managed by the reconciliation loop and do not participate in the regular workspace lifecycle, they must not support per-workspace overrides for fields like deadline, TTL, autostart, or dormancy.

Attempting to use these endpoints on a prebuilt workspace will now return a clear validation error (409 Conflict) with an appropriate explanation. This prevents accidental misconfiguration and preserves the lifecycle separation between prebuilds and regular workspaces.

Changes

The following endpoints now return an error if the target workspace is a prebuild:

  • PUT /workspaces/{workspace}/extend
  • PUT /workspaces/{workspace}/ttl
  • PUT /workspaces/{workspace}/autostart
  • PUT /workspaces/{workspace}/dormant

Update endpoints logic to use the API clock in order to allow time mocking in tests.

Related with:

@ssncferreira ssncferreira changed the title fix(api): disallow lifecycle configuration for prebuilt workspaces fix: disallow lifecycle configuration for prebuilt workspaces Aug 8, 2025
@ssncferreira ssncferreira changed the title fix: disallow lifecycle configuration for prebuilt workspaces fix: disallow lifecycle endpoints for prebuilt workspaces Aug 8, 2025
// Prebuild lifecycle is managed by the reconciliation loop, with scheduling behavior
// defined per preset at the template level, not per workspace.
if workspace.IsPrebuild() {
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
Copy link
Contributor Author

Choose a reason for hiding this comment

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

409 Conflict based on discussion: #19252 (comment)

ssncferreira added a commit that referenced this pull request Aug 13, 2025
## Description

This PR ensures that prebuilt workspaces are properly excluded from the
lifecycle executor and treated as a separate class of workspaces, fully
managed by the prebuild reconciliation loop.

It introduces two lifecycle guarantees:
* When a prebuilt workspace is created (i.e., when the workspace build
completes), all lifecycle-related fields are unset, ensuring the
workspace does not participate in TTL, autostop, autostart, dormancy, or
auto-deletion logic.
* When a prebuilt workspace is claimed, it transitions into a regular
user workspace. At this point, all lifecycle fields are correctly
populated according to template-level configurations, allowing the
workspace to be managed by the lifecycle executor as expected.

## Changes

* Prebuilt workspaces now have all lifecycle-relevant fields unset
during creation
* When a prebuild is claimed:
* Lifecycle fields are set based on template and workspace level
configurations. This ensures a clean transition into the standard
workspace lifecycle flow.
* Updated lifecycle-related SQL update queries to explicitly exclude
prebuilt workspaces.

## Relates 

Related issue: #18898

To reduce the scope of this PR and make the review process more
manageable, the original implementation has been split into the
following focused PRs:
* #19259
* #19263
* #19264
* #19265

These PRs should be considered in conjunction with this one to
understand the complete set of lifecycle separation changes for prebuilt
workspaces.
@@ -1115,12 +1126,20 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
return
}

// Use injected Clock to allow time mocking in tests
now := api.Clock.Now()
Copy link
Contributor Author

@ssncferreira ssncferreira Aug 13, 2025

Choose a reason for hiding this comment

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

If I understand correctly, all timestamps in the database are stored in UTC.
Do we need to explicitly set this to UTC? I see it being done in other places, for instance, in setting nextStartAt: https://github.com/coder/coder/blob/main/coderd/workspaces.go#L574

Copy link
Member

Choose a reason for hiding this comment

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

Generally it's recommended to use dbtime.Time as this also rounds to the microsecond which is the smallest unit of precision supported by Postgres.

But if you're just using it as an initial reference point I'd say it should be OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, the pattern I have been following is using the Clock.Now() on the application layer, and dtime.Time for database operations.

@ssncferreira ssncferreira marked this pull request as ready for review August 13, 2025 16:56
@@ -1115,12 +1126,20 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
return
}

// Use injected Clock to allow time mocking in tests
now := api.Clock.Now()
Copy link
Member

Choose a reason for hiding this comment

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

Generally it's recommended to use dbtime.Time as this also rounds to the microsecond which is the smallest unit of precision supported by Postgres.

But if you're just using it as an initial reference point I'd say it should be OK.

@ssncferreira ssncferreira merged commit 734299d into main Aug 14, 2025
33 checks passed
@ssncferreira ssncferreira deleted the ssncferreira/fix-api-prebuild-lifecycle-endpoints branch August 14, 2025 10:30
@github-actions github-actions bot locked and limited conversation to collaborators Aug 14, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants