Skip to content

Commit 08ad910

Browse files
feat: add prebuilds configuration & bootstrapping (coder#17527)
Closes coder/internal#508 --------- Signed-off-by: Danny Kopping <dannykopping@gmail.com> Co-authored-by: Cian Johnston <cian@coder.com>
1 parent e562e3c commit 08ad910

File tree

14 files changed

+328
-46
lines changed

14 files changed

+328
-46
lines changed

cli/testdata/server-config.yaml.golden

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -688,3 +688,16 @@ notifications:
688688
# How often to query the database for queued notifications.
689689
# (default: 15s, type: duration)
690690
fetchInterval: 15s
691+
# Configure how workspace prebuilds behave.
692+
workspace_prebuilds:
693+
# How often to reconcile workspace prebuilds state.
694+
# (default: 15s, type: duration)
695+
reconciliation_interval: 15s
696+
# Interval to increase reconciliation backoff by when prebuilds fail, after which
697+
# a retry attempt is made.
698+
# (default: 15s, type: duration)
699+
reconciliation_backoff_interval: 15s
700+
# Interval to look back to determine number of failed prebuilds, which influences
701+
# backoff.
702+
# (default: 1h0m0s, type: duration)
703+
reconciliation_backoff_lookback_period: 1h0m0s

coderd/apidoc/docs.go

Lines changed: 25 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 25 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ func New(options *Options) *API {
597597
api.AppearanceFetcher.Store(&f)
598598
api.PortSharer.Store(&portsharing.DefaultPortSharer)
599599
api.PrebuildsClaimer.Store(&prebuilds.DefaultClaimer)
600+
api.PrebuildsReconciler.Store(&prebuilds.DefaultReconciler)
600601
buildInfo := codersdk.BuildInfoResponse{
601602
ExternalURL: buildinfo.ExternalURL(),
602603
Version: buildinfo.Version(),
@@ -1568,10 +1569,11 @@ type API struct {
15681569
DERPMapper atomic.Pointer[func(derpMap *tailcfg.DERPMap) *tailcfg.DERPMap]
15691570
// AccessControlStore is a pointer to an atomic pointer since it is
15701571
// passed to dbauthz.
1571-
AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
1572-
PortSharer atomic.Pointer[portsharing.PortSharer]
1573-
FileCache files.Cache
1574-
PrebuildsClaimer atomic.Pointer[prebuilds.Claimer]
1572+
AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
1573+
PortSharer atomic.Pointer[portsharing.PortSharer]
1574+
FileCache files.Cache
1575+
PrebuildsClaimer atomic.Pointer[prebuilds.Claimer]
1576+
PrebuildsReconciler atomic.Pointer[prebuilds.ReconciliationOrchestrator]
15751577

15761578
UpdatesProvider tailnet.WorkspaceUpdatesProvider
15771579

@@ -1659,6 +1661,13 @@ func (api *API) Close() error {
16591661
_ = api.AppSigningKeyCache.Close()
16601662
_ = api.AppEncryptionKeyCache.Close()
16611663
_ = api.UpdatesProvider.Close()
1664+
1665+
if current := api.PrebuildsReconciler.Load(); current != nil {
1666+
ctx, giveUp := context.WithTimeoutCause(context.Background(), time.Second*30, xerrors.New("gave up waiting for reconciler to stop before shutdown"))
1667+
defer giveUp()
1668+
(*current).Stop(ctx, nil)
1669+
}
1670+
16621671
return nil
16631672
}
16641673

coderd/prebuilds/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ var ErrNoClaimablePrebuiltWorkspaces = xerrors.New("no claimable prebuilt worksp
1414
type ReconciliationOrchestrator interface {
1515
Reconciler
1616

17-
// RunLoop starts a continuous reconciliation loop that periodically calls ReconcileAll
17+
// Run starts a continuous reconciliation loop that periodically calls ReconcileAll
1818
// to ensure all prebuilds are in their desired states. The loop runs until the context
1919
// is canceled or Stop is called.
20-
RunLoop(ctx context.Context)
20+
Run(ctx context.Context)
2121

2222
// Stop gracefully shuts down the orchestrator with the given cause.
2323
// The cause is used for logging and error reporting.

coderd/prebuilds/noop.go

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,28 @@ import (
1010

1111
type NoopReconciler struct{}
1212

13-
func NewNoopReconciler() *NoopReconciler {
14-
return &NoopReconciler{}
15-
}
16-
17-
func (NoopReconciler) RunLoop(context.Context) {}
18-
19-
func (NoopReconciler) Stop(context.Context, error) {}
20-
21-
func (NoopReconciler) ReconcileAll(context.Context) error {
22-
return nil
23-
}
24-
13+
func (NoopReconciler) Run(context.Context) {}
14+
func (NoopReconciler) Stop(context.Context, error) {}
15+
func (NoopReconciler) ReconcileAll(context.Context) error { return nil }
2516
func (NoopReconciler) SnapshotState(context.Context, database.Store) (*GlobalSnapshot, error) {
2617
return &GlobalSnapshot{}, nil
2718
}
28-
29-
func (NoopReconciler) ReconcilePreset(context.Context, PresetSnapshot) error {
30-
return nil
31-
}
32-
19+
func (NoopReconciler) ReconcilePreset(context.Context, PresetSnapshot) error { return nil }
3320
func (NoopReconciler) CalculateActions(context.Context, PresetSnapshot) (*ReconciliationActions, error) {
3421
return &ReconciliationActions{}, nil
3522
}
3623

37-
var _ ReconciliationOrchestrator = NoopReconciler{}
24+
var DefaultReconciler ReconciliationOrchestrator = NoopReconciler{}
3825

39-
type AGPLPrebuildClaimer struct{}
26+
type NoopClaimer struct{}
4027

41-
func (AGPLPrebuildClaimer) Claim(context.Context, uuid.UUID, string, uuid.UUID) (*uuid.UUID, error) {
28+
func (NoopClaimer) Claim(context.Context, uuid.UUID, string, uuid.UUID) (*uuid.UUID, error) {
4229
// Not entitled to claim prebuilds in AGPL version.
4330
return nil, ErrNoClaimablePrebuiltWorkspaces
4431
}
4532

46-
func (AGPLPrebuildClaimer) Initiator() uuid.UUID {
33+
func (NoopClaimer) Initiator() uuid.UUID {
4734
return uuid.Nil
4835
}
4936

50-
var DefaultClaimer Claimer = AGPLPrebuildClaimer{}
37+
var DefaultClaimer Claimer = NoopClaimer{}

codersdk/deployment.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const (
8181
FeatureControlSharedPorts FeatureName = "control_shared_ports"
8282
FeatureCustomRoles FeatureName = "custom_roles"
8383
FeatureMultipleOrganizations FeatureName = "multiple_organizations"
84+
FeatureWorkspacePrebuilds FeatureName = "workspace_prebuilds"
8485
)
8586

8687
// FeatureNames must be kept in-sync with the Feature enum above.
@@ -103,6 +104,7 @@ var FeatureNames = []FeatureName{
103104
FeatureControlSharedPorts,
104105
FeatureCustomRoles,
105106
FeatureMultipleOrganizations,
107+
FeatureWorkspacePrebuilds,
106108
}
107109

108110
// Humanize returns the feature name in a human-readable format.
@@ -132,6 +134,7 @@ func (n FeatureName) AlwaysEnable() bool {
132134
FeatureHighAvailability: true,
133135
FeatureCustomRoles: true,
134136
FeatureMultipleOrganizations: true,
137+
FeatureWorkspacePrebuilds: true,
135138
}[n]
136139
}
137140

@@ -394,6 +397,7 @@ type DeploymentValues struct {
394397
Notifications NotificationsConfig `json:"notifications,omitempty" typescript:",notnull"`
395398
AdditionalCSPPolicy serpent.StringArray `json:"additional_csp_policy,omitempty" typescript:",notnull"`
396399
WorkspaceHostnameSuffix serpent.String `json:"workspace_hostname_suffix,omitempty" typescript:",notnull"`
400+
Prebuilds PrebuildsConfig `json:"workspace_prebuilds,omitempty" typescript:",notnull"`
397401

398402
Config serpent.YAMLConfigPath `json:"config,omitempty" typescript:",notnull"`
399403
WriteConfig serpent.Bool `json:"write_config,omitempty" typescript:",notnull"`
@@ -1034,6 +1038,11 @@ func (c *DeploymentValues) Options() serpent.OptionSet {
10341038
Parent: &deploymentGroupNotifications,
10351039
YAML: "webhook",
10361040
}
1041+
deploymentGroupPrebuilds = serpent.Group{
1042+
Name: "Workspace Prebuilds",
1043+
YAML: "workspace_prebuilds",
1044+
Description: "Configure how workspace prebuilds behave.",
1045+
}
10371046
deploymentGroupInbox = serpent.Group{
10381047
Name: "Inbox",
10391048
Parent: &deploymentGroupNotifications,
@@ -3029,7 +3038,44 @@ Write out the current server config as YAML to stdout.`,
30293038
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
30303039
Hidden: true, // Hidden because most operators should not need to modify this.
30313040
},
3032-
// Push notifications.
3041+
3042+
// Workspace Prebuilds Options
3043+
{
3044+
Name: "Reconciliation Interval",
3045+
Description: "How often to reconcile workspace prebuilds state.",
3046+
Flag: "workspace-prebuilds-reconciliation-interval",
3047+
Env: "CODER_WORKSPACE_PREBUILDS_RECONCILIATION_INTERVAL",
3048+
Value: &c.Prebuilds.ReconciliationInterval,
3049+
Default: (time.Second * 15).String(),
3050+
Group: &deploymentGroupPrebuilds,
3051+
YAML: "reconciliation_interval",
3052+
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
3053+
Hidden: ExperimentsSafe.Enabled(ExperimentWorkspacePrebuilds), // Hide setting while this feature is experimental.
3054+
},
3055+
{
3056+
Name: "Reconciliation Backoff Interval",
3057+
Description: "Interval to increase reconciliation backoff by when prebuilds fail, after which a retry attempt is made.",
3058+
Flag: "workspace-prebuilds-reconciliation-backoff-interval",
3059+
Env: "CODER_WORKSPACE_PREBUILDS_RECONCILIATION_BACKOFF_INTERVAL",
3060+
Value: &c.Prebuilds.ReconciliationBackoffInterval,
3061+
Default: (time.Second * 15).String(),
3062+
Group: &deploymentGroupPrebuilds,
3063+
YAML: "reconciliation_backoff_interval",
3064+
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
3065+
Hidden: true,
3066+
},
3067+
{
3068+
Name: "Reconciliation Backoff Lookback Period",
3069+
Description: "Interval to look back to determine number of failed prebuilds, which influences backoff.",
3070+
Flag: "workspace-prebuilds-reconciliation-backoff-lookback-period",
3071+
Env: "CODER_WORKSPACE_PREBUILDS_RECONCILIATION_BACKOFF_LOOKBACK_PERIOD",
3072+
Value: &c.Prebuilds.ReconciliationBackoffLookback,
3073+
Default: (time.Hour).String(), // TODO: use https://pkg.go.dev/github.com/jackc/pgtype@v1.12.0#Interval
3074+
Group: &deploymentGroupPrebuilds,
3075+
YAML: "reconciliation_backoff_lookback_period",
3076+
Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"),
3077+
Hidden: true,
3078+
},
30333079
}
30343080

30353081
return opts
@@ -3256,13 +3302,16 @@ const (
32563302
ExperimentWorkspaceUsage Experiment = "workspace-usage" // Enables the new workspace usage tracking.
32573303
ExperimentWebPush Experiment = "web-push" // Enables web push notifications through the browser.
32583304
ExperimentDynamicParameters Experiment = "dynamic-parameters" // Enables dynamic parameters when creating a workspace.
3305+
ExperimentWorkspacePrebuilds Experiment = "workspace-prebuilds" // Enables the new workspace prebuilds feature.
32593306
)
32603307

32613308
// ExperimentsSafe should include all experiments that are safe for
32623309
// users to opt-in to via --experimental='*'.
32633310
// Experiments that are not ready for consumption by all users should
32643311
// not be included here and will be essentially hidden.
3265-
var ExperimentsSafe = Experiments{}
3312+
var ExperimentsSafe = Experiments{
3313+
ExperimentWorkspacePrebuilds,
3314+
}
32663315

32673316
// Experiments is a list of experiments.
32683317
// Multiple experiments may be enabled at the same time.

docs/reference/api/general.md

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)