diff --git a/coderd/prebuilds/api.go b/coderd/prebuilds/api.go index 2342da5d62..00129eae37 100644 --- a/coderd/prebuilds/api.go +++ b/coderd/prebuilds/api.go @@ -9,7 +9,10 @@ import ( "github.com/coder/coder/v2/coderd/database" ) -var ErrNoClaimablePrebuiltWorkspaces = xerrors.New("no claimable prebuilt workspaces found") +var ( + ErrNoClaimablePrebuiltWorkspaces = xerrors.New("no claimable prebuilt workspaces found") + ErrAGPLDoesNotSupportPrebuiltWorkspaces = xerrors.New("prebuilt workspaces functionality is not supported under the AGPL license") +) // ReconciliationOrchestrator manages the lifecycle of prebuild reconciliation. // It runs a continuous loop to check and reconcile prebuild states, and can be stopped gracefully. diff --git a/coderd/prebuilds/noop.go b/coderd/prebuilds/noop.go index e3dc0597b1..6fb3f7c6a5 100644 --- a/coderd/prebuilds/noop.go +++ b/coderd/prebuilds/noop.go @@ -27,7 +27,7 @@ type NoopClaimer struct{} func (NoopClaimer) Claim(context.Context, uuid.UUID, string, uuid.UUID) (*uuid.UUID, error) { // Not entitled to claim prebuilds in AGPL version. - return nil, ErrNoClaimablePrebuiltWorkspaces + return nil, ErrAGPLDoesNotSupportPrebuiltWorkspaces } func (NoopClaimer) Initiator() uuid.UUID { diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 12b3787acf..2ac432d905 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -650,8 +650,28 @@ func createWorkspace( if req.TemplateVersionPresetID != uuid.Nil { // Try and claim an eligible prebuild, if available. claimedWorkspace, err = claimPrebuild(ctx, prebuildsClaimer, db, api.Logger, req, owner) - if err != nil && !errors.Is(err, prebuilds.ErrNoClaimablePrebuiltWorkspaces) { - return xerrors.Errorf("claim prebuild: %w", err) + // If claiming fails with an expected error (no claimable prebuilds or AGPL does not support prebuilds), + // we fall back to creating a new workspace. Otherwise, propagate the unexpected error. + if err != nil { + isExpectedError := errors.Is(err, prebuilds.ErrNoClaimablePrebuiltWorkspaces) || + errors.Is(err, prebuilds.ErrAGPLDoesNotSupportPrebuiltWorkspaces) + fields := []any{ + slog.Error(err), + slog.F("workspace_name", req.Name), + slog.F("template_version_preset_id", req.TemplateVersionPresetID), + } + + if !isExpectedError { + // if it's an unexpected error - use error log level + api.Logger.Error(ctx, "failed to claim prebuilt workspace", fields...) + + return xerrors.Errorf("failed to claim prebuilt workspace: %w", err) + } + + // if it's an expected error - use warn log level + api.Logger.Warn(ctx, "failed to claim prebuilt workspace", fields...) + + // fall back to creating a new workspace } } diff --git a/enterprise/coderd/prebuilds/claim_test.go b/enterprise/coderd/prebuilds/claim_test.go index 5d75b74634..145095e653 100644 --- a/enterprise/coderd/prebuilds/claim_test.go +++ b/enterprise/coderd/prebuilds/claim_test.go @@ -111,6 +111,11 @@ func TestClaimPrebuild(t *testing.T) { markPrebuildsClaimable: true, claimingErr: agplprebuilds.ErrNoClaimablePrebuiltWorkspaces, }, + "AGPL does not support prebuilds error is returned": { + expectPrebuildClaimed: false, + markPrebuildsClaimable: true, + claimingErr: agplprebuilds.ErrAGPLDoesNotSupportPrebuiltWorkspaces, + }, "unexpected claiming error is returned": { expectPrebuildClaimed: false, markPrebuildsClaimable: true, @@ -224,8 +229,11 @@ func TestClaimPrebuild(t *testing.T) { TemplateVersionPresetID: presets[0].ID, }) + isNoPrebuiltWorkspaces := errors.Is(tc.claimingErr, agplprebuilds.ErrNoClaimablePrebuiltWorkspaces) + isUnsupported := errors.Is(tc.claimingErr, agplprebuilds.ErrAGPLDoesNotSupportPrebuiltWorkspaces) + switch { - case tc.claimingErr != nil && errors.Is(tc.claimingErr, agplprebuilds.ErrNoClaimablePrebuiltWorkspaces): + case tc.claimingErr != nil && (isNoPrebuiltWorkspaces || isUnsupported): require.NoError(t, err) coderdtest.AwaitWorkspaceBuildJobCompleted(t, userClient, userWorkspace.LatestBuild.ID)