mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: reinitialize agents when a prebuilt workspace is claimed (#17475)
This pull request allows coder workspace agents to be reinitialized when a prebuilt workspace is claimed by a user. This facilitates the transfer of ownership between the anonymous prebuilds system user and the new owner of the workspace. Only a single agent per prebuilt workspace is supported for now, but plumbing has already been done to facilitate the seamless transition to multi-agent support. --------- Signed-off-by: Danny Kopping <dannykopping@gmail.com> Co-authored-by: Danny Kopping <dannykopping@gmail.com>
This commit is contained in:
co-authored by
Danny Kopping
parent
fcbdd1a28e
commit
425ee6fa55
@@ -3020,6 +3020,15 @@ func (q *querier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uui
|
||||
return q.db.GetWorkspaceAgentsByResourceIDs(ctx, ids)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx context.Context, arg database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams) ([]database.WorkspaceAgent, error) {
|
||||
_, err := q.GetWorkspaceByID(ctx, arg.WorkspaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return q.db.GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceAgent, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -2009,6 +2009,38 @@ func (s *MethodTestSuite) TestWorkspace() {
|
||||
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
|
||||
check.Args(agt.ID).Asserts(w, policy.ActionRead).Returns(agt)
|
||||
}))
|
||||
s.Run("GetWorkspaceAgentsByWorkspaceAndBuildNumber", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
tpl := dbgen.Template(s.T(), db, database.Template{
|
||||
OrganizationID: o.ID,
|
||||
CreatedBy: u.ID,
|
||||
})
|
||||
tv := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{
|
||||
TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true},
|
||||
OrganizationID: o.ID,
|
||||
CreatedBy: u.ID,
|
||||
})
|
||||
w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{
|
||||
TemplateID: tpl.ID,
|
||||
OrganizationID: o.ID,
|
||||
OwnerID: u.ID,
|
||||
})
|
||||
j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{
|
||||
Type: database.ProvisionerJobTypeWorkspaceBuild,
|
||||
})
|
||||
b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{
|
||||
JobID: j.ID,
|
||||
WorkspaceID: w.ID,
|
||||
TemplateVersionID: tv.ID,
|
||||
})
|
||||
res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: b.JobID})
|
||||
agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID})
|
||||
check.Args(database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams{
|
||||
WorkspaceID: w.ID,
|
||||
BuildNumber: 1,
|
||||
}).Asserts(w, policy.ActionRead).Returns([]database.WorkspaceAgent{agt})
|
||||
}))
|
||||
s.Run("GetWorkspaceAgentLifecycleStateByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
|
||||
@@ -294,6 +294,8 @@ type TemplateVersionBuilder struct {
|
||||
ps pubsub.Pubsub
|
||||
resources []*sdkproto.Resource
|
||||
params []database.TemplateVersionParameter
|
||||
presets []database.TemplateVersionPreset
|
||||
presetParams []database.TemplateVersionPresetParameter
|
||||
promote bool
|
||||
autoCreateTemplate bool
|
||||
}
|
||||
@@ -339,6 +341,13 @@ func (t TemplateVersionBuilder) Params(ps ...database.TemplateVersionParameter)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t TemplateVersionBuilder) Preset(preset database.TemplateVersionPreset, params ...database.TemplateVersionPresetParameter) TemplateVersionBuilder {
|
||||
// nolint: revive // returns modified struct
|
||||
t.presets = append(t.presets, preset)
|
||||
t.presetParams = append(t.presetParams, params...)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t TemplateVersionBuilder) SkipCreateTemplate() TemplateVersionBuilder {
|
||||
// nolint: revive // returns modified struct
|
||||
t.autoCreateTemplate = false
|
||||
@@ -378,6 +387,25 @@ func (t TemplateVersionBuilder) Do() TemplateVersionResponse {
|
||||
require.NoError(t.t, err)
|
||||
}
|
||||
|
||||
for _, preset := range t.presets {
|
||||
dbgen.Preset(t.t, t.db, database.InsertPresetParams{
|
||||
ID: preset.ID,
|
||||
TemplateVersionID: version.ID,
|
||||
Name: preset.Name,
|
||||
CreatedAt: version.CreatedAt,
|
||||
DesiredInstances: preset.DesiredInstances,
|
||||
InvalidateAfterSecs: preset.InvalidateAfterSecs,
|
||||
})
|
||||
}
|
||||
|
||||
for _, presetParam := range t.presetParams {
|
||||
dbgen.PresetParameter(t.t, t.db, database.InsertPresetParametersParams{
|
||||
TemplateVersionPresetID: presetParam.TemplateVersionPresetID,
|
||||
Names: []string{presetParam.Name},
|
||||
Values: []string{presetParam.Value},
|
||||
})
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(provisionerdserver.TemplateVersionImportJob{
|
||||
TemplateVersionID: t.seed.ID,
|
||||
})
|
||||
|
||||
@@ -1224,6 +1224,7 @@ func TelemetryItem(t testing.TB, db database.Store, seed database.TelemetryItem)
|
||||
|
||||
func Preset(t testing.TB, db database.Store, seed database.InsertPresetParams) database.TemplateVersionPreset {
|
||||
preset, err := db.InsertPreset(genCtx, database.InsertPresetParams{
|
||||
ID: takeFirst(seed.ID, uuid.New()),
|
||||
TemplateVersionID: takeFirst(seed.TemplateVersionID, uuid.New()),
|
||||
Name: takeFirst(seed.Name, testutil.GetRandomName(t)),
|
||||
CreatedAt: takeFirst(seed.CreatedAt, dbtime.Now()),
|
||||
|
||||
@@ -7654,6 +7654,30 @@ func (q *FakeQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, resou
|
||||
return q.getWorkspaceAgentsByResourceIDsNoLock(ctx, resourceIDs)
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx context.Context, arg database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams) ([]database.WorkspaceAgent, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
build, err := q.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams(arg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resources, err := q.getWorkspaceResourcesByJobIDNoLock(ctx, build.JobID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resourceIDs []uuid.UUID
|
||||
for _, resource := range resources {
|
||||
resourceIDs = append(resourceIDs, resource.ID)
|
||||
}
|
||||
|
||||
return q.GetWorkspaceAgentsByResourceIDs(ctx, resourceIDs)
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetWorkspaceAgentsCreatedAfter(_ context.Context, after time.Time) ([]database.WorkspaceAgent, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
@@ -1754,6 +1754,13 @@ func (m queryMetricsStore) GetWorkspaceAgentsByResourceIDs(ctx context.Context,
|
||||
return agents, err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx context.Context, arg database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams) ([]database.WorkspaceAgent, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("GetWorkspaceAgentsByWorkspaceAndBuildNumber").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceAgent, error) {
|
||||
start := time.Now()
|
||||
agents, err := m.s.GetWorkspaceAgentsCreatedAfter(ctx, createdAt)
|
||||
|
||||
@@ -3678,6 +3678,21 @@ func (mr *MockStoreMockRecorder) GetWorkspaceAgentsByResourceIDs(ctx, ids any) *
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentsByResourceIDs", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentsByResourceIDs), ctx, ids)
|
||||
}
|
||||
|
||||
// GetWorkspaceAgentsByWorkspaceAndBuildNumber mocks base method.
|
||||
func (m *MockStore) GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx context.Context, arg database.GetWorkspaceAgentsByWorkspaceAndBuildNumberParams) ([]database.WorkspaceAgent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetWorkspaceAgentsByWorkspaceAndBuildNumber", ctx, arg)
|
||||
ret0, _ := ret[0].([]database.WorkspaceAgent)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetWorkspaceAgentsByWorkspaceAndBuildNumber indicates an expected call of GetWorkspaceAgentsByWorkspaceAndBuildNumber.
|
||||
func (mr *MockStoreMockRecorder) GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetWorkspaceAgentsByWorkspaceAndBuildNumber", reflect.TypeOf((*MockStore)(nil).GetWorkspaceAgentsByWorkspaceAndBuildNumber), ctx, arg)
|
||||
}
|
||||
|
||||
// GetWorkspaceAgentsCreatedAfter mocks base method.
|
||||
func (m *MockStore) GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]database.WorkspaceAgent, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -400,6 +400,7 @@ type sqlcQuerier interface {
|
||||
GetWorkspaceAgentUsageStats(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentUsageStatsRow, error)
|
||||
GetWorkspaceAgentUsageStatsAndLabels(ctx context.Context, createdAt time.Time) ([]GetWorkspaceAgentUsageStatsAndLabelsRow, error)
|
||||
GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgent, error)
|
||||
GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx context.Context, arg GetWorkspaceAgentsByWorkspaceAndBuildNumberParams) ([]WorkspaceAgent, error)
|
||||
GetWorkspaceAgentsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceAgent, error)
|
||||
GetWorkspaceAgentsInLatestBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceAgent, error)
|
||||
GetWorkspaceAppByAgentIDAndSlug(ctx context.Context, arg GetWorkspaceAppByAgentIDAndSlugParams) (WorkspaceApp, error)
|
||||
|
||||
@@ -6678,6 +6678,7 @@ func (q *sqlQuerier) GetPresetsByTemplateVersionID(ctx context.Context, template
|
||||
|
||||
const insertPreset = `-- name: InsertPreset :one
|
||||
INSERT INTO template_version_presets (
|
||||
id,
|
||||
template_version_id,
|
||||
name,
|
||||
created_at,
|
||||
@@ -6689,11 +6690,13 @@ VALUES (
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5
|
||||
$5,
|
||||
$6
|
||||
) RETURNING id, template_version_id, name, created_at, desired_instances, invalidate_after_secs
|
||||
`
|
||||
|
||||
type InsertPresetParams struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
@@ -6703,6 +6706,7 @@ type InsertPresetParams struct {
|
||||
|
||||
func (q *sqlQuerier) InsertPreset(ctx context.Context, arg InsertPresetParams) (TemplateVersionPreset, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertPreset,
|
||||
arg.ID,
|
||||
arg.TemplateVersionID,
|
||||
arg.Name,
|
||||
arg.CreatedAt,
|
||||
@@ -14416,6 +14420,81 @@ func (q *sqlQuerier) GetWorkspaceAgentsByResourceIDs(ctx context.Context, ids []
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWorkspaceAgentsByWorkspaceAndBuildNumber = `-- name: GetWorkspaceAgentsByWorkspaceAndBuildNumber :many
|
||||
SELECT
|
||||
workspace_agents.id, workspace_agents.created_at, workspace_agents.updated_at, workspace_agents.name, workspace_agents.first_connected_at, workspace_agents.last_connected_at, workspace_agents.disconnected_at, workspace_agents.resource_id, workspace_agents.auth_token, workspace_agents.auth_instance_id, workspace_agents.architecture, workspace_agents.environment_variables, workspace_agents.operating_system, workspace_agents.instance_metadata, workspace_agents.resource_metadata, workspace_agents.directory, workspace_agents.version, workspace_agents.last_connected_replica_id, workspace_agents.connection_timeout_seconds, workspace_agents.troubleshooting_url, workspace_agents.motd_file, workspace_agents.lifecycle_state, workspace_agents.expanded_directory, workspace_agents.logs_length, workspace_agents.logs_overflowed, workspace_agents.started_at, workspace_agents.ready_at, workspace_agents.subsystems, workspace_agents.display_apps, workspace_agents.api_version, workspace_agents.display_order, workspace_agents.parent_id
|
||||
FROM
|
||||
workspace_agents
|
||||
JOIN
|
||||
workspace_resources ON workspace_agents.resource_id = workspace_resources.id
|
||||
JOIN
|
||||
workspace_builds ON workspace_resources.job_id = workspace_builds.job_id
|
||||
WHERE
|
||||
workspace_builds.workspace_id = $1 :: uuid AND
|
||||
workspace_builds.build_number = $2 :: int
|
||||
`
|
||||
|
||||
type GetWorkspaceAgentsByWorkspaceAndBuildNumberParams struct {
|
||||
WorkspaceID uuid.UUID `db:"workspace_id" json:"workspace_id"`
|
||||
BuildNumber int32 `db:"build_number" json:"build_number"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetWorkspaceAgentsByWorkspaceAndBuildNumber(ctx context.Context, arg GetWorkspaceAgentsByWorkspaceAndBuildNumberParams) ([]WorkspaceAgent, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWorkspaceAgentsByWorkspaceAndBuildNumber, arg.WorkspaceID, arg.BuildNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []WorkspaceAgent
|
||||
for rows.Next() {
|
||||
var i WorkspaceAgent
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Name,
|
||||
&i.FirstConnectedAt,
|
||||
&i.LastConnectedAt,
|
||||
&i.DisconnectedAt,
|
||||
&i.ResourceID,
|
||||
&i.AuthToken,
|
||||
&i.AuthInstanceID,
|
||||
&i.Architecture,
|
||||
&i.EnvironmentVariables,
|
||||
&i.OperatingSystem,
|
||||
&i.InstanceMetadata,
|
||||
&i.ResourceMetadata,
|
||||
&i.Directory,
|
||||
&i.Version,
|
||||
&i.LastConnectedReplicaID,
|
||||
&i.ConnectionTimeoutSeconds,
|
||||
&i.TroubleshootingURL,
|
||||
&i.MOTDFile,
|
||||
&i.LifecycleState,
|
||||
&i.ExpandedDirectory,
|
||||
&i.LogsLength,
|
||||
&i.LogsOverflowed,
|
||||
&i.StartedAt,
|
||||
&i.ReadyAt,
|
||||
pq.Array(&i.Subsystems),
|
||||
pq.Array(&i.DisplayApps),
|
||||
&i.APIVersion,
|
||||
&i.DisplayOrder,
|
||||
&i.ParentID,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWorkspaceAgentsCreatedAfter = `-- name: GetWorkspaceAgentsCreatedAfter :many
|
||||
SELECT id, created_at, updated_at, name, first_connected_at, last_connected_at, disconnected_at, resource_id, auth_token, auth_instance_id, architecture, environment_variables, operating_system, instance_metadata, resource_metadata, directory, version, last_connected_replica_id, connection_timeout_seconds, troubleshooting_url, motd_file, lifecycle_state, expanded_directory, logs_length, logs_overflowed, started_at, ready_at, subsystems, display_apps, api_version, display_order, parent_id FROM workspace_agents WHERE created_at > $1
|
||||
`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
-- name: InsertPreset :one
|
||||
INSERT INTO template_version_presets (
|
||||
id,
|
||||
template_version_id,
|
||||
name,
|
||||
created_at,
|
||||
@@ -7,6 +8,7 @@ INSERT INTO template_version_presets (
|
||||
invalidate_after_secs
|
||||
)
|
||||
VALUES (
|
||||
@id,
|
||||
@template_version_id,
|
||||
@name,
|
||||
@created_at,
|
||||
|
||||
@@ -253,6 +253,19 @@ WHERE
|
||||
wb.workspace_id = @workspace_id :: uuid
|
||||
);
|
||||
|
||||
-- name: GetWorkspaceAgentsByWorkspaceAndBuildNumber :many
|
||||
SELECT
|
||||
workspace_agents.*
|
||||
FROM
|
||||
workspace_agents
|
||||
JOIN
|
||||
workspace_resources ON workspace_agents.resource_id = workspace_resources.id
|
||||
JOIN
|
||||
workspace_builds ON workspace_resources.job_id = workspace_builds.job_id
|
||||
WHERE
|
||||
workspace_builds.workspace_id = @workspace_id :: uuid AND
|
||||
workspace_builds.build_number = @build_number :: int;
|
||||
|
||||
-- name: GetWorkspaceAgentAndLatestBuildByAuthToken :one
|
||||
SELECT
|
||||
sqlc.embed(workspaces),
|
||||
|
||||
Reference in New Issue
Block a user