From 4b7ed1a99e326a8facbe323143205de772548115 Mon Sep 17 00:00:00 2001 From: Kacper Sawicki Date: Mon, 11 Aug 2025 10:10:21 +0000 Subject: [PATCH 1/4] feat(database): add `has_external_agent` flag to template_versions and workspace_builds --- coderd/database/dbauthz/dbauthz.go | 52 +-- coderd/database/dbauthz/dbauthz_test.go | 18 +- coderd/database/dbgen/dbgen.go | 26 +- coderd/database/dbmetrics/querymetrics.go | 28 +- coderd/database/dbmock/dbmock.go | 56 +-- coderd/database/dump.sql | 6 +- .../000358_external_agents.down.sql | 77 ++++ .../migrations/000358_external_agents.up.sql | 89 ++++ coderd/database/modelqueries.go | 3 + coderd/database/models.go | 12 +- coderd/database/querier.go | 4 +- coderd/database/queries.sql.go | 383 +++++++++++------- coderd/database/queries/templates.sql | 6 + coderd/database/queries/templateversions.sql | 19 +- coderd/database/queries/workspacebuilds.sql | 19 +- coderd/database/queries/workspaces.sql | 13 +- .../provisionerdserver/provisionerdserver.go | 12 +- docs/admin/security/audit-logs.md | 4 +- enterprise/audit/table.go | 2 + 19 files changed, 570 insertions(+), 259 deletions(-) create mode 100644 coderd/database/migrations/000358_external_agents.down.sql create mode 100644 coderd/database/migrations/000358_external_agents.up.sql diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 321543fca68f8..7ae1e4bbf9b73 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -4733,9 +4733,9 @@ func (q *querier) UpdateTemplateScheduleByID(ctx context.Context, arg database.U return update(q.log, q.auth, fetch, q.db.UpdateTemplateScheduleByID)(ctx, arg) } -func (q *querier) UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg database.UpdateTemplateVersionAITaskByJobIDParams) error { - // An actor is allowed to update the template version AI task flag if they are authorized to update the template. - tv, err := q.db.GetTemplateVersionByJobID(ctx, arg.JobID) +func (q *querier) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) error { + // An actor is allowed to update the template version if they are authorized to update the template. + tv, err := q.db.GetTemplateVersionByID(ctx, arg.ID) if err != nil { return err } @@ -4752,12 +4752,12 @@ func (q *querier) UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg da if err := q.authorizeContext(ctx, policy.ActionUpdate, obj); err != nil { return err } - return q.db.UpdateTemplateVersionAITaskByJobID(ctx, arg) + return q.db.UpdateTemplateVersionByID(ctx, arg) } -func (q *querier) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) error { - // An actor is allowed to update the template version if they are authorized to update the template. - tv, err := q.db.GetTemplateVersionByID(ctx, arg.ID) +func (q *querier) UpdateTemplateVersionDescriptionByJobID(ctx context.Context, arg database.UpdateTemplateVersionDescriptionByJobIDParams) error { + // An actor is allowed to update the template version description if they are authorized to update the template. + tv, err := q.db.GetTemplateVersionByJobID(ctx, arg.JobID) if err != nil { return err } @@ -4774,11 +4774,11 @@ func (q *querier) UpdateTemplateVersionByID(ctx context.Context, arg database.Up if err := q.authorizeContext(ctx, policy.ActionUpdate, obj); err != nil { return err } - return q.db.UpdateTemplateVersionByID(ctx, arg) + return q.db.UpdateTemplateVersionDescriptionByJobID(ctx, arg) } -func (q *querier) UpdateTemplateVersionDescriptionByJobID(ctx context.Context, arg database.UpdateTemplateVersionDescriptionByJobIDParams) error { - // An actor is allowed to update the template version description if they are authorized to update the template. +func (q *querier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg database.UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error { + // An actor is allowed to update the template version external auth providers if they are authorized to update the template. tv, err := q.db.GetTemplateVersionByJobID(ctx, arg.JobID) if err != nil { return err @@ -4796,11 +4796,11 @@ func (q *querier) UpdateTemplateVersionDescriptionByJobID(ctx context.Context, a if err := q.authorizeContext(ctx, policy.ActionUpdate, obj); err != nil { return err } - return q.db.UpdateTemplateVersionDescriptionByJobID(ctx, arg) + return q.db.UpdateTemplateVersionExternalAuthProvidersByJobID(ctx, arg) } -func (q *querier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg database.UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error { - // An actor is allowed to update the template version external auth providers if they are authorized to update the template. +func (q *querier) UpdateTemplateVersionFlagsByJobID(ctx context.Context, arg database.UpdateTemplateVersionFlagsByJobIDParams) error { + // An actor is allowed to update the template version ai task and external agent flag if they are authorized to update the template. tv, err := q.db.GetTemplateVersionByJobID(ctx, arg.JobID) if err != nil { return err @@ -4818,7 +4818,7 @@ func (q *querier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context. if err := q.authorizeContext(ctx, policy.ActionUpdate, obj); err != nil { return err } - return q.db.UpdateTemplateVersionExternalAuthProvidersByJobID(ctx, arg) + return q.db.UpdateTemplateVersionFlagsByJobID(ctx, arg) } func (q *querier) UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg database.UpdateTemplateWorkspacesLastUsedAtParams) error { @@ -5143,7 +5143,15 @@ func (q *querier) UpdateWorkspaceAutostart(ctx context.Context, arg database.Upd return update(q.log, q.auth, fetch, q.db.UpdateWorkspaceAutostart)(ctx, arg) } -func (q *querier) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg database.UpdateWorkspaceBuildAITaskByIDParams) 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, policy.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) if err != nil { return err @@ -5158,18 +5166,10 @@ func (q *querier) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg databa if err != nil { return err } - return q.db.UpdateWorkspaceBuildAITaskByID(ctx, arg) -} - -// 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, policy.ActionUpdate, rbac.ResourceSystem); err != nil { - return err - } - return q.db.UpdateWorkspaceBuildCostByID(ctx, arg) + return q.db.UpdateWorkspaceBuildDeadlineByID(ctx, arg) } -func (q *querier) UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg database.UpdateWorkspaceBuildDeadlineByIDParams) error { +func (q *querier) UpdateWorkspaceBuildFlagsByID(ctx context.Context, arg database.UpdateWorkspaceBuildFlagsByIDParams) error { build, err := q.db.GetWorkspaceBuildByID(ctx, arg.ID) if err != nil { return err @@ -5184,7 +5184,7 @@ func (q *querier) UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg data if err != nil { return err } - return q.db.UpdateWorkspaceBuildDeadlineByID(ctx, arg) + return q.db.UpdateWorkspaceBuildFlagsByID(ctx, arg) } func (q *querier) UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg database.UpdateWorkspaceBuildProvisionerStateByIDParams) error { diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index b78aaab8013b5..084e5bbfb82e1 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -1355,13 +1355,13 @@ func (s *MethodTestSuite) TestTemplate() { dbm.EXPECT().UpdateTemplateScheduleByID(gomock.Any(), arg).Return(nil).AnyTimes() check.Args(arg).Asserts(t1, policy.ActionUpdate) })) - s.Run("UpdateTemplateVersionAITaskByJobID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { + s.Run("UpdateTemplateVersionFlagsByJobID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { t := testutil.Fake(s.T(), faker, database.Template{}) tv := testutil.Fake(s.T(), faker, database.TemplateVersion{TemplateID: uuid.NullUUID{UUID: t.ID, Valid: true}}) - arg := database.UpdateTemplateVersionAITaskByJobIDParams{JobID: tv.JobID, HasAITask: sql.NullBool{Bool: true, Valid: true}} + arg := database.UpdateTemplateVersionFlagsByJobIDParams{JobID: tv.JobID, HasAITask: sql.NullBool{Bool: true, Valid: true}, HasExternalAgent: sql.NullBool{Bool: true, Valid: true}} dbm.EXPECT().GetTemplateVersionByJobID(gomock.Any(), tv.JobID).Return(tv, nil).AnyTimes() dbm.EXPECT().GetTemplateByID(gomock.Any(), t.ID).Return(t, nil).AnyTimes() - dbm.EXPECT().UpdateTemplateVersionAITaskByJobID(gomock.Any(), arg).Return(nil).AnyTimes() + dbm.EXPECT().UpdateTemplateVersionFlagsByJobID(gomock.Any(), arg).Return(nil).AnyTimes() check.Args(arg).Asserts(t, policy.ActionUpdate) })) s.Run("UpdateTemplateWorkspacesLastUsedAt", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { @@ -2955,7 +2955,7 @@ func (s *MethodTestSuite) TestWorkspace() { Deadline: b.Deadline, }).Asserts(w, policy.ActionUpdate) })) - s.Run("UpdateWorkspaceBuildAITaskByID", s.Subtest(func(db database.Store, check *expects) { + s.Run("UpdateWorkspaceBuildFlagsByID", 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{ @@ -2983,10 +2983,12 @@ func (s *MethodTestSuite) TestWorkspace() { res := dbgen.WorkspaceResource(s.T(), db, database.WorkspaceResource{JobID: b.JobID}) agt := dbgen.WorkspaceAgent(s.T(), db, database.WorkspaceAgent{ResourceID: res.ID}) app := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: agt.ID}) - check.Args(database.UpdateWorkspaceBuildAITaskByIDParams{ - HasAITask: sql.NullBool{Bool: true, Valid: true}, - SidebarAppID: uuid.NullUUID{UUID: app.ID, Valid: true}, - ID: b.ID, + check.Args(database.UpdateWorkspaceBuildFlagsByIDParams{ + ID: b.ID, + HasAITask: sql.NullBool{Bool: true, Valid: true}, + HasExternalAgent: sql.NullBool{Bool: true, Valid: true}, + SidebarAppID: uuid.NullUUID{UUID: app.ID, Valid: true}, + UpdatedAt: b.UpdatedAt, }).Asserts(w, policy.ActionUpdate) })) s.Run("SoftDeleteWorkspaceByID", s.Subtest(func(db database.Store, check *expects) { diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 56927507b6109..fbf886f860d4c 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -437,6 +437,7 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil jobID := takeFirst(orig.JobID, uuid.New()) hasAITask := takeFirst(orig.HasAITask, sql.NullBool{}) sidebarAppID := takeFirst(orig.AITaskSidebarAppID, uuid.NullUUID{}) + hasExternalAgent := takeFirst(orig.HasExternalAgent, sql.NullBool{}) var build database.WorkspaceBuild err := db.InTx(func(db database.Store) error { err := db.InsertWorkspaceBuild(genCtx, database.InsertWorkspaceBuildParams{ @@ -470,12 +471,13 @@ func WorkspaceBuild(t testing.TB, db database.Store, orig database.WorkspaceBuil require.NoError(t, err) } - if hasAITask.Valid { - require.NoError(t, db.UpdateWorkspaceBuildAITaskByID(genCtx, database.UpdateWorkspaceBuildAITaskByIDParams{ - HasAITask: hasAITask, - SidebarAppID: sidebarAppID, - UpdatedAt: dbtime.Now(), - ID: buildID, + if hasAITask.Valid || hasExternalAgent.Valid { + require.NoError(t, db.UpdateWorkspaceBuildFlagsByID(genCtx, database.UpdateWorkspaceBuildFlagsByIDParams{ + ID: buildID, + HasAITask: hasAITask, + HasExternalAgent: hasExternalAgent, + SidebarAppID: sidebarAppID, + UpdatedAt: dbtime.Now(), })) } @@ -1028,6 +1030,7 @@ func ExternalAuthLink(t testing.TB, db database.Store, orig database.ExternalAut func TemplateVersion(t testing.TB, db database.Store, orig database.TemplateVersion) database.TemplateVersion { var version database.TemplateVersion hasAITask := takeFirst(orig.HasAITask, sql.NullBool{}) + hasExternalAgent := takeFirst(orig.HasExternalAgent, sql.NullBool{}) jobID := takeFirst(orig.JobID, uuid.New()) err := db.InTx(func(db database.Store) error { versionID := takeFirst(orig.ID, uuid.New()) @@ -1048,11 +1051,12 @@ func TemplateVersion(t testing.TB, db database.Store, orig database.TemplateVers return err } - if hasAITask.Valid { - require.NoError(t, db.UpdateTemplateVersionAITaskByJobID(genCtx, database.UpdateTemplateVersionAITaskByJobIDParams{ - JobID: jobID, - HasAITask: hasAITask, - UpdatedAt: dbtime.Now(), + if hasAITask.Valid || hasExternalAgent.Valid { + require.NoError(t, db.UpdateTemplateVersionFlagsByJobID(genCtx, database.UpdateTemplateVersionFlagsByJobIDParams{ + JobID: jobID, + HasAITask: hasAITask, + HasExternalAgent: hasExternalAgent, + UpdatedAt: dbtime.Now(), })) } diff --git a/coderd/database/dbmetrics/querymetrics.go b/coderd/database/dbmetrics/querymetrics.go index 01576c80f3544..12133997bf2c9 100644 --- a/coderd/database/dbmetrics/querymetrics.go +++ b/coderd/database/dbmetrics/querymetrics.go @@ -2917,13 +2917,6 @@ func (m queryMetricsStore) UpdateTemplateScheduleByID(ctx context.Context, arg d return err } -func (m queryMetricsStore) UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg database.UpdateTemplateVersionAITaskByJobIDParams) error { - start := time.Now() - r0 := m.s.UpdateTemplateVersionAITaskByJobID(ctx, arg) - m.queryLatencies.WithLabelValues("UpdateTemplateVersionAITaskByJobID").Observe(time.Since(start).Seconds()) - return r0 -} - func (m queryMetricsStore) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) error { start := time.Now() err := m.s.UpdateTemplateVersionByID(ctx, arg) @@ -2945,6 +2938,13 @@ func (m queryMetricsStore) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx return err } +func (m queryMetricsStore) UpdateTemplateVersionFlagsByJobID(ctx context.Context, arg database.UpdateTemplateVersionFlagsByJobIDParams) error { + start := time.Now() + r0 := m.s.UpdateTemplateVersionFlagsByJobID(ctx, arg) + m.queryLatencies.WithLabelValues("UpdateTemplateVersionFlagsByJobID").Observe(time.Since(start).Seconds()) + return r0 +} + func (m queryMetricsStore) UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg database.UpdateTemplateWorkspacesLastUsedAtParams) error { start := time.Now() r0 := m.s.UpdateTemplateWorkspacesLastUsedAt(ctx, arg) @@ -3148,13 +3148,6 @@ func (m queryMetricsStore) UpdateWorkspaceAutostart(ctx context.Context, arg dat return err } -func (m queryMetricsStore) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg database.UpdateWorkspaceBuildAITaskByIDParams) error { - start := time.Now() - r0 := m.s.UpdateWorkspaceBuildAITaskByID(ctx, arg) - m.queryLatencies.WithLabelValues("UpdateWorkspaceBuildAITaskByID").Observe(time.Since(start).Seconds()) - return r0 -} - func (m queryMetricsStore) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error { start := time.Now() err := m.s.UpdateWorkspaceBuildCostByID(ctx, arg) @@ -3169,6 +3162,13 @@ func (m queryMetricsStore) UpdateWorkspaceBuildDeadlineByID(ctx context.Context, return r0 } +func (m queryMetricsStore) UpdateWorkspaceBuildFlagsByID(ctx context.Context, arg database.UpdateWorkspaceBuildFlagsByIDParams) error { + start := time.Now() + r0 := m.s.UpdateWorkspaceBuildFlagsByID(ctx, arg) + m.queryLatencies.WithLabelValues("UpdateWorkspaceBuildFlagsByID").Observe(time.Since(start).Seconds()) + return r0 +} + func (m queryMetricsStore) UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg database.UpdateWorkspaceBuildProvisionerStateByIDParams) error { start := time.Now() r0 := m.s.UpdateWorkspaceBuildProvisionerStateByID(ctx, arg) diff --git a/coderd/database/dbmock/dbmock.go b/coderd/database/dbmock/dbmock.go index 6ecb3e49faf04..96e277cd7af58 100644 --- a/coderd/database/dbmock/dbmock.go +++ b/coderd/database/dbmock/dbmock.go @@ -6229,20 +6229,6 @@ func (mr *MockStoreMockRecorder) UpdateTemplateScheduleByID(ctx, arg any) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateScheduleByID", reflect.TypeOf((*MockStore)(nil).UpdateTemplateScheduleByID), ctx, arg) } -// UpdateTemplateVersionAITaskByJobID mocks base method. -func (m *MockStore) UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg database.UpdateTemplateVersionAITaskByJobIDParams) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateTemplateVersionAITaskByJobID", ctx, arg) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateTemplateVersionAITaskByJobID indicates an expected call of UpdateTemplateVersionAITaskByJobID. -func (mr *MockStoreMockRecorder) UpdateTemplateVersionAITaskByJobID(ctx, arg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateVersionAITaskByJobID", reflect.TypeOf((*MockStore)(nil).UpdateTemplateVersionAITaskByJobID), ctx, arg) -} - // UpdateTemplateVersionByID mocks base method. func (m *MockStore) UpdateTemplateVersionByID(ctx context.Context, arg database.UpdateTemplateVersionByIDParams) error { m.ctrl.T.Helper() @@ -6285,6 +6271,20 @@ func (mr *MockStoreMockRecorder) UpdateTemplateVersionExternalAuthProvidersByJob return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateVersionExternalAuthProvidersByJobID", reflect.TypeOf((*MockStore)(nil).UpdateTemplateVersionExternalAuthProvidersByJobID), ctx, arg) } +// UpdateTemplateVersionFlagsByJobID mocks base method. +func (m *MockStore) UpdateTemplateVersionFlagsByJobID(ctx context.Context, arg database.UpdateTemplateVersionFlagsByJobIDParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateTemplateVersionFlagsByJobID", ctx, arg) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateTemplateVersionFlagsByJobID indicates an expected call of UpdateTemplateVersionFlagsByJobID. +func (mr *MockStoreMockRecorder) UpdateTemplateVersionFlagsByJobID(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTemplateVersionFlagsByJobID", reflect.TypeOf((*MockStore)(nil).UpdateTemplateVersionFlagsByJobID), ctx, arg) +} + // UpdateTemplateWorkspacesLastUsedAt mocks base method. func (m *MockStore) UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg database.UpdateTemplateWorkspacesLastUsedAtParams) error { m.ctrl.T.Helper() @@ -6704,20 +6704,6 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceAutostart(ctx, arg any) *gomock. return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceAutostart", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceAutostart), ctx, arg) } -// UpdateWorkspaceBuildAITaskByID mocks base method. -func (m *MockStore) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg database.UpdateWorkspaceBuildAITaskByIDParams) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateWorkspaceBuildAITaskByID", ctx, arg) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateWorkspaceBuildAITaskByID indicates an expected call of UpdateWorkspaceBuildAITaskByID. -func (mr *MockStoreMockRecorder) UpdateWorkspaceBuildAITaskByID(ctx, arg any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceBuildAITaskByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceBuildAITaskByID), ctx, arg) -} - // UpdateWorkspaceBuildCostByID mocks base method. func (m *MockStore) UpdateWorkspaceBuildCostByID(ctx context.Context, arg database.UpdateWorkspaceBuildCostByIDParams) error { m.ctrl.T.Helper() @@ -6746,6 +6732,20 @@ func (mr *MockStoreMockRecorder) UpdateWorkspaceBuildDeadlineByID(ctx, arg any) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceBuildDeadlineByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceBuildDeadlineByID), ctx, arg) } +// UpdateWorkspaceBuildFlagsByID mocks base method. +func (m *MockStore) UpdateWorkspaceBuildFlagsByID(ctx context.Context, arg database.UpdateWorkspaceBuildFlagsByIDParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateWorkspaceBuildFlagsByID", ctx, arg) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateWorkspaceBuildFlagsByID indicates an expected call of UpdateWorkspaceBuildFlagsByID. +func (mr *MockStoreMockRecorder) UpdateWorkspaceBuildFlagsByID(ctx, arg any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceBuildFlagsByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceBuildFlagsByID), ctx, arg) +} + // UpdateWorkspaceBuildProvisionerStateByID mocks base method. func (m *MockStore) UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg database.UpdateWorkspaceBuildProvisionerStateByIDParams) error { m.ctrl.T.Helper() diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 34162ffb06d57..aca22b6dbbb4d 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1691,7 +1691,8 @@ CREATE TABLE template_versions ( message character varying(1048576) DEFAULT ''::character varying NOT NULL, archived boolean DEFAULT false NOT NULL, source_example_id text, - has_ai_task boolean + has_ai_task boolean, + has_external_agent boolean ); COMMENT ON COLUMN template_versions.external_auth_providers IS 'IDs of External auth providers for a specific template version'; @@ -1722,6 +1723,7 @@ CREATE VIEW template_version_with_user AS template_versions.archived, template_versions.source_example_id, template_versions.has_ai_task, + template_versions.has_external_agent, COALESCE(visible_users.avatar_url, ''::text) AS created_by_avatar_url, COALESCE(visible_users.username, ''::text) AS created_by_username, COALESCE(visible_users.name, ''::text) AS created_by_name @@ -2264,6 +2266,7 @@ CREATE TABLE workspace_builds ( template_version_preset_id uuid, has_ai_task boolean, ai_task_sidebar_app_id uuid, + has_external_agent boolean, CONSTRAINT workspace_builds_ai_task_sidebar_app_id_required CHECK (((((has_ai_task IS NULL) OR (has_ai_task = false)) AND (ai_task_sidebar_app_id IS NULL)) OR ((has_ai_task = true) AND (ai_task_sidebar_app_id IS NOT NULL)))), CONSTRAINT workspace_builds_deadline_below_max_deadline CHECK ((((deadline <> '0001-01-01 00:00:00+00'::timestamp with time zone) AND (deadline <= max_deadline)) OR (max_deadline = '0001-01-01 00:00:00+00'::timestamp with time zone))) ); @@ -2286,6 +2289,7 @@ CREATE VIEW workspace_build_with_user AS workspace_builds.template_version_preset_id, workspace_builds.has_ai_task, workspace_builds.ai_task_sidebar_app_id, + workspace_builds.has_external_agent, COALESCE(visible_users.avatar_url, ''::text) AS initiator_by_avatar_url, COALESCE(visible_users.username, ''::text) AS initiator_by_username, COALESCE(visible_users.name, ''::text) AS initiator_by_name diff --git a/coderd/database/migrations/000358_external_agents.down.sql b/coderd/database/migrations/000358_external_agents.down.sql new file mode 100644 index 0000000000000..a17d0cc7982a6 --- /dev/null +++ b/coderd/database/migrations/000358_external_agents.down.sql @@ -0,0 +1,77 @@ +DROP VIEW template_version_with_user; +DROP VIEW workspace_build_with_user; + +ALTER TABLE template_versions DROP COLUMN has_external_agent; +ALTER TABLE workspace_builds DROP COLUMN has_external_agent; + +-- Recreate `template_version_with_user` as defined in dump.sql +CREATE VIEW template_version_with_user AS +SELECT + template_versions.id, + template_versions.template_id, + template_versions.organization_id, + template_versions.created_at, + template_versions.updated_at, + template_versions.name, + template_versions.readme, + template_versions.job_id, + template_versions.created_by, + template_versions.external_auth_providers, + template_versions.message, + template_versions.archived, + template_versions.source_example_id, + template_versions.has_ai_task, + COALESCE(visible_users.avatar_url, '' :: text) AS created_by_avatar_url, + COALESCE(visible_users.username, '' :: text) AS created_by_username, + COALESCE(visible_users.name, '' :: text) AS created_by_name +FROM + ( + template_versions + LEFT JOIN visible_users ON ( + (template_versions.created_by = visible_users.id) + ) + ); + +COMMENT ON VIEW template_version_with_user IS 'Joins in the username + avatar url of the created by user.'; + +-- Recreate `workspace_build_with_user` as defined in dump.sql +CREATE VIEW workspace_build_with_user AS +SELECT + workspace_builds.id, + workspace_builds.created_at, + workspace_builds.updated_at, + workspace_builds.workspace_id, + workspace_builds.template_version_id, + workspace_builds.build_number, + workspace_builds.transition, + workspace_builds.initiator_id, + workspace_builds.provisioner_state, + workspace_builds.job_id, + workspace_builds.deadline, + workspace_builds.reason, + workspace_builds.daily_cost, + workspace_builds.max_deadline, + workspace_builds.template_version_preset_id, + workspace_builds.has_ai_task, + workspace_builds.ai_task_sidebar_app_id, + COALESCE( + visible_users.avatar_url, + '' :: text + ) AS initiator_by_avatar_url, + COALESCE( + visible_users.username, + '' :: text + ) AS initiator_by_username, + COALESCE(visible_users.name, '' :: text) AS initiator_by_name +FROM + ( + workspace_builds + LEFT JOIN visible_users ON ( + ( + workspace_builds.initiator_id = visible_users.id + ) + ) + ); + +COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.'; + diff --git a/coderd/database/migrations/000358_external_agents.up.sql b/coderd/database/migrations/000358_external_agents.up.sql new file mode 100644 index 0000000000000..00b7d865dfd30 --- /dev/null +++ b/coderd/database/migrations/000358_external_agents.up.sql @@ -0,0 +1,89 @@ +-- Determines if a coder_external_agent resource is defined in a template version. +ALTER TABLE + template_versions +ADD + COLUMN has_external_agent BOOLEAN; + +DROP VIEW template_version_with_user; + +-- We're adding the external_agents column. +CREATE VIEW template_version_with_user AS +SELECT + template_versions.id, + template_versions.template_id, + template_versions.organization_id, + template_versions.created_at, + template_versions.updated_at, + template_versions.name, + template_versions.readme, + template_versions.job_id, + template_versions.created_by, + template_versions.external_auth_providers, + template_versions.message, + template_versions.archived, + template_versions.source_example_id, + template_versions.has_ai_task, + template_versions.has_external_agent, + COALESCE(visible_users.avatar_url, '' :: text) AS created_by_avatar_url, + COALESCE(visible_users.username, '' :: text) AS created_by_username, + COALESCE(visible_users.name, '' :: text) AS created_by_name +FROM + ( + template_versions + LEFT JOIN visible_users ON ( + (template_versions.created_by = visible_users.id) + ) + ); + +COMMENT ON VIEW template_version_with_user IS 'Joins in the username + avatar url of the created by user.'; + +-- Determines if a coder_external_agent resource was included in a +-- workspace build. +ALTER TABLE + workspace_builds +ADD + COLUMN has_external_agent BOOLEAN; + +DROP VIEW workspace_build_with_user; + +-- We're adding the has_external_agent column. +CREATE VIEW workspace_build_with_user AS +SELECT + workspace_builds.id, + workspace_builds.created_at, + workspace_builds.updated_at, + workspace_builds.workspace_id, + workspace_builds.template_version_id, + workspace_builds.build_number, + workspace_builds.transition, + workspace_builds.initiator_id, + workspace_builds.provisioner_state, + workspace_builds.job_id, + workspace_builds.deadline, + workspace_builds.reason, + workspace_builds.daily_cost, + workspace_builds.max_deadline, + workspace_builds.template_version_preset_id, + workspace_builds.has_ai_task, + workspace_builds.ai_task_sidebar_app_id, + workspace_builds.has_external_agent, + COALESCE( + visible_users.avatar_url, + '' :: text + ) AS initiator_by_avatar_url, + COALESCE( + visible_users.username, + '' :: text + ) AS initiator_by_username, + COALESCE(visible_users.name, '' :: text) AS initiator_by_name +FROM + ( + workspace_builds + LEFT JOIN visible_users ON ( + ( + workspace_builds.initiator_id = visible_users.id + ) + ) + ); + +COMMENT ON VIEW workspace_build_with_user IS 'Joins in the username + avatar url of the initiated by user.'; diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index dceddd2e8c3da..69bea8d81adab 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -84,6 +84,7 @@ func (q *sqlQuerier) GetAuthorizedTemplates(ctx context.Context, arg GetTemplate arg.HasAITask, arg.AuthorID, arg.AuthorUsername, + arg.HasExternalAgent, ) if err != nil { return nil, err @@ -271,6 +272,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa arg.LastUsedAfter, arg.UsingActive, arg.HasAITask, + arg.HasExternalAgent, arg.RequesterID, arg.Offset, arg.Limit, @@ -321,6 +323,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa &i.LatestBuildTransition, &i.LatestBuildStatus, &i.LatestBuildHasAITask, + &i.LatestBuildHasExternalAgent, &i.Count, ); err != nil { return nil, err diff --git a/coderd/database/models.go b/coderd/database/models.go index cb0215ec1fed5..effd436f4d18d 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -3634,6 +3634,7 @@ type TemplateVersion struct { Archived bool `db:"archived" json:"archived"` SourceExampleID sql.NullString `db:"source_example_id" json:"source_example_id"` HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` + HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` CreatedByAvatarURL string `db:"created_by_avatar_url" json:"created_by_avatar_url"` CreatedByUsername string `db:"created_by_username" json:"created_by_username"` CreatedByName string `db:"created_by_name" json:"created_by_name"` @@ -3720,10 +3721,11 @@ type TemplateVersionTable struct { // IDs of External auth providers for a specific template version ExternalAuthProviders json.RawMessage `db:"external_auth_providers" json:"external_auth_providers"` // Message describing the changes in this version of the template, similar to a Git commit message. Like a commit message, this should be a short, high-level description of the changes in this version of the template. This message is immutable and should not be updated after the fact. - Message string `db:"message" json:"message"` - Archived bool `db:"archived" json:"archived"` - SourceExampleID sql.NullString `db:"source_example_id" json:"source_example_id"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` + Message string `db:"message" json:"message"` + Archived bool `db:"archived" json:"archived"` + SourceExampleID sql.NullString `db:"source_example_id" json:"source_example_id"` + HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` + HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` } type TemplateVersionTerraformValue struct { @@ -4173,6 +4175,7 @@ type WorkspaceBuild struct { TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"` HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` AITaskSidebarAppID uuid.NullUUID `db:"ai_task_sidebar_app_id" json:"ai_task_sidebar_app_id"` + HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` InitiatorByAvatarUrl string `db:"initiator_by_avatar_url" json:"initiator_by_avatar_url"` InitiatorByUsername string `db:"initiator_by_username" json:"initiator_by_username"` InitiatorByName string `db:"initiator_by_name" json:"initiator_by_name"` @@ -4204,6 +4207,7 @@ type WorkspaceBuildTable struct { TemplateVersionPresetID uuid.NullUUID `db:"template_version_preset_id" json:"template_version_preset_id"` HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` AITaskSidebarAppID uuid.NullUUID `db:"ai_task_sidebar_app_id" json:"ai_task_sidebar_app_id"` + HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` } type WorkspaceLatestBuild struct { diff --git a/coderd/database/querier.go b/coderd/database/querier.go index 8b60920086ca3..8ac974ff20ee8 100644 --- a/coderd/database/querier.go +++ b/coderd/database/querier.go @@ -617,10 +617,10 @@ type sqlcQuerier interface { UpdateTemplateDeletedByID(ctx context.Context, arg UpdateTemplateDeletedByIDParams) error UpdateTemplateMetaByID(ctx context.Context, arg UpdateTemplateMetaByIDParams) error UpdateTemplateScheduleByID(ctx context.Context, arg UpdateTemplateScheduleByIDParams) error - UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg UpdateTemplateVersionAITaskByJobIDParams) error UpdateTemplateVersionByID(ctx context.Context, arg UpdateTemplateVersionByIDParams) error UpdateTemplateVersionDescriptionByJobID(ctx context.Context, arg UpdateTemplateVersionDescriptionByJobIDParams) error UpdateTemplateVersionExternalAuthProvidersByJobID(ctx context.Context, arg UpdateTemplateVersionExternalAuthProvidersByJobIDParams) error + UpdateTemplateVersionFlagsByJobID(ctx context.Context, arg UpdateTemplateVersionFlagsByJobIDParams) error UpdateTemplateWorkspacesLastUsedAt(ctx context.Context, arg UpdateTemplateWorkspacesLastUsedAtParams) error UpdateUsageEventsPostPublish(ctx context.Context, arg UpdateUsageEventsPostPublishParams) error UpdateUserDeletedByID(ctx context.Context, id uuid.UUID) error @@ -650,9 +650,9 @@ type sqlcQuerier interface { UpdateWorkspaceAppHealthByID(ctx context.Context, arg UpdateWorkspaceAppHealthByIDParams) error UpdateWorkspaceAutomaticUpdates(ctx context.Context, arg UpdateWorkspaceAutomaticUpdatesParams) error UpdateWorkspaceAutostart(ctx context.Context, arg UpdateWorkspaceAutostartParams) error - UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg UpdateWorkspaceBuildAITaskByIDParams) error UpdateWorkspaceBuildCostByID(ctx context.Context, arg UpdateWorkspaceBuildCostByIDParams) error UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg UpdateWorkspaceBuildDeadlineByIDParams) error + UpdateWorkspaceBuildFlagsByID(ctx context.Context, arg UpdateWorkspaceBuildFlagsByIDParams) error UpdateWorkspaceBuildProvisionerStateByID(ctx context.Context, arg UpdateWorkspaceBuildProvisionerStateByIDParams) error UpdateWorkspaceDeletedByID(ctx context.Context, arg UpdateWorkspaceDeletedByIDParams) error UpdateWorkspaceDormantDeletingAt(ctx context.Context, arg UpdateWorkspaceDormantDeletingAtParams) (WorkspaceTable, error) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 9615cc2c1a42a..977917178d7bb 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -8584,11 +8584,11 @@ func (q *sqlQuerier) InsertProvisionerJobLogs(ctx context.Context, arg InsertPro } const updateProvisionerJobLogsLength = `-- name: UpdateProvisionerJobLogsLength :exec -UPDATE +UPDATE provisioner_jobs -SET +SET logs_length = logs_length + $2 -WHERE +WHERE id = $1 ` @@ -8603,11 +8603,11 @@ func (q *sqlQuerier) UpdateProvisionerJobLogsLength(ctx context.Context, arg Upd } const updateProvisionerJobLogsOverflowed = `-- name: UpdateProvisionerJobLogsOverflowed :exec -UPDATE +UPDATE provisioner_jobs -SET +SET logs_overflowed = $2 -WHERE +WHERE id = $1 ` @@ -9625,7 +9625,7 @@ FROM provisioner_keys WHERE organization_id = $1 -AND +AND lower(name) = lower($2) ` @@ -9741,10 +9741,10 @@ WHERE AND -- exclude reserved built-in key id != '00000000-0000-0000-0000-000000000001'::uuid -AND +AND -- exclude reserved user-auth key id != '00000000-0000-0000-0000-000000000002'::uuid -AND +AND -- exclude reserved psk key id != '00000000-0000-0000-0000-000000000003'::uuid ` @@ -11528,7 +11528,7 @@ func (q *sqlQuerier) GetTailnetTunnelPeerIDs(ctx context.Context, srcID uuid.UUI } const updateTailnetPeerStatusByCoordinator = `-- name: UpdateTailnetPeerStatusByCoordinator :exec -UPDATE +UPDATE tailnet_peers SET status = $2 @@ -12141,21 +12141,28 @@ WHERE ELSE true END + -- Filter by has_external_agent in latest version + AND CASE + WHEN $10 :: boolean IS NOT NULL THEN + tv.has_external_agent = $10 :: boolean + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedTemplates -- @authorize_filter ORDER BY (t.name, t.id) ASC ` type GetTemplatesWithFilterParams struct { - Deleted bool `db:"deleted" json:"deleted"` - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - ExactName string `db:"exact_name" json:"exact_name"` - FuzzyName string `db:"fuzzy_name" json:"fuzzy_name"` - IDs []uuid.UUID `db:"ids" json:"ids"` - Deprecated sql.NullBool `db:"deprecated" json:"deprecated"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` - AuthorID uuid.UUID `db:"author_id" json:"author_id"` - AuthorUsername string `db:"author_username" json:"author_username"` + Deleted bool `db:"deleted" json:"deleted"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + ExactName string `db:"exact_name" json:"exact_name"` + FuzzyName string `db:"fuzzy_name" json:"fuzzy_name"` + IDs []uuid.UUID `db:"ids" json:"ids"` + Deprecated sql.NullBool `db:"deprecated" json:"deprecated"` + HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` + AuthorID uuid.UUID `db:"author_id" json:"author_id"` + AuthorUsername string `db:"author_username" json:"author_username"` + HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` } func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplatesWithFilterParams) ([]Template, error) { @@ -12169,6 +12176,7 @@ func (q *sqlQuerier) GetTemplatesWithFilter(ctx context.Context, arg GetTemplate arg.HasAITask, arg.AuthorID, arg.AuthorUsername, + arg.HasExternalAgent, ) if err != nil { return nil, err @@ -12653,7 +12661,7 @@ FROM -- Scope an archive to a single template and ignore already archived template versions ( SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent FROM template_versions WHERE @@ -12754,7 +12762,7 @@ func (q *sqlQuerier) ArchiveUnusedTemplateVersions(ctx context.Context, arg Arch const getPreviousTemplateVersion = `-- name: GetPreviousTemplateVersion :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -12793,6 +12801,7 @@ func (q *sqlQuerier) GetPreviousTemplateVersion(ctx context.Context, arg GetPrev &i.Archived, &i.SourceExampleID, &i.HasAITask, + &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -12802,7 +12811,7 @@ func (q *sqlQuerier) GetPreviousTemplateVersion(ctx context.Context, arg GetPrev const getTemplateVersionByID = `-- name: GetTemplateVersionByID :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -12827,6 +12836,7 @@ func (q *sqlQuerier) GetTemplateVersionByID(ctx context.Context, id uuid.UUID) ( &i.Archived, &i.SourceExampleID, &i.HasAITask, + &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -12836,7 +12846,7 @@ func (q *sqlQuerier) GetTemplateVersionByID(ctx context.Context, id uuid.UUID) ( const getTemplateVersionByJobID = `-- name: GetTemplateVersionByJobID :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -12861,6 +12871,7 @@ func (q *sqlQuerier) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.U &i.Archived, &i.SourceExampleID, &i.HasAITask, + &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -12870,7 +12881,7 @@ func (q *sqlQuerier) GetTemplateVersionByJobID(ctx context.Context, jobID uuid.U const getTemplateVersionByTemplateIDAndName = `-- name: GetTemplateVersionByTemplateIDAndName :one SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -12901,6 +12912,7 @@ func (q *sqlQuerier) GetTemplateVersionByTemplateIDAndName(ctx context.Context, &i.Archived, &i.SourceExampleID, &i.HasAITask, + &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -12925,7 +12937,7 @@ func (q *sqlQuerier) GetTemplateVersionHasAITask(ctx context.Context, id uuid.UU const getTemplateVersionsByIDs = `-- name: GetTemplateVersionsByIDs :many SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -12956,6 +12968,7 @@ func (q *sqlQuerier) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UU &i.Archived, &i.SourceExampleID, &i.HasAITask, + &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -12975,7 +12988,7 @@ func (q *sqlQuerier) GetTemplateVersionsByIDs(ctx context.Context, ids []uuid.UU const getTemplateVersionsByTemplateID = `-- name: GetTemplateVersionsByTemplateID :many SELECT - id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, created_by_avatar_url, created_by_username, created_by_name + id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE @@ -13053,6 +13066,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge &i.Archived, &i.SourceExampleID, &i.HasAITask, + &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -13071,7 +13085,7 @@ func (q *sqlQuerier) GetTemplateVersionsByTemplateID(ctx context.Context, arg Ge } const getTemplateVersionsCreatedAfter = `-- name: GetTemplateVersionsCreatedAfter :many -SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE created_at > $1 +SELECT id, template_id, organization_id, created_at, updated_at, name, readme, job_id, created_by, external_auth_providers, message, archived, source_example_id, has_ai_task, has_external_agent, created_by_avatar_url, created_by_username, created_by_name FROM template_version_with_user AS template_versions WHERE created_at > $1 ` func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, createdAt time.Time) ([]TemplateVersion, error) { @@ -13098,6 +13112,7 @@ func (q *sqlQuerier) GetTemplateVersionsCreatedAfter(ctx context.Context, create &i.Archived, &i.SourceExampleID, &i.HasAITask, + &i.HasExternalAgent, &i.CreatedByAvatarURL, &i.CreatedByUsername, &i.CreatedByName, @@ -13186,27 +13201,6 @@ func (q *sqlQuerier) UnarchiveTemplateVersion(ctx context.Context, arg Unarchive return err } -const updateTemplateVersionAITaskByJobID = `-- name: UpdateTemplateVersionAITaskByJobID :exec -UPDATE - template_versions -SET - has_ai_task = $2, - updated_at = $3 -WHERE - job_id = $1 -` - -type UpdateTemplateVersionAITaskByJobIDParams struct { - JobID uuid.UUID `db:"job_id" json:"job_id"` - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` -} - -func (q *sqlQuerier) UpdateTemplateVersionAITaskByJobID(ctx context.Context, arg UpdateTemplateVersionAITaskByJobIDParams) error { - _, err := q.db.ExecContext(ctx, updateTemplateVersionAITaskByJobID, arg.JobID, arg.HasAITask, arg.UpdatedAt) - return err -} - const updateTemplateVersionByID = `-- name: UpdateTemplateVersionByID :exec UPDATE template_versions @@ -13280,6 +13274,34 @@ func (q *sqlQuerier) UpdateTemplateVersionExternalAuthProvidersByJobID(ctx conte return err } +const updateTemplateVersionFlagsByJobID = `-- name: UpdateTemplateVersionFlagsByJobID :exec +UPDATE + template_versions +SET + has_ai_task = $2, + has_external_agent = $3, + updated_at = $4 +WHERE + job_id = $1 +` + +type UpdateTemplateVersionFlagsByJobIDParams struct { + JobID uuid.UUID `db:"job_id" json:"job_id"` + HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` + HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` +} + +func (q *sqlQuerier) UpdateTemplateVersionFlagsByJobID(ctx context.Context, arg UpdateTemplateVersionFlagsByJobIDParams) error { + _, err := q.db.ExecContext(ctx, updateTemplateVersionFlagsByJobID, + arg.JobID, + arg.HasAITask, + arg.HasExternalAgent, + arg.UpdatedAt, + ) + return err +} + const getTemplateVersionTerraformValues = `-- name: GetTemplateVersionTerraformValues :one SELECT template_version_terraform_values.template_version_id, template_version_terraform_values.updated_at, template_version_terraform_values.cached_plan, template_version_terraform_values.cached_module_files, template_version_terraform_values.provisionerd_version @@ -13497,14 +13519,14 @@ DO $$ DECLARE table_record record; BEGIN - FOR table_record IN - SELECT table_schema, table_name - FROM information_schema.tables + FOR table_record IN + SELECT table_schema, table_name + FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema') AND table_type = 'BASE TABLE' LOOP - EXECUTE format('ALTER TABLE %I.%I DISABLE TRIGGER ALL', - table_record.table_schema, + EXECUTE format('ALTER TABLE %I.%I DISABLE TRIGGER ALL', + table_record.table_schema, table_record.table_name); END LOOP; END; @@ -15910,7 +15932,7 @@ const getWorkspaceAgentAndLatestBuildByAuthToken = `-- name: GetWorkspaceAgentAn SELECT workspaces.id, workspaces.created_at, workspaces.updated_at, workspaces.owner_id, workspaces.organization_id, workspaces.template_id, workspaces.deleted, workspaces.name, workspaces.autostart_schedule, workspaces.ttl, workspaces.last_used_at, workspaces.dormant_at, workspaces.deleting_at, workspaces.automatic_updates, workspaces.favorite, workspaces.next_start_at, workspaces.group_acl, workspaces.user_acl, 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, workspace_agents.api_key_scope, workspace_agents.deleted, - workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.provisioner_state, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.ai_task_sidebar_app_id, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name + workspace_build_with_user.id, workspace_build_with_user.created_at, workspace_build_with_user.updated_at, workspace_build_with_user.workspace_id, workspace_build_with_user.template_version_id, workspace_build_with_user.build_number, workspace_build_with_user.transition, workspace_build_with_user.initiator_id, workspace_build_with_user.provisioner_state, workspace_build_with_user.job_id, workspace_build_with_user.deadline, workspace_build_with_user.reason, workspace_build_with_user.daily_cost, workspace_build_with_user.max_deadline, workspace_build_with_user.template_version_preset_id, workspace_build_with_user.has_ai_task, workspace_build_with_user.ai_task_sidebar_app_id, workspace_build_with_user.has_external_agent, workspace_build_with_user.initiator_by_avatar_url, workspace_build_with_user.initiator_by_username, workspace_build_with_user.initiator_by_name FROM workspace_agents JOIN @@ -16023,6 +16045,7 @@ func (q *sqlQuerier) GetWorkspaceAgentAndLatestBuildByAuthToken(ctx context.Cont &i.WorkspaceBuild.TemplateVersionPresetID, &i.WorkspaceBuild.HasAITask, &i.WorkspaceBuild.AITaskSidebarAppID, + &i.WorkspaceBuild.HasExternalAgent, &i.WorkspaceBuild.InitiatorByAvatarUrl, &i.WorkspaceBuild.InitiatorByUsername, &i.WorkspaceBuild.InitiatorByName, @@ -17454,7 +17477,7 @@ WITH agent_stats AS ( coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms)), -1)::FLOAT AS workspace_connection_latency_95 FROM workspace_agent_stats -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. - WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 + WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 GROUP BY user_id, agent_id, workspace_id, template_id ), latest_agent_stats AS ( SELECT @@ -18677,7 +18700,7 @@ func (q *sqlQuerier) InsertWorkspaceBuildParameters(ctx context.Context, arg Ins } const getActiveWorkspaceBuildsByTemplateID = `-- name: GetActiveWorkspaceBuildsByTemplateID :many -SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name +SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.has_external_agent, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name FROM ( SELECT workspace_id, MAX(build_number) as max_build_number @@ -18734,6 +18757,7 @@ func (q *sqlQuerier) GetActiveWorkspaceBuildsByTemplateID(ctx context.Context, t &i.TemplateVersionPresetID, &i.HasAITask, &i.AITaskSidebarAppID, + &i.HasExternalAgent, &i.InitiatorByAvatarUrl, &i.InitiatorByUsername, &i.InitiatorByName, @@ -18833,7 +18857,7 @@ func (q *sqlQuerier) GetFailedWorkspaceBuildsByTemplateID(ctx context.Context, a const getLatestWorkspaceBuildByWorkspaceID = `-- name: GetLatestWorkspaceBuildByWorkspaceID :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, has_external_agent, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -18865,6 +18889,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w &i.TemplateVersionPresetID, &i.HasAITask, &i.AITaskSidebarAppID, + &i.HasExternalAgent, &i.InitiatorByAvatarUrl, &i.InitiatorByUsername, &i.InitiatorByName, @@ -18872,8 +18897,68 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w return i, err } +const getLatestWorkspaceBuilds = `-- name: GetLatestWorkspaceBuilds :many +SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.has_external_agent, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name +FROM ( + SELECT + workspace_id, MAX(build_number) as max_build_number + FROM + workspace_build_with_user AS workspace_builds + GROUP BY + workspace_id +) m +JOIN + workspace_build_with_user AS wb +ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number +` + +func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error) { + rows, err := q.db.QueryContext(ctx, getLatestWorkspaceBuilds) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceBuild + for rows.Next() { + var i WorkspaceBuild + if err := rows.Scan( + &i.ID, + &i.CreatedAt, + &i.UpdatedAt, + &i.WorkspaceID, + &i.TemplateVersionID, + &i.BuildNumber, + &i.Transition, + &i.InitiatorID, + &i.ProvisionerState, + &i.JobID, + &i.Deadline, + &i.Reason, + &i.DailyCost, + &i.MaxDeadline, + &i.TemplateVersionPresetID, + &i.HasAITask, + &i.AITaskSidebarAppID, + &i.HasExternalAgent, + &i.InitiatorByAvatarUrl, + &i.InitiatorByUsername, + &i.InitiatorByName, + ); 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 getLatestWorkspaceBuildsByWorkspaceIDs = `-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many -SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name +SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.has_external_agent, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name FROM ( SELECT workspace_id, MAX(build_number) as max_build_number @@ -18916,6 +19001,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, &i.TemplateVersionPresetID, &i.HasAITask, &i.AITaskSidebarAppID, + &i.HasExternalAgent, &i.InitiatorByAvatarUrl, &i.InitiatorByUsername, &i.InitiatorByName, @@ -18935,7 +19021,7 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, const getWorkspaceBuildByID = `-- name: GetWorkspaceBuildByID :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, has_external_agent, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -18965,6 +19051,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W &i.TemplateVersionPresetID, &i.HasAITask, &i.AITaskSidebarAppID, + &i.HasExternalAgent, &i.InitiatorByAvatarUrl, &i.InitiatorByUsername, &i.InitiatorByName, @@ -18974,7 +19061,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByID(ctx context.Context, id uuid.UUID) (W const getWorkspaceBuildByJobID = `-- name: GetWorkspaceBuildByJobID :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, has_external_agent, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -19004,6 +19091,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU &i.TemplateVersionPresetID, &i.HasAITask, &i.AITaskSidebarAppID, + &i.HasExternalAgent, &i.InitiatorByAvatarUrl, &i.InitiatorByUsername, &i.InitiatorByName, @@ -19013,7 +19101,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByJobID(ctx context.Context, jobID uuid.UU const getWorkspaceBuildByWorkspaceIDAndBuildNumber = `-- name: GetWorkspaceBuildByWorkspaceIDAndBuildNumber :one SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, has_external_agent, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -19047,6 +19135,7 @@ func (q *sqlQuerier) GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx context.Co &i.TemplateVersionPresetID, &i.HasAITask, &i.AITaskSidebarAppID, + &i.HasExternalAgent, &i.InitiatorByAvatarUrl, &i.InitiatorByUsername, &i.InitiatorByName, @@ -19123,7 +19212,7 @@ func (q *sqlQuerier) GetWorkspaceBuildStatsByTemplates(ctx context.Context, sinc const getWorkspaceBuildsByWorkspaceID = `-- name: GetWorkspaceBuildsByWorkspaceID :many SELECT - id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name + id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, has_external_agent, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user AS workspace_builds WHERE @@ -19196,6 +19285,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge &i.TemplateVersionPresetID, &i.HasAITask, &i.AITaskSidebarAppID, + &i.HasExternalAgent, &i.InitiatorByAvatarUrl, &i.InitiatorByUsername, &i.InitiatorByName, @@ -19214,7 +19304,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsByWorkspaceID(ctx context.Context, arg Ge } const getWorkspaceBuildsCreatedAfter = `-- name: GetWorkspaceBuildsCreatedAfter :many -SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user WHERE created_at > $1 +SELECT id, created_at, updated_at, workspace_id, template_version_id, build_number, transition, initiator_id, provisioner_state, job_id, deadline, reason, daily_cost, max_deadline, template_version_preset_id, has_ai_task, ai_task_sidebar_app_id, has_external_agent, initiator_by_avatar_url, initiator_by_username, initiator_by_name FROM workspace_build_with_user WHERE created_at > $1 ` func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, createdAt time.Time) ([]WorkspaceBuild, error) { @@ -19244,6 +19334,7 @@ func (q *sqlQuerier) GetWorkspaceBuildsCreatedAfter(ctx context.Context, created &i.TemplateVersionPresetID, &i.HasAITask, &i.AITaskSidebarAppID, + &i.HasExternalAgent, &i.InitiatorByAvatarUrl, &i.InitiatorByUsername, &i.InitiatorByName, @@ -19320,33 +19411,6 @@ func (q *sqlQuerier) InsertWorkspaceBuild(ctx context.Context, arg InsertWorkspa return err } -const updateWorkspaceBuildAITaskByID = `-- name: UpdateWorkspaceBuildAITaskByID :exec -UPDATE - workspace_builds -SET - has_ai_task = $1, - ai_task_sidebar_app_id = $2, - updated_at = $3::timestamptz -WHERE id = $4::uuid -` - -type UpdateWorkspaceBuildAITaskByIDParams struct { - HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` - SidebarAppID uuid.NullUUID `db:"sidebar_app_id" json:"sidebar_app_id"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - ID uuid.UUID `db:"id" json:"id"` -} - -func (q *sqlQuerier) UpdateWorkspaceBuildAITaskByID(ctx context.Context, arg UpdateWorkspaceBuildAITaskByIDParams) error { - _, err := q.db.ExecContext(ctx, updateWorkspaceBuildAITaskByID, - arg.HasAITask, - arg.SidebarAppID, - arg.UpdatedAt, - arg.ID, - ) - return err -} - const updateWorkspaceBuildCostByID = `-- name: UpdateWorkspaceBuildCostByID :exec UPDATE workspace_builds @@ -19401,6 +19465,36 @@ func (q *sqlQuerier) UpdateWorkspaceBuildDeadlineByID(ctx context.Context, arg U return err } +const updateWorkspaceBuildFlagsByID = `-- name: UpdateWorkspaceBuildFlagsByID :exec +UPDATE + workspace_builds +SET + has_ai_task = $1, + ai_task_sidebar_app_id = $2, + has_external_agent = $3, + updated_at = $4::timestamptz +WHERE id = $5::uuid +` + +type UpdateWorkspaceBuildFlagsByIDParams struct { + HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` + SidebarAppID uuid.NullUUID `db:"sidebar_app_id" json:"sidebar_app_id"` + HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + ID uuid.UUID `db:"id" json:"id"` +} + +func (q *sqlQuerier) UpdateWorkspaceBuildFlagsByID(ctx context.Context, arg UpdateWorkspaceBuildFlagsByIDParams) error { + _, err := q.db.ExecContext(ctx, updateWorkspaceBuildFlagsByID, + arg.HasAITask, + arg.SidebarAppID, + arg.HasExternalAgent, + arg.UpdatedAt, + arg.ID, + ) + return err +} + const updateWorkspaceBuildProvisionerStateByID = `-- name: UpdateWorkspaceBuildProvisionerStateByID :exec UPDATE workspace_builds @@ -20368,7 +20462,8 @@ SELECT latest_build.error as latest_build_error, latest_build.transition as latest_build_transition, latest_build.job_status as latest_build_status, - latest_build.has_ai_task as latest_build_has_ai_task + latest_build.has_ai_task as latest_build_has_ai_task, + latest_build.has_external_agent as latest_build_has_external_agent FROM workspaces_expanded as workspaces JOIN @@ -20381,6 +20476,7 @@ LEFT JOIN LATERAL ( workspace_builds.transition, workspace_builds.template_version_id, workspace_builds.has_ai_task, + workspace_builds.has_external_agent, template_versions.name AS template_version_name, provisioner_jobs.id AS provisioner_job_id, provisioner_jobs.started_at, @@ -20621,16 +20717,22 @@ WHERE )) = ($19 :: boolean) ELSE true END + -- Filter by has_external_agent in latest build + AND CASE + WHEN $20 :: boolean IS NOT NULL THEN + latest_build.has_external_agent = $20 :: boolean + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces -- @authorize_filter ), filtered_workspaces_order AS ( SELECT - fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_ai_task + fw.id, fw.created_at, fw.updated_at, fw.owner_id, fw.organization_id, fw.template_id, fw.deleted, fw.name, fw.autostart_schedule, fw.ttl, fw.last_used_at, fw.dormant_at, fw.deleting_at, fw.automatic_updates, fw.favorite, fw.next_start_at, fw.group_acl, fw.user_acl, fw.owner_avatar_url, fw.owner_username, fw.owner_name, fw.organization_name, fw.organization_display_name, fw.organization_icon, fw.organization_description, fw.template_name, fw.template_display_name, fw.template_icon, fw.template_description, fw.template_version_id, fw.template_version_name, fw.latest_build_completed_at, fw.latest_build_canceled_at, fw.latest_build_error, fw.latest_build_transition, fw.latest_build_status, fw.latest_build_has_ai_task, fw.latest_build_has_external_agent FROM filtered_workspaces fw ORDER BY -- To ensure that 'favorite' workspaces show up first in the list only for their owner. - CASE WHEN owner_id = $20 AND favorite THEN 0 ELSE 1 END ASC, + CASE WHEN owner_id = $21 AND favorite THEN 0 ELSE 1 END ASC, (latest_build_completed_at IS NOT NULL AND latest_build_canceled_at IS NULL AND latest_build_error IS NULL AND @@ -20639,14 +20741,14 @@ WHERE LOWER(name) ASC LIMIT CASE - WHEN $22 :: integer > 0 THEN - $22 + WHEN $23 :: integer > 0 THEN + $23 END OFFSET - $21 + $22 ), filtered_workspaces_order_with_summary AS ( SELECT - fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_ai_task + fwo.id, fwo.created_at, fwo.updated_at, fwo.owner_id, fwo.organization_id, fwo.template_id, fwo.deleted, fwo.name, fwo.autostart_schedule, fwo.ttl, fwo.last_used_at, fwo.dormant_at, fwo.deleting_at, fwo.automatic_updates, fwo.favorite, fwo.next_start_at, fwo.group_acl, fwo.user_acl, fwo.owner_avatar_url, fwo.owner_username, fwo.owner_name, fwo.organization_name, fwo.organization_display_name, fwo.organization_icon, fwo.organization_description, fwo.template_name, fwo.template_display_name, fwo.template_icon, fwo.template_description, fwo.template_version_id, fwo.template_version_name, fwo.latest_build_completed_at, fwo.latest_build_canceled_at, fwo.latest_build_error, fwo.latest_build_transition, fwo.latest_build_status, fwo.latest_build_has_ai_task, fwo.latest_build_has_external_agent FROM filtered_workspaces_order fwo -- Return a technical summary row with total count of workspaces. @@ -20690,9 +20792,10 @@ WHERE '', -- latest_build_error 'start'::workspace_transition, -- latest_build_transition 'unknown'::provisioner_job_status, -- latest_build_status - false -- latest_build_has_ai_task + false, -- latest_build_has_ai_task + false -- latest_build_has_external_agent WHERE - $23 :: boolean = true + $24 :: boolean = true ), total_count AS ( SELECT count(*) AS count @@ -20700,7 +20803,7 @@ WHERE filtered_workspaces ) SELECT - fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_ai_task, + fwos.id, fwos.created_at, fwos.updated_at, fwos.owner_id, fwos.organization_id, fwos.template_id, fwos.deleted, fwos.name, fwos.autostart_schedule, fwos.ttl, fwos.last_used_at, fwos.dormant_at, fwos.deleting_at, fwos.automatic_updates, fwos.favorite, fwos.next_start_at, fwos.group_acl, fwos.user_acl, fwos.owner_avatar_url, fwos.owner_username, fwos.owner_name, fwos.organization_name, fwos.organization_display_name, fwos.organization_icon, fwos.organization_description, fwos.template_name, fwos.template_display_name, fwos.template_icon, fwos.template_description, fwos.template_version_id, fwos.template_version_name, fwos.latest_build_completed_at, fwos.latest_build_canceled_at, fwos.latest_build_error, fwos.latest_build_transition, fwos.latest_build_status, fwos.latest_build_has_ai_task, fwos.latest_build_has_external_agent, tc.count FROM filtered_workspaces_order_with_summary fwos @@ -20728,6 +20831,7 @@ type GetWorkspacesParams struct { LastUsedAfter time.Time `db:"last_used_after" json:"last_used_after"` UsingActive sql.NullBool `db:"using_active" json:"using_active"` HasAITask sql.NullBool `db:"has_ai_task" json:"has_ai_task"` + HasExternalAgent sql.NullBool `db:"has_external_agent" json:"has_external_agent"` RequesterID uuid.UUID `db:"requester_id" json:"requester_id"` Offset int32 `db:"offset_" json:"offset_"` Limit int32 `db:"limit_" json:"limit_"` @@ -20735,44 +20839,45 @@ type GetWorkspacesParams struct { } type GetWorkspacesRow struct { - ID uuid.UUID `db:"id" json:"id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` - OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` - TemplateID uuid.UUID `db:"template_id" json:"template_id"` - Deleted bool `db:"deleted" json:"deleted"` - Name string `db:"name" json:"name"` - AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"` - Ttl sql.NullInt64 `db:"ttl" json:"ttl"` - LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"` - DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` - DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"` - AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` - Favorite bool `db:"favorite" json:"favorite"` - NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"` - GroupACL json.RawMessage `db:"group_acl" json:"group_acl"` - UserACL json.RawMessage `db:"user_acl" json:"user_acl"` - OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"` - OwnerUsername string `db:"owner_username" json:"owner_username"` - OwnerName string `db:"owner_name" json:"owner_name"` - OrganizationName string `db:"organization_name" json:"organization_name"` - OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"` - OrganizationIcon string `db:"organization_icon" json:"organization_icon"` - OrganizationDescription string `db:"organization_description" json:"organization_description"` - TemplateName string `db:"template_name" json:"template_name"` - TemplateDisplayName string `db:"template_display_name" json:"template_display_name"` - TemplateIcon string `db:"template_icon" json:"template_icon"` - TemplateDescription string `db:"template_description" json:"template_description"` - TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` - TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"` - LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"` - LatestBuildCanceledAt sql.NullTime `db:"latest_build_canceled_at" json:"latest_build_canceled_at"` - LatestBuildError sql.NullString `db:"latest_build_error" json:"latest_build_error"` - LatestBuildTransition WorkspaceTransition `db:"latest_build_transition" json:"latest_build_transition"` - LatestBuildStatus ProvisionerJobStatus `db:"latest_build_status" json:"latest_build_status"` - LatestBuildHasAITask sql.NullBool `db:"latest_build_has_ai_task" json:"latest_build_has_ai_task"` - Count int64 `db:"count" json:"count"` + ID uuid.UUID `db:"id" json:"id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + OrganizationID uuid.UUID `db:"organization_id" json:"organization_id"` + TemplateID uuid.UUID `db:"template_id" json:"template_id"` + Deleted bool `db:"deleted" json:"deleted"` + Name string `db:"name" json:"name"` + AutostartSchedule sql.NullString `db:"autostart_schedule" json:"autostart_schedule"` + Ttl sql.NullInt64 `db:"ttl" json:"ttl"` + LastUsedAt time.Time `db:"last_used_at" json:"last_used_at"` + DormantAt sql.NullTime `db:"dormant_at" json:"dormant_at"` + DeletingAt sql.NullTime `db:"deleting_at" json:"deleting_at"` + AutomaticUpdates AutomaticUpdates `db:"automatic_updates" json:"automatic_updates"` + Favorite bool `db:"favorite" json:"favorite"` + NextStartAt sql.NullTime `db:"next_start_at" json:"next_start_at"` + GroupACL json.RawMessage `db:"group_acl" json:"group_acl"` + UserACL json.RawMessage `db:"user_acl" json:"user_acl"` + OwnerAvatarUrl string `db:"owner_avatar_url" json:"owner_avatar_url"` + OwnerUsername string `db:"owner_username" json:"owner_username"` + OwnerName string `db:"owner_name" json:"owner_name"` + OrganizationName string `db:"organization_name" json:"organization_name"` + OrganizationDisplayName string `db:"organization_display_name" json:"organization_display_name"` + OrganizationIcon string `db:"organization_icon" json:"organization_icon"` + OrganizationDescription string `db:"organization_description" json:"organization_description"` + TemplateName string `db:"template_name" json:"template_name"` + TemplateDisplayName string `db:"template_display_name" json:"template_display_name"` + TemplateIcon string `db:"template_icon" json:"template_icon"` + TemplateDescription string `db:"template_description" json:"template_description"` + TemplateVersionID uuid.UUID `db:"template_version_id" json:"template_version_id"` + TemplateVersionName sql.NullString `db:"template_version_name" json:"template_version_name"` + LatestBuildCompletedAt sql.NullTime `db:"latest_build_completed_at" json:"latest_build_completed_at"` + LatestBuildCanceledAt sql.NullTime `db:"latest_build_canceled_at" json:"latest_build_canceled_at"` + LatestBuildError sql.NullString `db:"latest_build_error" json:"latest_build_error"` + LatestBuildTransition WorkspaceTransition `db:"latest_build_transition" json:"latest_build_transition"` + LatestBuildStatus ProvisionerJobStatus `db:"latest_build_status" json:"latest_build_status"` + LatestBuildHasAITask sql.NullBool `db:"latest_build_has_ai_task" json:"latest_build_has_ai_task"` + LatestBuildHasExternalAgent sql.NullBool `db:"latest_build_has_external_agent" json:"latest_build_has_external_agent"` + Count int64 `db:"count" json:"count"` } // build_params is used to filter by build parameters if present. @@ -20799,6 +20904,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) arg.LastUsedAfter, arg.UsingActive, arg.HasAITask, + arg.HasExternalAgent, arg.RequesterID, arg.Offset, arg.Limit, @@ -20849,6 +20955,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) &i.LatestBuildTransition, &i.LatestBuildStatus, &i.LatestBuildHasAITask, + &i.LatestBuildHasExternalAgent, &i.Count, ); err != nil { return nil, err diff --git a/coderd/database/queries/templates.sql b/coderd/database/queries/templates.sql index a922a9bef1918..4bb70c6580503 100644 --- a/coderd/database/queries/templates.sql +++ b/coderd/database/queries/templates.sql @@ -72,6 +72,12 @@ WHERE ELSE true END + -- Filter by has_external_agent in latest version + AND CASE + WHEN sqlc.narg('has_external_agent') :: boolean IS NOT NULL THEN + tv.has_external_agent = sqlc.narg('has_external_agent') :: boolean + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedTemplates -- @authorize_filter ORDER BY (t.name, t.id) ASC diff --git a/coderd/database/queries/templateversions.sql b/coderd/database/queries/templateversions.sql index 97fb6bd9ecc08..128b2e5f582da 100644 --- a/coderd/database/queries/templateversions.sql +++ b/coderd/database/queries/templateversions.sql @@ -122,15 +122,6 @@ SET WHERE job_id = $1; --- name: UpdateTemplateVersionAITaskByJobID :exec -UPDATE - template_versions -SET - has_ai_task = $2, - updated_at = $3 -WHERE - job_id = $1; - -- name: GetPreviousTemplateVersion :one SELECT * @@ -241,3 +232,13 @@ SELECT EXISTS ( FROM template_versions WHERE id = $1 AND has_ai_task = TRUE ); + +-- name: UpdateTemplateVersionFlagsByJobID :exec +UPDATE + template_versions +SET + has_ai_task = $2, + has_external_agent = $3, + updated_at = $4 +WHERE + job_id = $1; diff --git a/coderd/database/queries/workspacebuilds.sql b/coderd/database/queries/workspacebuilds.sql index 40bf0f18cf8c5..6c020f5a97f50 100644 --- a/coderd/database/queries/workspacebuilds.sql +++ b/coderd/database/queries/workspacebuilds.sql @@ -145,15 +145,6 @@ SET updated_at = @updated_at::timestamptz WHERE id = @id::uuid; --- name: UpdateWorkspaceBuildAITaskByID :exec -UPDATE - workspace_builds -SET - has_ai_task = @has_ai_task, - ai_task_sidebar_app_id = @sidebar_app_id, - updated_at = @updated_at::timestamptz -WHERE id = @id::uuid; - -- name: GetActiveWorkspaceBuildsByTemplateID :many SELECT wb.* FROM ( @@ -247,3 +238,13 @@ WHERE AND pj.job_status = 'failed' ORDER BY tv.name ASC, wb.build_number DESC; + +-- name: UpdateWorkspaceBuildFlagsByID :exec +UPDATE + workspace_builds +SET + has_ai_task = @has_ai_task, + ai_task_sidebar_app_id = @sidebar_app_id, + has_external_agent = @has_external_agent, + updated_at = @updated_at::timestamptz +WHERE id = @id::uuid; diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 8b9a9e3076555..34461b50401f4 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -117,7 +117,8 @@ SELECT latest_build.error as latest_build_error, latest_build.transition as latest_build_transition, latest_build.job_status as latest_build_status, - latest_build.has_ai_task as latest_build_has_ai_task + latest_build.has_ai_task as latest_build_has_ai_task, + latest_build.has_external_agent as latest_build_has_external_agent FROM workspaces_expanded as workspaces JOIN @@ -130,6 +131,7 @@ LEFT JOIN LATERAL ( workspace_builds.transition, workspace_builds.template_version_id, workspace_builds.has_ai_task, + workspace_builds.has_external_agent, template_versions.name AS template_version_name, provisioner_jobs.id AS provisioner_job_id, provisioner_jobs.started_at, @@ -370,6 +372,12 @@ WHERE )) = (sqlc.narg('has_ai_task') :: boolean) ELSE true END + -- Filter by has_external_agent in latest build + AND CASE + WHEN sqlc.narg('has_external_agent') :: boolean IS NOT NULL THEN + latest_build.has_external_agent = sqlc.narg('has_external_agent') :: boolean + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces -- @authorize_filter ), filtered_workspaces_order AS ( @@ -439,7 +447,8 @@ WHERE '', -- latest_build_error 'start'::workspace_transition, -- latest_build_transition 'unknown'::provisioner_job_status, -- latest_build_status - false -- latest_build_has_ai_task + false, -- latest_build_has_ai_task + false -- latest_build_has_external_agent WHERE @with_summary :: boolean = true ), total_count AS ( diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index 83ca7669370ec..950e55413a662 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1727,13 +1727,14 @@ func (s *server) completeTemplateImportJob(ctx context.Context, job database.Pro if err != nil { return xerrors.Errorf("update template version external auth providers: %w", err) } - err = db.UpdateTemplateVersionAITaskByJobID(ctx, database.UpdateTemplateVersionAITaskByJobIDParams{ + err = db.UpdateTemplateVersionFlagsByJobID(ctx, database.UpdateTemplateVersionFlagsByJobIDParams{ JobID: jobID, HasAITask: sql.NullBool{ Bool: jobType.TemplateImport.HasAiTasks, Valid: true, }, - UpdatedAt: now, + HasExternalAgent: sql.NullBool{}, + UpdatedAt: now, }) if err != nil { return xerrors.Errorf("update template version external auth providers: %w", err) @@ -2028,14 +2029,15 @@ func (s *server) completeWorkspaceBuildJob(ctx context.Context, job database.Pro // Regardless of whether there is an AI task or not, update the field to indicate one way or the other since it // always defaults to nil. ONLY if has_ai_task=true MUST ai_task_sidebar_app_id be set. - if err := db.UpdateWorkspaceBuildAITaskByID(ctx, database.UpdateWorkspaceBuildAITaskByIDParams{ + if err := db.UpdateWorkspaceBuildFlagsByID(ctx, database.UpdateWorkspaceBuildFlagsByIDParams{ ID: workspaceBuild.ID, HasAITask: sql.NullBool{ Bool: hasAITask, Valid: true, }, - SidebarAppID: sidebarAppID, - UpdatedAt: now, + HasExternalAgent: sql.NullBool{}, + SidebarAppID: sidebarAppID, + UpdatedAt: now, }); err != nil { return xerrors.Errorf("update workspace build ai tasks flag: %w", err) } diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index 0232c3d45a0c2..69d85b0d67f72 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -33,9 +33,9 @@ We track the following resources: | PrebuildsSettings
| |
FieldTracked
idfalse
reconciliation_pausedtrue
| | RoleSyncSettings
| |
FieldTracked
fieldtrue
mappingtrue
| | Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
cors_behaviortrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
use_classic_parameter_flowtrue
user_acltrue
| -| TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_ai_taskfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| +| TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_ai_taskfalse
has_external_agentfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| | User
create, write, delete | |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
is_systemtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| -| WorkspaceBuild
start, stop | |
FieldTracked
ai_task_sidebar_app_idfalse
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
has_ai_taskfalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_namefalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| +| WorkspaceBuild
start, stop | |
FieldTracked
ai_task_sidebar_app_idfalse
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
has_ai_taskfalse
has_external_agentfalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_namefalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| | WorkspaceProxy
| |
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| | WorkspaceTable
| |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
group_acltrue
idtrue
last_used_atfalse
nametrue
next_start_attrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
user_acltrue
| diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 1ad76a1e44ca9..0519efd72f31b 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -135,6 +135,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "archived": ActionTrack, "source_example_id": ActionIgnore, // Never changes. "has_ai_task": ActionIgnore, // Never changes. + "has_external_agent": ActionIgnore, // Never changes. }, &database.User{}: { "id": ActionTrack, @@ -197,6 +198,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "template_version_preset_id": ActionIgnore, // Never changes. "has_ai_task": ActionIgnore, // Never changes. "ai_task_sidebar_app_id": ActionIgnore, // Never changes. + "has_external_agent": ActionIgnore, // Never changes. }, &database.AuditableGroup{}: { "id": ActionTrack, From 0ae0761a9da359b59697a1457d0621e9c3302342 Mon Sep 17 00:00:00 2001 From: Kacper Sawicki Date: Mon, 11 Aug 2025 10:35:12 +0000 Subject: [PATCH 2/4] replace dbgen with testutil.Fake --- coderd/database/dbauthz/dbauthz_test.go | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 084e5bbfb82e1..e4639c3ae0adf 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -2955,34 +2955,38 @@ func (s *MethodTestSuite) TestWorkspace() { Deadline: b.Deadline, }).Asserts(w, policy.ActionUpdate) })) - s.Run("UpdateWorkspaceBuildFlagsByID", 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{ + s.Run("UpdateWorkspaceBuildFlagsByID", s.Mocked(func(dbm *dbmock.MockStore, faker *gofakeit.Faker, check *expects) { + u := testutil.Fake(s.T(), faker, database.User{}) + o := testutil.Fake(s.T(), faker, database.Organization{}) + tpl := testutil.Fake(s.T(), faker, database.Template{ OrganizationID: o.ID, CreatedBy: u.ID, }) - tv := dbgen.TemplateVersion(s.T(), db, database.TemplateVersion{ + tv := testutil.Fake(s.T(), faker, database.TemplateVersion{ TemplateID: uuid.NullUUID{UUID: tpl.ID, Valid: true}, OrganizationID: o.ID, CreatedBy: u.ID, }) - w := dbgen.Workspace(s.T(), db, database.WorkspaceTable{ + w := testutil.Fake(s.T(), faker, database.Workspace{ TemplateID: tpl.ID, OrganizationID: o.ID, OwnerID: u.ID, }) - j := dbgen.ProvisionerJob(s.T(), db, nil, database.ProvisionerJob{ + j := testutil.Fake(s.T(), faker, database.ProvisionerJob{ Type: database.ProvisionerJobTypeWorkspaceBuild, }) - b := dbgen.WorkspaceBuild(s.T(), db, database.WorkspaceBuild{ + b := testutil.Fake(s.T(), faker, 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}) - app := dbgen.WorkspaceApp(s.T(), db, database.WorkspaceApp{AgentID: agt.ID}) + res := testutil.Fake(s.T(), faker, database.WorkspaceResource{JobID: b.JobID}) + agt := testutil.Fake(s.T(), faker, database.WorkspaceAgent{ResourceID: res.ID}) + app := testutil.Fake(s.T(), faker, database.WorkspaceApp{AgentID: agt.ID}) + + dbm.EXPECT().GetWorkspaceByID(gomock.Any(), w.ID).Return(w, nil).AnyTimes() + dbm.EXPECT().GetWorkspaceBuildByID(gomock.Any(), b.ID).Return(b, nil).AnyTimes() + dbm.EXPECT().UpdateWorkspaceBuildFlagsByID(gomock.Any(), gomock.Any()).Return(nil).AnyTimes() check.Args(database.UpdateWorkspaceBuildFlagsByIDParams{ ID: b.ID, HasAITask: sql.NullBool{Bool: true, Valid: true}, From 870f8c0e0a2e857c4e79c919993cf66e4ca45fc9 Mon Sep 17 00:00:00 2001 From: Kacper Sawicki Date: Tue, 12 Aug 2025 12:44:01 +0000 Subject: [PATCH 3/4] Update queries.sql.go --- coderd/database/queries.sql.go | 92 ++++++---------------------------- 1 file changed, 16 insertions(+), 76 deletions(-) diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 977917178d7bb..8fc4a94a8ad07 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -8584,11 +8584,11 @@ func (q *sqlQuerier) InsertProvisionerJobLogs(ctx context.Context, arg InsertPro } const updateProvisionerJobLogsLength = `-- name: UpdateProvisionerJobLogsLength :exec -UPDATE +UPDATE provisioner_jobs -SET +SET logs_length = logs_length + $2 -WHERE +WHERE id = $1 ` @@ -8603,11 +8603,11 @@ func (q *sqlQuerier) UpdateProvisionerJobLogsLength(ctx context.Context, arg Upd } const updateProvisionerJobLogsOverflowed = `-- name: UpdateProvisionerJobLogsOverflowed :exec -UPDATE +UPDATE provisioner_jobs -SET +SET logs_overflowed = $2 -WHERE +WHERE id = $1 ` @@ -9625,7 +9625,7 @@ FROM provisioner_keys WHERE organization_id = $1 -AND +AND lower(name) = lower($2) ` @@ -9741,10 +9741,10 @@ WHERE AND -- exclude reserved built-in key id != '00000000-0000-0000-0000-000000000001'::uuid -AND +AND -- exclude reserved user-auth key id != '00000000-0000-0000-0000-000000000002'::uuid -AND +AND -- exclude reserved psk key id != '00000000-0000-0000-0000-000000000003'::uuid ` @@ -11528,7 +11528,7 @@ func (q *sqlQuerier) GetTailnetTunnelPeerIDs(ctx context.Context, srcID uuid.UUI } const updateTailnetPeerStatusByCoordinator = `-- name: UpdateTailnetPeerStatusByCoordinator :exec -UPDATE +UPDATE tailnet_peers SET status = $2 @@ -13519,14 +13519,14 @@ DO $$ DECLARE table_record record; BEGIN - FOR table_record IN - SELECT table_schema, table_name - FROM information_schema.tables + FOR table_record IN + SELECT table_schema, table_name + FROM information_schema.tables WHERE table_schema NOT IN ('pg_catalog', 'information_schema') AND table_type = 'BASE TABLE' LOOP - EXECUTE format('ALTER TABLE %I.%I DISABLE TRIGGER ALL', - table_record.table_schema, + EXECUTE format('ALTER TABLE %I.%I DISABLE TRIGGER ALL', + table_record.table_schema, table_record.table_name); END LOOP; END; @@ -17477,7 +17477,7 @@ WITH agent_stats AS ( coalesce((PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY connection_median_latency_ms)), -1)::FLOAT AS workspace_connection_latency_95 FROM workspace_agent_stats -- The greater than 0 is to support legacy agents that don't report connection_median_latency_ms. - WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 + WHERE workspace_agent_stats.created_at > $1 AND connection_median_latency_ms > 0 GROUP BY user_id, agent_id, workspace_id, template_id ), latest_agent_stats AS ( SELECT @@ -18897,66 +18897,6 @@ func (q *sqlQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, w return i, err } -const getLatestWorkspaceBuilds = `-- name: GetLatestWorkspaceBuilds :many -SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.has_external_agent, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name -FROM ( - SELECT - workspace_id, MAX(build_number) as max_build_number - FROM - workspace_build_with_user AS workspace_builds - GROUP BY - workspace_id -) m -JOIN - workspace_build_with_user AS wb -ON m.workspace_id = wb.workspace_id AND m.max_build_number = wb.build_number -` - -func (q *sqlQuerier) GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error) { - rows, err := q.db.QueryContext(ctx, getLatestWorkspaceBuilds) - if err != nil { - return nil, err - } - defer rows.Close() - var items []WorkspaceBuild - for rows.Next() { - var i WorkspaceBuild - if err := rows.Scan( - &i.ID, - &i.CreatedAt, - &i.UpdatedAt, - &i.WorkspaceID, - &i.TemplateVersionID, - &i.BuildNumber, - &i.Transition, - &i.InitiatorID, - &i.ProvisionerState, - &i.JobID, - &i.Deadline, - &i.Reason, - &i.DailyCost, - &i.MaxDeadline, - &i.TemplateVersionPresetID, - &i.HasAITask, - &i.AITaskSidebarAppID, - &i.HasExternalAgent, - &i.InitiatorByAvatarUrl, - &i.InitiatorByUsername, - &i.InitiatorByName, - ); 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 getLatestWorkspaceBuildsByWorkspaceIDs = `-- name: GetLatestWorkspaceBuildsByWorkspaceIDs :many SELECT wb.id, wb.created_at, wb.updated_at, wb.workspace_id, wb.template_version_id, wb.build_number, wb.transition, wb.initiator_id, wb.provisioner_state, wb.job_id, wb.deadline, wb.reason, wb.daily_cost, wb.max_deadline, wb.template_version_preset_id, wb.has_ai_task, wb.ai_task_sidebar_app_id, wb.has_external_agent, wb.initiator_by_avatar_url, wb.initiator_by_username, wb.initiator_by_name FROM ( From 5a1b1ed1ce91af597d7f05f779da11471f46f1a0 Mon Sep 17 00:00:00 2001 From: Kacper Sawicki Date: Mon, 18 Aug 2025 12:52:16 +0000 Subject: [PATCH 4/4] Fix migration numbers --- ...8_external_agents.down.sql => 000360_external_agents.down.sql} | 0 ...00358_external_agents.up.sql => 000360_external_agents.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000358_external_agents.down.sql => 000360_external_agents.down.sql} (100%) rename coderd/database/migrations/{000358_external_agents.up.sql => 000360_external_agents.up.sql} (100%) diff --git a/coderd/database/migrations/000358_external_agents.down.sql b/coderd/database/migrations/000360_external_agents.down.sql similarity index 100% rename from coderd/database/migrations/000358_external_agents.down.sql rename to coderd/database/migrations/000360_external_agents.down.sql diff --git a/coderd/database/migrations/000358_external_agents.up.sql b/coderd/database/migrations/000360_external_agents.up.sql similarity index 100% rename from coderd/database/migrations/000358_external_agents.up.sql rename to coderd/database/migrations/000360_external_agents.up.sql pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy