Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit cd416c8

Browse files
authored
refactor: workspace builds (coder#7541)
* refactor workspace builds Signed-off-by: Spike Curtis <[email protected]> * make gen Signed-off-by: Spike Curtis <[email protected]> * Remove ParameterResolver from typescript Signed-off-by: Spike Curtis <[email protected]> * rename conversion -> database/db2sdk Signed-off-by: Spike Curtis <[email protected]> * tests for db2sdk Signed-off-by: Spike Curtis <[email protected]> * Tests for ParameterResolver Signed-off-by: Spike Curtis <[email protected]> * wsbuilder tests Signed-off-by: Spike Curtis <[email protected]> * Move parameter validation tests to richparameters_test.go Signed-off-by: Spike Curtis <[email protected]> * Fix CI generation; rename mock->dbmock Signed-off-by: Spike Curtis <[email protected]> * Fix test imports Signed-off-by: Spike Curtis <[email protected]> --------- Signed-off-by: Spike Curtis <[email protected]>
1 parent 622456f commit cd416c8

31 files changed

+5678
-1235
lines changed

.github/workflows/ci.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ jobs:
184184
run: go install golang.org/x/tools/cmd/goimports@latest
185185
- name: Install yq
186186
run: go run github.com/mikefarah/yq/[email protected]
187+
- name: Install mockgen
188+
run: go install github.com/golang/mock/[email protected]
187189

188190
- name: Install Protoc
189191
run: |

Makefile

+6
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ lint/shellcheck: $(SHELL_SRC_FILES)
420420
gen: \
421421
coderd/database/dump.sql \
422422
coderd/database/querier.go \
423+
coderd/database/dbmock/store.go \
423424
provisionersdk/proto/provisioner.pb.go \
424425
provisionerd/proto/provisionerd.pb.go \
425426
site/src/api/typesGenerated.ts \
@@ -441,6 +442,7 @@ gen/mark-fresh:
441442
files="\
442443
coderd/database/dump.sql \
443444
coderd/database/querier.go \
445+
coderd/database/dbmock/store.go \
444446
provisionersdk/proto/provisioner.pb.go \
445447
provisionerd/proto/provisionerd.pb.go \
446448
site/src/api/typesGenerated.ts \
@@ -476,6 +478,10 @@ coderd/database/dump.sql: coderd/database/gen/dump/main.go $(wildcard coderd/dat
476478
coderd/database/querier.go: coderd/database/sqlc.yaml coderd/database/dump.sql $(wildcard coderd/database/queries/*.sql) coderd/database/gen/enum/main.go
477479
./coderd/database/generate.sh
478480

481+
482+
coderd/database/dbmock/store.go: coderd/database/db.go coderd/database/querier.go
483+
go generate ./coderd/database/dbmock/
484+
479485
provisionersdk/proto/provisioner.pb.go: provisionersdk/proto/provisioner.proto
480486
protoc \
481487
--go_out=. \

cli/templatedelete_test.go

+12-16
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,13 @@ func TestTemplateDelete(t *testing.T) {
5151

5252
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
5353
user := coderdtest.CreateFirstUser(t, client)
54-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
55-
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
56-
templates := []codersdk.Template{
57-
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID),
58-
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID),
59-
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID),
60-
}
54+
templates := []codersdk.Template{}
6155
templateNames := []string{}
62-
for _, template := range templates {
56+
for i := 0; i < 3; i++ {
57+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
58+
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
59+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
60+
templates = append(templates, template)
6361
templateNames = append(templateNames, template.Name)
6462
}
6563

@@ -78,15 +76,13 @@ func TestTemplateDelete(t *testing.T) {
7876

7977
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
8078
user := coderdtest.CreateFirstUser(t, client)
81-
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
82-
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
83-
templates := []codersdk.Template{
84-
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID),
85-
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID),
86-
coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID),
87-
}
79+
templates := []codersdk.Template{}
8880
templateNames := []string{}
89-
for _, template := range templates {
81+
for i := 0; i < 3; i++ {
82+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
83+
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
84+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
85+
templates = append(templates, template)
9086
templateNames = append(templateNames, template.Name)
9187
}
9288

coderd/apidoc/docs.go

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/autobuild/executor/lifecycle_executor.go

+23-97
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package executor
22

33
import (
44
"context"
5-
"encoding/json"
5+
"database/sql"
66
"sync/atomic"
77
"time"
88

@@ -13,8 +13,8 @@ import (
1313
"cdr.dev/slog"
1414
"github.com/coder/coder/coderd/database"
1515
"github.com/coder/coder/coderd/database/dbauthz"
16-
"github.com/coder/coder/coderd/provisionerdserver"
1716
"github.com/coder/coder/coderd/schedule"
17+
"github.com/coder/coder/coderd/wsbuilder"
1818
)
1919

2020
// Executor automatically starts or stops workspaces.
@@ -168,20 +168,35 @@ func (e *Executor) runOnce(t time.Time) Stats {
168168
)
169169
return nil
170170
}
171-
172-
log.Info(e.ctx, "scheduling workspace transition", slog.F("transition", validTransition))
173-
174-
stats.Transitions[ws.ID] = validTransition
175-
if err := build(e.ctx, db, ws, validTransition, priorHistory, priorJob); err != nil {
171+
builder := wsbuilder.New(ws, validTransition).
172+
SetLastWorkspaceBuildInTx(&priorHistory).
173+
SetLastWorkspaceBuildJobInTx(&priorJob)
174+
175+
switch validTransition {
176+
case database.WorkspaceTransitionStart:
177+
builder = builder.Reason(database.BuildReasonAutostart)
178+
case database.WorkspaceTransitionStop:
179+
builder = builder.Reason(database.BuildReasonAutostop)
180+
default:
181+
log.Error(e.ctx, "unsupported transition", slog.F("transition", validTransition))
182+
return nil
183+
}
184+
if _, _, err := builder.Build(e.ctx, db, nil); err != nil {
176185
log.Error(e.ctx, "unable to transition workspace",
177186
slog.F("transition", validTransition),
178187
slog.Error(err),
179188
)
180189
return nil
181190
}
191+
stats.Transitions[ws.ID] = validTransition
192+
193+
log.Info(e.ctx, "scheduling workspace transition", slog.F("transition", validTransition))
182194

183195
return nil
184-
}, nil)
196+
197+
// Run with RepeatableRead isolation so that the build process sees the same data
198+
// as our calculation that determines whether an autobuild is necessary.
199+
}, &sql.TxOptions{Isolation: sql.LevelRepeatableRead})
185200
if err != nil {
186201
log.Error(e.ctx, "workspace scheduling failed", slog.Error(err))
187202
}
@@ -248,92 +263,3 @@ func getNextTransition(
248263
return "", time.Time{}, xerrors.Errorf("last transition not valid for autostart or autostop")
249264
}
250265
}
251-
252-
// TODO(cian): this function duplicates most of api.postWorkspaceBuilds. Refactor.
253-
// See: https://github.com/coder/coder/issues/1401
254-
func build(ctx context.Context, store database.Store, workspace database.Workspace, trans database.WorkspaceTransition, priorHistory database.WorkspaceBuild, priorJob database.ProvisionerJob) error {
255-
template, err := store.GetTemplateByID(ctx, workspace.TemplateID)
256-
if err != nil {
257-
return xerrors.Errorf("get workspace template: %w", err)
258-
}
259-
260-
priorBuildNumber := priorHistory.BuildNumber
261-
262-
// This must happen in a transaction to ensure history can be inserted, and
263-
// the prior history can update it's "after" column to point at the new.
264-
workspaceBuildID := uuid.New()
265-
input, err := json.Marshal(provisionerdserver.WorkspaceProvisionJob{
266-
WorkspaceBuildID: workspaceBuildID,
267-
})
268-
if err != nil {
269-
return xerrors.Errorf("marshal provision job: %w", err)
270-
}
271-
provisionerJobID := uuid.New()
272-
now := database.Now()
273-
274-
var buildReason database.BuildReason
275-
switch trans {
276-
case database.WorkspaceTransitionStart:
277-
buildReason = database.BuildReasonAutostart
278-
case database.WorkspaceTransitionStop:
279-
buildReason = database.BuildReasonAutostop
280-
default:
281-
return xerrors.Errorf("Unsupported transition: %q", trans)
282-
}
283-
284-
lastBuildParameters, err := store.GetWorkspaceBuildParameters(ctx, priorHistory.ID)
285-
if err != nil {
286-
return xerrors.Errorf("fetch prior workspace build parameters: %w", err)
287-
}
288-
289-
return store.InTx(func(db database.Store) error {
290-
newProvisionerJob, err := store.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{
291-
ID: provisionerJobID,
292-
CreatedAt: now,
293-
UpdatedAt: now,
294-
InitiatorID: workspace.OwnerID,
295-
OrganizationID: template.OrganizationID,
296-
Provisioner: template.Provisioner,
297-
Type: database.ProvisionerJobTypeWorkspaceBuild,
298-
StorageMethod: priorJob.StorageMethod,
299-
FileID: priorJob.FileID,
300-
Tags: priorJob.Tags,
301-
Input: input,
302-
})
303-
if err != nil {
304-
return xerrors.Errorf("insert provisioner job: %w", err)
305-
}
306-
workspaceBuild, err := store.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{
307-
ID: workspaceBuildID,
308-
CreatedAt: now,
309-
UpdatedAt: now,
310-
WorkspaceID: workspace.ID,
311-
TemplateVersionID: priorHistory.TemplateVersionID,
312-
BuildNumber: priorBuildNumber + 1,
313-
ProvisionerState: priorHistory.ProvisionerState,
314-
InitiatorID: workspace.OwnerID,
315-
Transition: trans,
316-
JobID: newProvisionerJob.ID,
317-
Reason: buildReason,
318-
})
319-
if err != nil {
320-
return xerrors.Errorf("insert workspace build: %w", err)
321-
}
322-
323-
names := make([]string, 0, len(lastBuildParameters))
324-
values := make([]string, 0, len(lastBuildParameters))
325-
for _, param := range lastBuildParameters {
326-
names = append(names, param.Name)
327-
values = append(values, param.Value)
328-
}
329-
err = db.InsertWorkspaceBuildParameters(ctx, database.InsertWorkspaceBuildParametersParams{
330-
WorkspaceBuildID: workspaceBuild.ID,
331-
Name: names,
332-
Value: values,
333-
})
334-
if err != nil {
335-
return xerrors.Errorf("insert workspace build parameters: %w", err)
336-
}
337-
return nil
338-
}, nil)
339-
}

coderd/database/db2sdk/db2sdk.go

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Package db2sdk provides common conversion routines from database types to codersdk types
2+
package db2sdk
3+
4+
import (
5+
"encoding/json"
6+
"time"
7+
8+
"github.com/coder/coder/coderd/database"
9+
"github.com/coder/coder/coderd/parameter"
10+
"github.com/coder/coder/codersdk"
11+
"github.com/coder/coder/provisionersdk/proto"
12+
)
13+
14+
func WorkspaceBuildParameters(params []database.WorkspaceBuildParameter) []codersdk.WorkspaceBuildParameter {
15+
out := make([]codersdk.WorkspaceBuildParameter, len(params))
16+
for i, p := range params {
17+
out[i] = WorkspaceBuildParameter(p)
18+
}
19+
return out
20+
}
21+
22+
func WorkspaceBuildParameter(p database.WorkspaceBuildParameter) codersdk.WorkspaceBuildParameter {
23+
return codersdk.WorkspaceBuildParameter{
24+
Name: p.Name,
25+
Value: p.Value,
26+
}
27+
}
28+
29+
func TemplateVersionParameter(param database.TemplateVersionParameter) (codersdk.TemplateVersionParameter, error) {
30+
var protoOptions []*proto.RichParameterOption
31+
err := json.Unmarshal(param.Options, &protoOptions)
32+
if err != nil {
33+
return codersdk.TemplateVersionParameter{}, err
34+
}
35+
options := make([]codersdk.TemplateVersionParameterOption, 0)
36+
for _, option := range protoOptions {
37+
options = append(options, codersdk.TemplateVersionParameterOption{
38+
Name: option.Name,
39+
Description: option.Description,
40+
Value: option.Value,
41+
Icon: option.Icon,
42+
})
43+
}
44+
45+
descriptionPlaintext, err := parameter.Plaintext(param.Description)
46+
if err != nil {
47+
return codersdk.TemplateVersionParameter{}, err
48+
}
49+
return codersdk.TemplateVersionParameter{
50+
Name: param.Name,
51+
DisplayName: param.DisplayName,
52+
Description: param.Description,
53+
DescriptionPlaintext: descriptionPlaintext,
54+
Type: param.Type,
55+
Mutable: param.Mutable,
56+
DefaultValue: param.DefaultValue,
57+
Icon: param.Icon,
58+
Options: options,
59+
ValidationRegex: param.ValidationRegex,
60+
ValidationMin: param.ValidationMin,
61+
ValidationMax: param.ValidationMax,
62+
ValidationError: param.ValidationError,
63+
ValidationMonotonic: codersdk.ValidationMonotonicOrder(param.ValidationMonotonic),
64+
Required: param.Required,
65+
LegacyVariableName: param.LegacyVariableName,
66+
}, nil
67+
}
68+
69+
func Parameters(params []database.ParameterValue) []codersdk.Parameter {
70+
out := make([]codersdk.Parameter, len(params))
71+
for i, p := range params {
72+
out[i] = Parameter(p)
73+
}
74+
return out
75+
}
76+
77+
func Parameter(parameterValue database.ParameterValue) codersdk.Parameter {
78+
return codersdk.Parameter{
79+
ID: parameterValue.ID,
80+
CreatedAt: parameterValue.CreatedAt,
81+
UpdatedAt: parameterValue.UpdatedAt,
82+
Scope: codersdk.ParameterScope(parameterValue.Scope),
83+
ScopeID: parameterValue.ScopeID,
84+
Name: parameterValue.Name,
85+
SourceScheme: codersdk.ParameterSourceScheme(parameterValue.SourceScheme),
86+
DestinationScheme: codersdk.ParameterDestinationScheme(parameterValue.DestinationScheme),
87+
SourceValue: parameterValue.SourceValue,
88+
}
89+
}
90+
91+
func ProvisionerJobStatus(provisionerJob database.ProvisionerJob) codersdk.ProvisionerJobStatus {
92+
switch {
93+
case provisionerJob.CanceledAt.Valid:
94+
if !provisionerJob.CompletedAt.Valid {
95+
return codersdk.ProvisionerJobCanceling
96+
}
97+
if provisionerJob.Error.String == "" {
98+
return codersdk.ProvisionerJobCanceled
99+
}
100+
return codersdk.ProvisionerJobFailed
101+
case !provisionerJob.StartedAt.Valid:
102+
return codersdk.ProvisionerJobPending
103+
case provisionerJob.CompletedAt.Valid:
104+
if provisionerJob.Error.String == "" {
105+
return codersdk.ProvisionerJobSucceeded
106+
}
107+
return codersdk.ProvisionerJobFailed
108+
case database.Now().Sub(provisionerJob.UpdatedAt) > 30*time.Second:
109+
return codersdk.ProvisionerJobFailed
110+
default:
111+
return codersdk.ProvisionerJobRunning
112+
}
113+
}

0 commit comments

Comments
 (0)