feat: add prebuilds configuration & bootstrapping (#17527)

Closes https://github.com/coder/internal/issues/508

---------

Signed-off-by: Danny Kopping <dannykopping@gmail.com>
Co-authored-by: Cian Johnston <cian@coder.com>
This commit is contained in:
Danny Kopping
2025-04-25 11:07:15 +02:00
committed by GitHub
parent e562e3c882
commit 08ad910171
14 changed files with 328 additions and 46 deletions

27
coderd/apidoc/docs.go generated
View File

@ -11926,6 +11926,9 @@ const docTemplate = `{
"workspace_hostname_suffix": {
"type": "string"
},
"workspace_prebuilds": {
"$ref": "#/definitions/codersdk.PrebuildsConfig"
},
"write_config": {
"type": "boolean"
}
@ -12005,7 +12008,8 @@ const docTemplate = `{
"notifications",
"workspace-usage",
"web-push",
"dynamic-parameters"
"dynamic-parameters",
"workspace-prebuilds"
],
"x-enum-comments": {
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
@ -12013,6 +12017,7 @@ const docTemplate = `{
"ExperimentExample": "This isn't used for anything.",
"ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.",
"ExperimentWebPush": "Enables web push notifications through the browser.",
"ExperimentWorkspacePrebuilds": "Enables the new workspace prebuilds feature.",
"ExperimentWorkspaceUsage": "Enables the new workspace usage tracking."
},
"x-enum-varnames": [
@ -12021,7 +12026,8 @@ const docTemplate = `{
"ExperimentNotifications",
"ExperimentWorkspaceUsage",
"ExperimentWebPush",
"ExperimentDynamicParameters"
"ExperimentDynamicParameters",
"ExperimentWorkspacePrebuilds"
]
},
"codersdk.ExternalAuth": {
@ -13654,6 +13660,23 @@ const docTemplate = `{
}
}
},
"codersdk.PrebuildsConfig": {
"type": "object",
"properties": {
"reconciliation_backoff_interval": {
"description": "ReconciliationBackoffInterval specifies the amount of time to increase the backoff interval\nwhen errors occur during reconciliation.",
"type": "integer"
},
"reconciliation_backoff_lookback": {
"description": "ReconciliationBackoffLookback determines the time window to look back when calculating\nthe number of failed prebuilds, which influences the backoff strategy.",
"type": "integer"
},
"reconciliation_interval": {
"description": "ReconciliationInterval defines how often the workspace prebuilds state should be reconciled.",
"type": "integer"
}
}
},
"codersdk.Preset": {
"type": "object",
"properties": {

View File

@ -10684,6 +10684,9 @@
"workspace_hostname_suffix": {
"type": "string"
},
"workspace_prebuilds": {
"$ref": "#/definitions/codersdk.PrebuildsConfig"
},
"write_config": {
"type": "boolean"
}
@ -10759,7 +10762,8 @@
"notifications",
"workspace-usage",
"web-push",
"dynamic-parameters"
"dynamic-parameters",
"workspace-prebuilds"
],
"x-enum-comments": {
"ExperimentAutoFillParameters": "This should not be taken out of experiments until we have redesigned the feature.",
@ -10767,6 +10771,7 @@
"ExperimentExample": "This isn't used for anything.",
"ExperimentNotifications": "Sends notifications via SMTP and webhooks following certain events.",
"ExperimentWebPush": "Enables web push notifications through the browser.",
"ExperimentWorkspacePrebuilds": "Enables the new workspace prebuilds feature.",
"ExperimentWorkspaceUsage": "Enables the new workspace usage tracking."
},
"x-enum-varnames": [
@ -10775,7 +10780,8 @@
"ExperimentNotifications",
"ExperimentWorkspaceUsage",
"ExperimentWebPush",
"ExperimentDynamicParameters"
"ExperimentDynamicParameters",
"ExperimentWorkspacePrebuilds"
]
},
"codersdk.ExternalAuth": {
@ -12346,6 +12352,23 @@
}
}
},
"codersdk.PrebuildsConfig": {
"type": "object",
"properties": {
"reconciliation_backoff_interval": {
"description": "ReconciliationBackoffInterval specifies the amount of time to increase the backoff interval\nwhen errors occur during reconciliation.",
"type": "integer"
},
"reconciliation_backoff_lookback": {
"description": "ReconciliationBackoffLookback determines the time window to look back when calculating\nthe number of failed prebuilds, which influences the backoff strategy.",
"type": "integer"
},
"reconciliation_interval": {
"description": "ReconciliationInterval defines how often the workspace prebuilds state should be reconciled.",
"type": "integer"
}
}
},
"codersdk.Preset": {
"type": "object",
"properties": {

View File

@ -597,6 +597,7 @@ func New(options *Options) *API {
api.AppearanceFetcher.Store(&f)
api.PortSharer.Store(&portsharing.DefaultPortSharer)
api.PrebuildsClaimer.Store(&prebuilds.DefaultClaimer)
api.PrebuildsReconciler.Store(&prebuilds.DefaultReconciler)
buildInfo := codersdk.BuildInfoResponse{
ExternalURL: buildinfo.ExternalURL(),
Version: buildinfo.Version(),
@ -1568,10 +1569,11 @@ type API struct {
DERPMapper atomic.Pointer[func(derpMap *tailcfg.DERPMap) *tailcfg.DERPMap]
// AccessControlStore is a pointer to an atomic pointer since it is
// passed to dbauthz.
AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
PortSharer atomic.Pointer[portsharing.PortSharer]
FileCache files.Cache
PrebuildsClaimer atomic.Pointer[prebuilds.Claimer]
AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
PortSharer atomic.Pointer[portsharing.PortSharer]
FileCache files.Cache
PrebuildsClaimer atomic.Pointer[prebuilds.Claimer]
PrebuildsReconciler atomic.Pointer[prebuilds.ReconciliationOrchestrator]
UpdatesProvider tailnet.WorkspaceUpdatesProvider
@ -1659,6 +1661,13 @@ func (api *API) Close() error {
_ = api.AppSigningKeyCache.Close()
_ = api.AppEncryptionKeyCache.Close()
_ = api.UpdatesProvider.Close()
if current := api.PrebuildsReconciler.Load(); current != nil {
ctx, giveUp := context.WithTimeoutCause(context.Background(), time.Second*30, xerrors.New("gave up waiting for reconciler to stop before shutdown"))
defer giveUp()
(*current).Stop(ctx, nil)
}
return nil
}

View File

@ -14,10 +14,10 @@ var ErrNoClaimablePrebuiltWorkspaces = xerrors.New("no claimable prebuilt worksp
type ReconciliationOrchestrator interface {
Reconciler
// RunLoop starts a continuous reconciliation loop that periodically calls ReconcileAll
// Run starts a continuous reconciliation loop that periodically calls ReconcileAll
// to ensure all prebuilds are in their desired states. The loop runs until the context
// is canceled or Stop is called.
RunLoop(ctx context.Context)
Run(ctx context.Context)
// Stop gracefully shuts down the orchestrator with the given cause.
// The cause is used for logging and error reporting.

View File

@ -10,41 +10,28 @@ import (
type NoopReconciler struct{}
func NewNoopReconciler() *NoopReconciler {
return &NoopReconciler{}
}
func (NoopReconciler) RunLoop(context.Context) {}
func (NoopReconciler) Stop(context.Context, error) {}
func (NoopReconciler) ReconcileAll(context.Context) error {
return nil
}
func (NoopReconciler) Run(context.Context) {}
func (NoopReconciler) Stop(context.Context, error) {}
func (NoopReconciler) ReconcileAll(context.Context) error { return nil }
func (NoopReconciler) SnapshotState(context.Context, database.Store) (*GlobalSnapshot, error) {
return &GlobalSnapshot{}, nil
}
func (NoopReconciler) ReconcilePreset(context.Context, PresetSnapshot) error {
return nil
}
func (NoopReconciler) ReconcilePreset(context.Context, PresetSnapshot) error { return nil }
func (NoopReconciler) CalculateActions(context.Context, PresetSnapshot) (*ReconciliationActions, error) {
return &ReconciliationActions{}, nil
}
var _ ReconciliationOrchestrator = NoopReconciler{}
var DefaultReconciler ReconciliationOrchestrator = NoopReconciler{}
type AGPLPrebuildClaimer struct{}
type NoopClaimer struct{}
func (AGPLPrebuildClaimer) Claim(context.Context, uuid.UUID, string, uuid.UUID) (*uuid.UUID, error) {
func (NoopClaimer) Claim(context.Context, uuid.UUID, string, uuid.UUID) (*uuid.UUID, error) {
// Not entitled to claim prebuilds in AGPL version.
return nil, ErrNoClaimablePrebuiltWorkspaces
}
func (AGPLPrebuildClaimer) Initiator() uuid.UUID {
func (NoopClaimer) Initiator() uuid.UUID {
return uuid.Nil
}
var DefaultClaimer Claimer = AGPLPrebuildClaimer{}
var DefaultClaimer Claimer = NoopClaimer{}