mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
fix(coderd/database): avoid clobbering workspace build state (#9826)
Fixes #9823. - Decomposes UpdateWorkspaceBuildByID into UpdateWorkspaceBuildProvisionerStateByID and UpdateWorkspaceBuildDeadlineByID. - Replaces existing invocations of UpdateWorkspaceBuildByID with the newer queries where applicable. - Modifies GetActiveWorkspaceBuildsByTemplateID to not return incomplete workspace builds.
This commit is contained in:
@ -77,10 +77,9 @@ func TestWorkspaceActivityBump(t *testing.T) {
|
|||||||
dbBuild, err := db.GetWorkspaceBuildByID(ctx, workspace.LatestBuild.ID)
|
dbBuild, err := db.GetWorkspaceBuildByID(ctx, workspace.LatestBuild.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = db.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
err = db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||||
ID: workspace.LatestBuild.ID,
|
ID: workspace.LatestBuild.ID,
|
||||||
UpdatedAt: dbtime.Now(),
|
UpdatedAt: dbtime.Now(),
|
||||||
ProvisionerState: dbBuild.ProvisionerState,
|
|
||||||
Deadline: dbBuild.Deadline,
|
Deadline: dbBuild.Deadline,
|
||||||
MaxDeadline: dbtime.Now().Add(maxTTL),
|
MaxDeadline: dbtime.Now().Add(maxTTL),
|
||||||
})
|
})
|
||||||
|
@ -2675,7 +2675,15 @@ func (q *querier) UpdateWorkspaceAutostart(ctx context.Context, arg database.Upd
|
|||||||
return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceAutostart)(ctx, arg)
|
return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceAutostart)(ctx, arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *querier) UpdateWorkspaceBuildByID(ctx context.Context, arg database.UpdateWorkspaceBuildByIDParams) error {
|
// UpdateWorkspaceBuildCostByID is used by the provisioning system to update the cost of a workspace build.
|
||||||
|
func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
||||||
|
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return q.db.UpdateWorkspaceBuildCostByID(ctx, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *querier) UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg database.UpdateWorkspaceBuildDeadlineByIDParams) error {
|
||||||
build, err := q.db.GetWorkspaceBuildByID(ctx, arg.ID)
|
build, err := q.db.GetWorkspaceBuildByID(ctx, arg.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -2685,20 +2693,19 @@ func (q *querier) UpdateWorkspaceBuildByID(ctx context.Context, arg database.Upd
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace.RBACObject())
|
err = q.authorizeContext(ctx, rbac.ActionUpdate, workspace.RBACObject())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
return q.db.UpdateWorkspaceBuildDeadlineByID(ctx, arg)
|
||||||
return q.db.UpdateWorkspaceBuildByID(ctx, arg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateWorkspaceBuildCostByID is used by the provisioning system to update the cost of a workspace build.
|
func (q *querier) UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg database.UpdateWorkspaceBuildProvisionerStateByIDParams) error {
|
||||||
func (q *querier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
|
||||||
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
if err := q.authorizeContext(ctx, rbac.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return q.db.UpdateWorkspaceBuildCostByID(ctx, arg)
|
return q.db.UpdateWorkspaceBuildProvisionerStateByID(ctx, arg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use SoftDeleteWorkspaceByID
|
// Deprecated: Use SoftDeleteWorkspaceByID
|
||||||
|
@ -1232,14 +1232,13 @@ func (s *MethodTestSuite) TestWorkspace() {
|
|||||||
ID: ws.ID,
|
ID: ws.ID,
|
||||||
}).Asserts(ws, rbac.ActionUpdate).Returns()
|
}).Asserts(ws, rbac.ActionUpdate).Returns()
|
||||||
}))
|
}))
|
||||||
s.Run("UpdateWorkspaceBuildByID", s.Subtest(func(db database.Store, check *expects) {
|
s.Run("UpdateWorkspaceBuildDeadlineByID", s.Subtest(func(db database.Store, check *expects) {
|
||||||
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||||
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||||
check.Args(database.UpdateWorkspaceBuildByIDParams{
|
check.Args(database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||||
ID: build.ID,
|
ID: build.ID,
|
||||||
UpdatedAt: build.UpdatedAt,
|
UpdatedAt: build.UpdatedAt,
|
||||||
Deadline: build.Deadline,
|
Deadline: build.Deadline,
|
||||||
ProvisionerState: []byte{},
|
|
||||||
}).Asserts(ws, rbac.ActionUpdate)
|
}).Asserts(ws, rbac.ActionUpdate)
|
||||||
}))
|
}))
|
||||||
s.Run("SoftDeleteWorkspaceByID", s.Subtest(func(db database.Store, check *expects) {
|
s.Run("SoftDeleteWorkspaceByID", s.Subtest(func(db database.Store, check *expects) {
|
||||||
@ -1378,6 +1377,14 @@ func (s *MethodTestSuite) TestSystemFunctions() {
|
|||||||
DailyCost: 10,
|
DailyCost: 10,
|
||||||
}).Asserts(rbac.ResourceSystem, rbac.ActionUpdate)
|
}).Asserts(rbac.ResourceSystem, rbac.ActionUpdate)
|
||||||
}))
|
}))
|
||||||
|
s.Run("UpdateWorkspaceBuildProvisionerStateByID", s.Subtest(func(db database.Store, check *expects) {
|
||||||
|
ws := dbgen.Workspace(s.T(), db, database.Workspace{})
|
||||||
|
build := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{WorkspaceID: ws.ID, JobID: uuid.New()})
|
||||||
|
check.Args(database.UpdateWorkspaceBuildProvisionerStateByIDParams{
|
||||||
|
ID: build.ID,
|
||||||
|
ProvisionerState: []byte("testing"),
|
||||||
|
}).Asserts(rbac.ResourceSystem, rbac.ActionUpdate)
|
||||||
|
}))
|
||||||
s.Run("UpsertLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) {
|
s.Run("UpsertLastUpdateCheck", s.Subtest(func(db database.Store, check *expects) {
|
||||||
check.Args("value").Asserts(rbac.ResourceSystem, rbac.ActionUpdate)
|
check.Args("value").Asserts(rbac.ResourceSystem, rbac.ActionUpdate)
|
||||||
}))
|
}))
|
||||||
|
@ -5854,28 +5854,6 @@ func (q *FakeQuerier) UpdateWorkspaceAutostart(_ context.Context, arg database.U
|
|||||||
return sql.ErrNoRows
|
return sql.ErrNoRows
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *FakeQuerier) UpdateWorkspaceBuildByID(_ context.Context, arg database.UpdateWorkspaceBuildByIDParams) error {
|
|
||||||
if err := validateDatabaseType(arg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
q.mutex.Lock()
|
|
||||||
defer q.mutex.Unlock()
|
|
||||||
|
|
||||||
for index, workspaceBuild := range q.workspaceBuilds {
|
|
||||||
if workspaceBuild.ID != arg.ID {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
workspaceBuild.UpdatedAt = arg.UpdatedAt
|
|
||||||
workspaceBuild.ProvisionerState = arg.ProvisionerState
|
|
||||||
workspaceBuild.Deadline = arg.Deadline
|
|
||||||
workspaceBuild.MaxDeadline = arg.MaxDeadline
|
|
||||||
q.workspaceBuilds[index] = workspaceBuild
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return sql.ErrNoRows
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *FakeQuerier) UpdateWorkspaceBuildCostByID(_ context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
func (q *FakeQuerier) UpdateWorkspaceBuildCostByID(_ context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
||||||
if err := validateDatabaseType(arg); err != nil {
|
if err := validateDatabaseType(arg); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -5895,6 +5873,51 @@ func (q *FakeQuerier) UpdateWorkspaceBuildCostByID(_ context.Context, arg databa
|
|||||||
return sql.ErrNoRows
|
return sql.ErrNoRows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *FakeQuerier) UpdateWorkspaceBuildDeadlineByID(_ context.Context, arg database.UpdateWorkspaceBuildDeadlineByIDParams) error {
|
||||||
|
err := validateDatabaseType(arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
q.mutex.Lock()
|
||||||
|
defer q.mutex.Unlock()
|
||||||
|
|
||||||
|
for idx, build := range q.workspaceBuilds {
|
||||||
|
if build.ID != arg.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
build.Deadline = arg.Deadline
|
||||||
|
build.MaxDeadline = arg.MaxDeadline
|
||||||
|
build.UpdatedAt = arg.UpdatedAt
|
||||||
|
q.workspaceBuilds[idx] = build
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql.ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *FakeQuerier) UpdateWorkspaceBuildProvisionerStateByID(_ context.Context, arg database.UpdateWorkspaceBuildProvisionerStateByIDParams) error {
|
||||||
|
err := validateDatabaseType(arg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
q.mutex.Lock()
|
||||||
|
defer q.mutex.Unlock()
|
||||||
|
|
||||||
|
for idx, build := range q.workspaceBuilds {
|
||||||
|
if build.ID != arg.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
build.ProvisionerState = arg.ProvisionerState
|
||||||
|
build.UpdatedAt = arg.UpdatedAt
|
||||||
|
q.workspaceBuilds[idx] = build
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return sql.ErrNoRows
|
||||||
|
}
|
||||||
|
|
||||||
func (q *FakeQuerier) UpdateWorkspaceDeletedByID(_ context.Context, arg database.UpdateWorkspaceDeletedByIDParams) error {
|
func (q *FakeQuerier) UpdateWorkspaceDeletedByID(_ context.Context, arg database.UpdateWorkspaceDeletedByIDParams) error {
|
||||||
if err := validateDatabaseType(arg); err != nil {
|
if err := validateDatabaseType(arg); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1649,13 +1649,6 @@ func (m metricsStore) UpdateWorkspaceAutostart(ctx context.Context, arg database
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m metricsStore) UpdateWorkspaceBuildByID(ctx context.Context, arg database.UpdateWorkspaceBuildByIDParams) error {
|
|
||||||
start := time.Now()
|
|
||||||
err := m.s.UpdateWorkspaceBuildByID(ctx, arg)
|
|
||||||
m.queryLatencies.WithLabelValues("UpdateWorkspaceBuildByID").Observe(time.Since(start).Seconds())
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m metricsStore) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
func (m metricsStore) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
err := m.s.UpdateWorkspaceBuildCostByID(ctx, arg)
|
err := m.s.UpdateWorkspaceBuildCostByID(ctx, arg)
|
||||||
@ -1663,6 +1656,20 @@ func (m metricsStore) UpdateWorkspaceBuildCostByID(ctx context.Context, arg data
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m metricsStore) UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg database.UpdateWorkspaceBuildDeadlineByIDParams) error {
|
||||||
|
start := time.Now()
|
||||||
|
r0 := m.s.UpdateWorkspaceBuildDeadlineByID(ctx, arg)
|
||||||
|
m.queryLatencies.WithLabelValues("UpdateWorkspaceBuildDeadlineByID").Observe(time.Since(start).Seconds())
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m metricsStore) UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg database.UpdateWorkspaceBuildProvisionerStateByIDParams) error {
|
||||||
|
start := time.Now()
|
||||||
|
r0 := m.s.UpdateWorkspaceBuildProvisionerStateByID(ctx, arg)
|
||||||
|
m.queryLatencies.WithLabelValues("UpdateWorkspaceBuildProvisionerStateByID").Observe(time.Since(start).Seconds())
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
func (m metricsStore) UpdateWorkspaceDeletedByID(ctx context.Context, arg database.UpdateWorkspaceDeletedByIDParams) error {
|
func (m metricsStore) UpdateWorkspaceDeletedByID(ctx context.Context, arg database.UpdateWorkspaceDeletedByIDParams) error {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
err := m.s.UpdateWorkspaceDeletedByID(ctx, arg)
|
err := m.s.UpdateWorkspaceDeletedByID(ctx, arg)
|
||||||
|
@ -3467,20 +3467,6 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceAutostart(arg0, arg1 interface{}
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAutostart", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAutostart), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAutostart", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAutostart), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateWorkspaceBuildByID mocks base method.
|
|
||||||
func (m *MockStore) UpdateWorkspaceBuildByID(arg0 context.Context, arg1 database.UpdateWorkspaceBuildByIDParams) error {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "UpdateWorkspaceBuildByID", arg0, arg1)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateWorkspaceBuildByID indicates an expected call of UpdateWorkspaceBuildByID.
|
|
||||||
func (mr *MockStoreMockRecorder) UpdateWorkspaceBuildByID(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceBuildByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceBuildByID), arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateWorkspaceBuildCostByID mocks base method.
|
// UpdateWorkspaceBuildCostByID mocks base method.
|
||||||
func (m *MockStore) UpdateWorkspaceBuildCostByID(arg0 context.Context, arg1 database.UpdateWorkspaceBuildCostByIDParams) error {
|
func (m *MockStore) UpdateWorkspaceBuildCostByID(arg0 context.Context, arg1 database.UpdateWorkspaceBuildCostByIDParams) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
@ -3495,6 +3481,34 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceBuildCostByID(arg0, arg1 interfa
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceBuildCostByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceBuildCostByID), arg0, arg1)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceBuildCostByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceBuildCostByID), arg0, arg1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateWorkspaceBuildDeadlineByID mocks base method.
|
||||||
|
func (m *MockStore) UpdateWorkspaceBuildDeadlineByID(arg0 context.Context, arg1 database.UpdateWorkspaceBuildDeadlineByIDParams) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateWorkspaceBuildDeadlineByID", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWorkspaceBuildDeadlineByID indicates an expected call of UpdateWorkspaceBuildDeadlineByID.
|
||||||
|
func (mr *MockStoreMockRecorder) UpdateWorkspaceBuildDeadlineByID(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceBuildDeadlineByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceBuildDeadlineByID), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWorkspaceBuildProvisionerStateByID mocks base method.
|
||||||
|
func (m *MockStore) UpdateWorkspaceBuildProvisionerStateByID(arg0 context.Context, arg1 database.UpdateWorkspaceBuildProvisionerStateByIDParams) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UpdateWorkspaceBuildProvisionerStateByID", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWorkspaceBuildProvisionerStateByID indicates an expected call of UpdateWorkspaceBuildProvisionerStateByID.
|
||||||
|
func (mr *MockStoreMockRecorder) UpdateWorkspaceBuildProvisionerStateByID(arg0, arg1 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceBuildProvisionerStateByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceBuildProvisionerStateByID), arg0, arg1)
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateWorkspaceDeletedByID mocks base method.
|
// UpdateWorkspaceDeletedByID mocks base method.
|
||||||
func (m *MockStore) UpdateWorkspaceDeletedByID(arg0 context.Context, arg1 database.UpdateWorkspaceDeletedByIDParams) error {
|
func (m *MockStore) UpdateWorkspaceDeletedByID(arg0 context.Context, arg1 database.UpdateWorkspaceDeletedByIDParams) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -305,8 +305,9 @@ type sqlcQuerier interface {
|
|||||||
UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error
|
UpdateWorkspaceAgentStartupByID(ctx context.Context, arg UpdateWorkspaceAgentStartupByIDParams) error
|
||||||
UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error
|
UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error
|
||||||
UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error
|
UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error
|
||||||
UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) error
|
|
||||||
UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) error
|
UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) error
|
||||||
|
UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg UpdateWorkspaceBuildDeadlineByIDParams) error
|
||||||
|
UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg UpdateWorkspaceBuildProvisionerStateByIDParams) error
|
||||||
UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error
|
UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error
|
||||||
UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (Workspace, error)
|
UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (Workspace, error)
|
||||||
UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error
|
UpdateWorkspaceLastUsedAt(ctx context.Context, arg UpdateWorkspaceLastUsedAtParams) error
|
||||||
|
@ -8489,8 +8489,13 @@ FROM (
|
|||||||
JOIN
|
JOIN
|
||||||
workspace_build_with_user AS wb
|
workspace_build_with_user AS wb
|
||||||
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number
|
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number
|
||||||
|
JOIN
|
||||||
|
provisioner_jobs AS pj
|
||||||
|
ON wb.job_id = pj.id
|
||||||
WHERE
|
WHERE
|
||||||
wb.transition = 'start'::workspace_transition
|
wb.transition = 'start'::workspace_transition
|
||||||
|
AND
|
||||||
|
pj.completed_at IS NOT NULL
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *sqlQuerier) GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceBuild, error) {
|
func (q *sqlQuerier) GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, templateID uuid.UUID) ([]WorkspaceBuild, error) {
|
||||||
@ -8979,37 +8984,6 @@ func (q *sqlQuerier) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspa
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateWorkspaceBuildByID = `-- name: UpdateWorkspaceBuildByID :exec
|
|
||||||
UPDATE
|
|
||||||
workspace_builds
|
|
||||||
SET
|
|
||||||
updated_at = $2,
|
|
||||||
provisioner_state = $3,
|
|
||||||
deadline = $4,
|
|
||||||
max_deadline = $5
|
|
||||||
WHERE
|
|
||||||
id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
type UpdateWorkspaceBuildByIDParams struct {
|
|
||||||
ID uuid.UUID `db:"id" json:"id"`
|
|
||||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
|
||||||
ProvisionerState []byte `db:"provisioner_state" json:"provisioner_state"`
|
|
||||||
Deadline time.Time `db:"deadline" json:"deadline"`
|
|
||||||
MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *sqlQuerier) UpdateWorkspaceBuildByID(ctx context.Context, arg UpdateWorkspaceBuildByIDParams) error {
|
|
||||||
_, err := q.db.ExecContext(ctx, updateWorkspaceBuildByID,
|
|
||||||
arg.ID,
|
|
||||||
arg.UpdatedAt,
|
|
||||||
arg.ProvisionerState,
|
|
||||||
arg.Deadline,
|
|
||||||
arg.MaxDeadline,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateWorkspaceBuildCostByID = `-- name: UpdateWorkspaceBuildCostByID :exec
|
const updateWorkspaceBuildCostByID = `-- name: UpdateWorkspaceBuildCostByID :exec
|
||||||
UPDATE
|
UPDATE
|
||||||
workspace_builds
|
workspace_builds
|
||||||
@ -9029,6 +9003,53 @@ func (q *sqlQuerier) UpdateWorkspaceBuildCostByID(ctx context.Context, arg Updat
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateWorkspaceBuildDeadlineByID = `-- name: UpdateWorkspaceBuildDeadlineByID :exec
|
||||||
|
UPDATE
|
||||||
|
workspace_builds
|
||||||
|
SET
|
||||||
|
deadline = $1::timestamptz,
|
||||||
|
max_deadline = $2::timestamptz,
|
||||||
|
updated_at = $3::timestamptz
|
||||||
|
WHERE id = $4::uuid
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateWorkspaceBuildDeadlineByIDParams struct {
|
||||||
|
Deadline time.Time `db:"deadline" json:"deadline"`
|
||||||
|
MaxDeadline time.Time `db:"max_deadline" json:"max_deadline"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *sqlQuerier) UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg UpdateWorkspaceBuildDeadlineByIDParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, updateWorkspaceBuildDeadlineByID,
|
||||||
|
arg.Deadline,
|
||||||
|
arg.MaxDeadline,
|
||||||
|
arg.UpdatedAt,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateWorkspaceBuildProvisionerStateByID = `-- name: UpdateWorkspaceBuildProvisionerStateByID :exec
|
||||||
|
UPDATE
|
||||||
|
workspace_builds
|
||||||
|
SET
|
||||||
|
provisioner_state = $1::bytea,
|
||||||
|
updated_at = $2::timestamptz
|
||||||
|
WHERE id = $3::uuid
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateWorkspaceBuildProvisionerStateByIDParams struct {
|
||||||
|
ProvisionerState []byte `db:"provisioner_state" json:"provisioner_state"`
|
||||||
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||||
|
ID uuid.UUID `db:"id" json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *sqlQuerier) UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg UpdateWorkspaceBuildProvisionerStateByIDParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, updateWorkspaceBuildProvisionerStateByID, arg.ProvisionerState, arg.UpdatedAt, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const getWorkspaceResourceByID = `-- name: GetWorkspaceResourceByID :one
|
const getWorkspaceResourceByID = `-- name: GetWorkspaceResourceByID :one
|
||||||
SELECT
|
SELECT
|
||||||
id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost
|
id, created_at, job_id, transition, type, name, hide, icon, instance_type, daily_cost
|
||||||
|
@ -125,17 +125,6 @@ INSERT INTO
|
|||||||
VALUES
|
VALUES
|
||||||
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);
|
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);
|
||||||
|
|
||||||
-- name: UpdateWorkspaceBuildByID :exec
|
|
||||||
UPDATE
|
|
||||||
workspace_builds
|
|
||||||
SET
|
|
||||||
updated_at = $2,
|
|
||||||
provisioner_state = $3,
|
|
||||||
deadline = $4,
|
|
||||||
max_deadline = $5
|
|
||||||
WHERE
|
|
||||||
id = $1;
|
|
||||||
|
|
||||||
-- name: UpdateWorkspaceBuildCostByID :exec
|
-- name: UpdateWorkspaceBuildCostByID :exec
|
||||||
UPDATE
|
UPDATE
|
||||||
workspace_builds
|
workspace_builds
|
||||||
@ -144,6 +133,23 @@ SET
|
|||||||
WHERE
|
WHERE
|
||||||
id = $1;
|
id = $1;
|
||||||
|
|
||||||
|
-- name: UpdateWorkspaceBuildDeadlineByID :exec
|
||||||
|
UPDATE
|
||||||
|
workspace_builds
|
||||||
|
SET
|
||||||
|
deadline = @deadline::timestamptz,
|
||||||
|
max_deadline = @max_deadline::timestamptz,
|
||||||
|
updated_at = @updated_at::timestamptz
|
||||||
|
WHERE id = @id::uuid;
|
||||||
|
|
||||||
|
-- name: UpdateWorkspaceBuildProvisionerStateByID :exec
|
||||||
|
UPDATE
|
||||||
|
workspace_builds
|
||||||
|
SET
|
||||||
|
provisioner_state = @provisioner_state::bytea,
|
||||||
|
updated_at = @updated_at::timestamptz
|
||||||
|
WHERE id = @id::uuid;
|
||||||
|
|
||||||
-- name: GetActiveWorkspaceBuildsByTemplateID :many
|
-- name: GetActiveWorkspaceBuildsByTemplateID :many
|
||||||
SELECT wb.*
|
SELECT wb.*
|
||||||
FROM (
|
FROM (
|
||||||
@ -166,5 +172,10 @@ FROM (
|
|||||||
JOIN
|
JOIN
|
||||||
workspace_build_with_user AS wb
|
workspace_build_with_user AS wb
|
||||||
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number
|
ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number
|
||||||
|
JOIN
|
||||||
|
provisioner_jobs AS pj
|
||||||
|
ON wb.job_id = pj.id
|
||||||
WHERE
|
WHERE
|
||||||
wb.transition = 'start'::workspace_transition;
|
wb.transition = 'start'::workspace_transition
|
||||||
|
AND
|
||||||
|
pj.completed_at IS NOT NULL;
|
||||||
|
@ -832,15 +832,22 @@ func (s *server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*proto.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if jobType.WorkspaceBuild.State != nil {
|
if jobType.WorkspaceBuild.State != nil {
|
||||||
err = db.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
err = db.UpdateWorkspaceBuildProvisionerStateByID(ctx, database.UpdateWorkspaceBuildProvisionerStateByIDParams{
|
||||||
ID: input.WorkspaceBuildID,
|
ID: input.WorkspaceBuildID,
|
||||||
UpdatedAt: dbtime.Now(),
|
UpdatedAt: dbtime.Now(),
|
||||||
ProvisionerState: jobType.WorkspaceBuild.State,
|
ProvisionerState: jobType.WorkspaceBuild.State,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("update workspace build state: %w", err)
|
||||||
|
}
|
||||||
|
err = db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||||
|
ID: input.WorkspaceBuildID,
|
||||||
|
UpdatedAt: dbtime.Now(),
|
||||||
Deadline: build.Deadline,
|
Deadline: build.Deadline,
|
||||||
MaxDeadline: build.MaxDeadline,
|
MaxDeadline: build.MaxDeadline,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("update workspace build state: %w", err)
|
return xerrors.Errorf("update workspace build deadline: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1114,15 +1121,22 @@ func (s *server) CompleteJob(ctx context.Context, completed *proto.CompletedJob)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("update provisioner job: %w", err)
|
return xerrors.Errorf("update provisioner job: %w", err)
|
||||||
}
|
}
|
||||||
err = db.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
err = db.UpdateWorkspaceBuildProvisionerStateByID(ctx, database.UpdateWorkspaceBuildProvisionerStateByIDParams{
|
||||||
ID: workspaceBuild.ID,
|
ID: workspaceBuild.ID,
|
||||||
Deadline: autoStop.Deadline,
|
|
||||||
MaxDeadline: autoStop.MaxDeadline,
|
|
||||||
ProvisionerState: jobType.WorkspaceBuild.State,
|
ProvisionerState: jobType.WorkspaceBuild.State,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("update workspace build: %w", err)
|
return xerrors.Errorf("update workspace build provisioner state: %w", err)
|
||||||
|
}
|
||||||
|
err = db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||||
|
ID: workspaceBuild.ID,
|
||||||
|
Deadline: autoStop.Deadline,
|
||||||
|
MaxDeadline: autoStop.MaxDeadline,
|
||||||
|
UpdatedAt: now,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("update workspace build deadline: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
agentTimeouts := make(map[time.Duration]bool) // A set of agent timeouts.
|
agentTimeouts := make(map[time.Duration]bool) // A set of agent timeouts.
|
||||||
|
@ -333,12 +333,10 @@ func unhangJob(ctx context.Context, log slog.Logger, db database.Store, pub pubs
|
|||||||
return xerrors.Errorf("get previous workspace build: %w", err)
|
return xerrors.Errorf("get previous workspace build: %w", err)
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = db.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
err = db.UpdateWorkspaceBuildProvisionerStateByID(ctx, database.UpdateWorkspaceBuildProvisionerStateByIDParams{
|
||||||
ID: build.ID,
|
ID: build.ID,
|
||||||
UpdatedAt: dbtime.Now(),
|
UpdatedAt: dbtime.Now(),
|
||||||
ProvisionerState: prevBuild.ProvisionerState,
|
ProvisionerState: prevBuild.ProvisionerState,
|
||||||
Deadline: time.Time{},
|
|
||||||
MaxDeadline: time.Time{},
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("update workspace build by id: %w", err)
|
return xerrors.Errorf("update workspace build by id: %w", err)
|
||||||
|
@ -941,10 +941,9 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return xerrors.New("Cannot extend workspace: deadline is beyond max deadline imposed by template")
|
return xerrors.New("Cannot extend workspace: deadline is beyond max deadline imposed by template")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
if err := s.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||||
ID: build.ID,
|
ID: build.ID,
|
||||||
UpdatedAt: build.UpdatedAt,
|
UpdatedAt: dbtime.Now(),
|
||||||
ProvisionerState: build.ProvisionerState,
|
|
||||||
Deadline: newDeadline,
|
Deadline: newDeadline,
|
||||||
MaxDeadline: build.MaxDeadline,
|
MaxDeadline: build.MaxDeadline,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -278,8 +278,8 @@ func (s *EnterpriseTemplateScheduleStore) updateWorkspaceBuild(ctx context.Conte
|
|||||||
autostop.Deadline = autostop.MaxDeadline
|
autostop.Deadline = autostop.MaxDeadline
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the workspace build.
|
// Update the workspace build deadline.
|
||||||
err = db.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
err = db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||||
ID: build.ID,
|
ID: build.ID,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
Deadline: autostop.Deadline,
|
Deadline: autostop.Deadline,
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||||
agplschedule "github.com/coder/coder/v2/coderd/schedule"
|
agplschedule "github.com/coder/coder/v2/coderd/schedule"
|
||||||
|
"github.com/coder/coder/v2/cryptorand"
|
||||||
"github.com/coder/coder/v2/enterprise/coderd/schedule"
|
"github.com/coder/coder/v2/enterprise/coderd/schedule"
|
||||||
"github.com/coder/coder/v2/testutil"
|
"github.com/coder/coder/v2/testutil"
|
||||||
)
|
)
|
||||||
@ -164,9 +165,13 @@ func TestTemplateUpdateBuildDeadlines(t *testing.T) {
|
|||||||
JobID: job.ID,
|
JobID: job.ID,
|
||||||
InitiatorID: user.ID,
|
InitiatorID: user.ID,
|
||||||
TemplateVersionID: templateVersion.ID,
|
TemplateVersionID: templateVersion.ID,
|
||||||
|
ProvisionerState: []byte(must(cryptorand.String(64))),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Assert test invariant: workspace build state must not be empty
|
||||||
|
require.NotEmpty(t, wsBuild.ProvisionerState, "provisioner state must not be empty")
|
||||||
|
|
||||||
acquiredJob, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
acquiredJob, err := db.AcquireProvisionerJob(ctx, database.AcquireProvisionerJobParams{
|
||||||
StartedAt: sql.NullTime{
|
StartedAt: sql.NullTime{
|
||||||
Time: buildTime,
|
Time: buildTime,
|
||||||
@ -191,10 +196,9 @@ func TestTemplateUpdateBuildDeadlines(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = db.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
err = db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||||
ID: wsBuild.ID,
|
ID: wsBuild.ID,
|
||||||
UpdatedAt: buildTime,
|
UpdatedAt: buildTime,
|
||||||
ProvisionerState: []byte{},
|
|
||||||
Deadline: c.deadline,
|
Deadline: c.deadline,
|
||||||
MaxDeadline: c.maxDeadline,
|
MaxDeadline: c.maxDeadline,
|
||||||
})
|
})
|
||||||
@ -240,6 +244,9 @@ func TestTemplateUpdateBuildDeadlines(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.WithinDuration(t, c.newDeadline, newBuild.Deadline, time.Second)
|
require.WithinDuration(t, c.newDeadline, newBuild.Deadline, time.Second)
|
||||||
require.WithinDuration(t, c.newMaxDeadline, newBuild.MaxDeadline, time.Second)
|
require.WithinDuration(t, c.newMaxDeadline, newBuild.MaxDeadline, time.Second)
|
||||||
|
|
||||||
|
// Check that the new build has the same state as before.
|
||||||
|
require.Equal(t, wsBuild.ProvisionerState, newBuild.ProvisionerState, "provisioner state mismatch")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -420,12 +427,15 @@ func TestTemplateUpdateBuildDeadlinesSkip(t *testing.T) {
|
|||||||
JobID: job.ID,
|
JobID: job.ID,
|
||||||
InitiatorID: user.ID,
|
InitiatorID: user.ID,
|
||||||
TemplateVersionID: templateVersion.ID,
|
TemplateVersionID: templateVersion.ID,
|
||||||
|
ProvisionerState: []byte(must(cryptorand.String(64))),
|
||||||
})
|
})
|
||||||
|
|
||||||
err := db.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{
|
// Assert test invariant: workspace build state must not be empty
|
||||||
|
require.NotEmpty(t, wsBuild.ProvisionerState, "provisioner state must not be empty")
|
||||||
|
|
||||||
|
err := db.UpdateWorkspaceBuildDeadlineByID(ctx, database.UpdateWorkspaceBuildDeadlineByIDParams{
|
||||||
ID: wsBuild.ID,
|
ID: wsBuild.ID,
|
||||||
UpdatedAt: buildTime,
|
UpdatedAt: buildTime,
|
||||||
ProvisionerState: []byte{},
|
|
||||||
Deadline: originalMaxDeadline,
|
Deadline: originalMaxDeadline,
|
||||||
MaxDeadline: originalMaxDeadline,
|
MaxDeadline: originalMaxDeadline,
|
||||||
})
|
})
|
||||||
@ -434,6 +444,9 @@ func TestTemplateUpdateBuildDeadlinesSkip(t *testing.T) {
|
|||||||
wsBuild, err = db.GetWorkspaceBuildByID(ctx, wsBuild.ID)
|
wsBuild, err = db.GetWorkspaceBuildByID(ctx, wsBuild.ID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Assert test invariant: workspace build state must not be empty
|
||||||
|
require.NotEmpty(t, wsBuild.ProvisionerState, "provisioner state must not be empty")
|
||||||
|
|
||||||
builds[i].wsBuild = wsBuild
|
builds[i].wsBuild = wsBuild
|
||||||
|
|
||||||
if !b.buildStarted {
|
if !b.buildStarted {
|
||||||
@ -519,5 +532,14 @@ func TestTemplateUpdateBuildDeadlinesSkip(t *testing.T) {
|
|||||||
assert.WithinDuration(t, originalMaxDeadline, newBuild.Deadline, time.Second, msg)
|
assert.WithinDuration(t, originalMaxDeadline, newBuild.Deadline, time.Second, msg)
|
||||||
assert.WithinDuration(t, originalMaxDeadline, newBuild.MaxDeadline, time.Second, msg)
|
assert.WithinDuration(t, originalMaxDeadline, newBuild.MaxDeadline, time.Second, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, builds[i].wsBuild.ProvisionerState, newBuild.ProvisionerState, "provisioner state mismatch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func must[V any](v V, err error) V {
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user