From 68c60bc0cf2a5a924e3a0a8ba790767781da679d Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 19:35:58 +0000 Subject: [PATCH 01/10] fix: use is-dormant instead of dormant_at --- coderd/database/dbfake/dbfake.go | 5 +- coderd/database/modelqueries.go | 2 +- coderd/database/queries.sql.go | 234 +++++++++--------- coderd/database/queries/workspaces.sql | 4 +- coderd/searchquery/search.go | 26 +- coderd/searchquery/search_test.go | 47 +--- coderd/workspaces.go | 22 +- enterprise/coderd/workspaces_test.go | 72 +++--- .../DormantWorkspaceBanner.tsx | 5 +- .../useWorkspacesToBeDeleted.ts | 2 +- .../pages/WorkspacesPage/WorkspacesPage.tsx | 4 +- site/src/utils/filters.ts | 2 +- 12 files changed, 168 insertions(+), 257 deletions(-) diff --git a/coderd/database/dbfake/dbfake.go b/coderd/database/dbfake/dbfake.go index e9b6582bc6b32..5bb61523a763c 100644 --- a/coderd/database/dbfake/dbfake.go +++ b/coderd/database/dbfake/dbfake.go @@ -6818,12 +6818,11 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database. } // We omit locked workspaces by default. - if arg.DormantAt.IsZero() && workspace.DormantAt.Valid { + if arg.IsDormant == "" && workspace.DormantAt.Valid { continue } - // Filter out workspaces that are locked after the timestamp. - if !arg.DormantAt.IsZero() && workspace.DormantAt.Time.Before(arg.DormantAt) { + if arg.IsDormant != "" && !workspace.DormantAt.Valid { continue } diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index b695d6682f3f2..17b4852efcf56 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -217,7 +217,7 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa arg.Name, arg.HasAgent, arg.AgentInactiveDisconnectTimeoutSeconds, - arg.DormantAt, + arg.IsDormant, arg.LastUsedBefore, arg.LastUsedAfter, arg.Offset, diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index ca8c1009f709d..3e617fddb1b8a 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -9723,6 +9723,119 @@ func (q *sqlQuerier) InsertWorkspaceResourceMetadata(ctx context.Context, arg In return items, nil } +const getWorkspaceAgentScriptsByAgentIDs = `-- name: GetWorkspaceAgentScriptsByAgentIDs :many +SELECT workspace_agent_id, log_source_id, log_path, created_at, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds FROM workspace_agent_scripts WHERE workspace_agent_id = ANY($1 :: uuid [ ]) +` + +func (q *sqlQuerier) GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentScript, error) { + rows, err := q.db.QueryContext(ctx, getWorkspaceAgentScriptsByAgentIDs, pq.Array(ids)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceAgentScript + for rows.Next() { + var i WorkspaceAgentScript + if err := rows.Scan( + &i.WorkspaceAgentID, + &i.LogSourceID, + &i.LogPath, + &i.CreatedAt, + &i.Script, + &i.Cron, + &i.StartBlocksLogin, + &i.RunOnStart, + &i.RunOnStop, + &i.TimeoutSeconds, + ); 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 insertWorkspaceAgentScripts = `-- name: InsertWorkspaceAgentScripts :many +INSERT INTO + workspace_agent_scripts (workspace_agent_id, created_at, log_source_id, log_path, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds) +SELECT + $1 :: uuid AS workspace_agent_id, + $2 :: timestamptz AS created_at, + unnest($3 :: uuid [ ]) AS log_source_id, + unnest($4 :: text [ ]) AS log_path, + unnest($5 :: text [ ]) AS script, + unnest($6 :: text [ ]) AS cron, + unnest($7 :: boolean [ ]) AS start_blocks_login, + unnest($8 :: boolean [ ]) AS run_on_start, + unnest($9 :: boolean [ ]) AS run_on_stop, + unnest($10 :: integer [ ]) AS timeout_seconds +RETURNING workspace_agent_scripts.workspace_agent_id, workspace_agent_scripts.log_source_id, workspace_agent_scripts.log_path, workspace_agent_scripts.created_at, workspace_agent_scripts.script, workspace_agent_scripts.cron, workspace_agent_scripts.start_blocks_login, workspace_agent_scripts.run_on_start, workspace_agent_scripts.run_on_stop, workspace_agent_scripts.timeout_seconds +` + +type InsertWorkspaceAgentScriptsParams struct { + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + LogSourceID []uuid.UUID `db:"log_source_id" json:"log_source_id"` + LogPath []string `db:"log_path" json:"log_path"` + Script []string `db:"script" json:"script"` + Cron []string `db:"cron" json:"cron"` + StartBlocksLogin []bool `db:"start_blocks_login" json:"start_blocks_login"` + RunOnStart []bool `db:"run_on_start" json:"run_on_start"` + RunOnStop []bool `db:"run_on_stop" json:"run_on_stop"` + TimeoutSeconds []int32 `db:"timeout_seconds" json:"timeout_seconds"` +} + +func (q *sqlQuerier) InsertWorkspaceAgentScripts(ctx context.Context, arg InsertWorkspaceAgentScriptsParams) ([]WorkspaceAgentScript, error) { + rows, err := q.db.QueryContext(ctx, insertWorkspaceAgentScripts, + arg.WorkspaceAgentID, + arg.CreatedAt, + pq.Array(arg.LogSourceID), + pq.Array(arg.LogPath), + pq.Array(arg.Script), + pq.Array(arg.Cron), + pq.Array(arg.StartBlocksLogin), + pq.Array(arg.RunOnStart), + pq.Array(arg.RunOnStop), + pq.Array(arg.TimeoutSeconds), + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceAgentScript + for rows.Next() { + var i WorkspaceAgentScript + if err := rows.Scan( + &i.WorkspaceAgentID, + &i.LogSourceID, + &i.LogPath, + &i.CreatedAt, + &i.Script, + &i.Cron, + &i.StartBlocksLogin, + &i.RunOnStart, + &i.RunOnStop, + &i.TimeoutSeconds, + ); 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 getDeploymentWorkspaceStats = `-- name: GetDeploymentWorkspaceStats :one WITH workspaces_with_jobs AS ( SELECT @@ -10160,8 +10273,8 @@ WHERE -- Filter by dormant workspaces. By default we do not return dormant -- workspaces since they are considered soft-deleted. AND CASE - WHEN $10 :: timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN - dormant_at IS NOT NULL AND dormant_at >= $10 + WHEN $10 :: text != '' THEN + dormant_at IS NOT NULL ELSE dormant_at IS NULL END @@ -10204,7 +10317,7 @@ type GetWorkspacesParams struct { Name string `db:"name" json:"name"` HasAgent string `db:"has_agent" json:"has_agent"` AgentInactiveDisconnectTimeoutSeconds int64 `db:"agent_inactive_disconnect_timeout_seconds" json:"agent_inactive_disconnect_timeout_seconds"` - DormantAt time.Time `db:"dormant_at" json:"dormant_at"` + IsDormant string `db:"is_dormant" json:"is_dormant"` LastUsedBefore time.Time `db:"last_used_before" json:"last_used_before"` LastUsedAfter time.Time `db:"last_used_after" json:"last_used_after"` Offset int32 `db:"offset_" json:"offset_"` @@ -10243,7 +10356,7 @@ func (q *sqlQuerier) GetWorkspaces(ctx context.Context, arg GetWorkspacesParams) arg.Name, arg.HasAgent, arg.AgentInactiveDisconnectTimeoutSeconds, - arg.DormantAt, + arg.IsDormant, arg.LastUsedBefore, arg.LastUsedAfter, arg.Offset, @@ -10683,116 +10796,3 @@ func (q *sqlQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.C _, err := q.db.ExecContext(ctx, updateWorkspacesDormantDeletingAtByTemplateID, arg.TimeTilDormantAutodeleteMs, arg.DormantAt, arg.TemplateID) return err } - -const getWorkspaceAgentScriptsByAgentIDs = `-- name: GetWorkspaceAgentScriptsByAgentIDs :many -SELECT workspace_agent_id, log_source_id, log_path, created_at, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds FROM workspace_agent_scripts WHERE workspace_agent_id = ANY($1 :: uuid [ ]) -` - -func (q *sqlQuerier) GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentScript, error) { - rows, err := q.db.QueryContext(ctx, getWorkspaceAgentScriptsByAgentIDs, pq.Array(ids)) - if err != nil { - return nil, err - } - defer rows.Close() - var items []WorkspaceAgentScript - for rows.Next() { - var i WorkspaceAgentScript - if err := rows.Scan( - &i.WorkspaceAgentID, - &i.LogSourceID, - &i.LogPath, - &i.CreatedAt, - &i.Script, - &i.Cron, - &i.StartBlocksLogin, - &i.RunOnStart, - &i.RunOnStop, - &i.TimeoutSeconds, - ); 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 insertWorkspaceAgentScripts = `-- name: InsertWorkspaceAgentScripts :many -INSERT INTO - workspace_agent_scripts (workspace_agent_id, created_at, log_source_id, log_path, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds) -SELECT - $1 :: uuid AS workspace_agent_id, - $2 :: timestamptz AS created_at, - unnest($3 :: uuid [ ]) AS log_source_id, - unnest($4 :: text [ ]) AS log_path, - unnest($5 :: text [ ]) AS script, - unnest($6 :: text [ ]) AS cron, - unnest($7 :: boolean [ ]) AS start_blocks_login, - unnest($8 :: boolean [ ]) AS run_on_start, - unnest($9 :: boolean [ ]) AS run_on_stop, - unnest($10 :: integer [ ]) AS timeout_seconds -RETURNING workspace_agent_scripts.workspace_agent_id, workspace_agent_scripts.log_source_id, workspace_agent_scripts.log_path, workspace_agent_scripts.created_at, workspace_agent_scripts.script, workspace_agent_scripts.cron, workspace_agent_scripts.start_blocks_login, workspace_agent_scripts.run_on_start, workspace_agent_scripts.run_on_stop, workspace_agent_scripts.timeout_seconds -` - -type InsertWorkspaceAgentScriptsParams struct { - WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - LogSourceID []uuid.UUID `db:"log_source_id" json:"log_source_id"` - LogPath []string `db:"log_path" json:"log_path"` - Script []string `db:"script" json:"script"` - Cron []string `db:"cron" json:"cron"` - StartBlocksLogin []bool `db:"start_blocks_login" json:"start_blocks_login"` - RunOnStart []bool `db:"run_on_start" json:"run_on_start"` - RunOnStop []bool `db:"run_on_stop" json:"run_on_stop"` - TimeoutSeconds []int32 `db:"timeout_seconds" json:"timeout_seconds"` -} - -func (q *sqlQuerier) InsertWorkspaceAgentScripts(ctx context.Context, arg InsertWorkspaceAgentScriptsParams) ([]WorkspaceAgentScript, error) { - rows, err := q.db.QueryContext(ctx, insertWorkspaceAgentScripts, - arg.WorkspaceAgentID, - arg.CreatedAt, - pq.Array(arg.LogSourceID), - pq.Array(arg.LogPath), - pq.Array(arg.Script), - pq.Array(arg.Cron), - pq.Array(arg.StartBlocksLogin), - pq.Array(arg.RunOnStart), - pq.Array(arg.RunOnStop), - pq.Array(arg.TimeoutSeconds), - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []WorkspaceAgentScript - for rows.Next() { - var i WorkspaceAgentScript - if err := rows.Scan( - &i.WorkspaceAgentID, - &i.LogSourceID, - &i.LogPath, - &i.CreatedAt, - &i.Script, - &i.Cron, - &i.StartBlocksLogin, - &i.RunOnStart, - &i.RunOnStop, - &i.TimeoutSeconds, - ); 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 -} diff --git a/coderd/database/queries/workspaces.sql b/coderd/database/queries/workspaces.sql index 849964bef14e6..805f64d70b927 100644 --- a/coderd/database/queries/workspaces.sql +++ b/coderd/database/queries/workspaces.sql @@ -242,8 +242,8 @@ WHERE -- Filter by dormant workspaces. By default we do not return dormant -- workspaces since they are considered soft-deleted. AND CASE - WHEN @dormant_at :: timestamptz > '0001-01-01 00:00:00+00'::timestamptz THEN - dormant_at IS NOT NULL AND dormant_at >= @dormant_at + WHEN @is_dormant :: text != '' THEN + dormant_at IS NOT NULL ELSE dormant_at IS NULL END diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index efdde1bb1d2e7..66f947dbaa313 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -12,7 +12,6 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/httpapi" - "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" ) @@ -70,11 +69,7 @@ func Users(query string) (database.GetUsersParams, []codersdk.ValidationError) { return filter, parser.Errors } -type PostFilter struct { - DeletingBy *time.Time `json:"deleting_by" format:"date-time"` -} - -func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectTimeout time.Duration) (database.GetWorkspacesParams, PostFilter, []codersdk.ValidationError) { +func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectTimeout time.Duration) (database.GetWorkspacesParams, []codersdk.ValidationError) { filter := database.GetWorkspacesParams{ AgentInactiveDisconnectTimeoutSeconds: int64(agentInactiveDisconnectTimeout.Seconds()), @@ -82,10 +77,8 @@ func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectT Limit: int32(page.Limit), } - var postFilter PostFilter - if query == "" { - return filter, postFilter, nil + return filter, nil } // Always lowercase for all searches. @@ -105,7 +98,7 @@ func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectT return nil }) if len(errors) > 0 { - return filter, postFilter, errors + return filter, errors } parser := httpapi.NewQueryParamParser() @@ -114,21 +107,12 @@ func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectT filter.Name = parser.String(values, "", "name") filter.Status = string(httpapi.ParseCustom(parser, values, "", "status", httpapi.ParseEnum[database.WorkspaceStatus])) filter.HasAgent = parser.String(values, "", "has-agent") - filter.DormantAt = parser.Time(values, time.Time{}, "dormant_at", "2006-01-02") + filter.IsDormant = parser.String(values, "", "is-dormant") filter.LastUsedAfter = parser.Time3339Nano(values, time.Time{}, "last_used_after") filter.LastUsedBefore = parser.Time3339Nano(values, time.Time{}, "last_used_before") - if _, ok := values["deleting_by"]; ok { - postFilter.DeletingBy = ptr.Ref(parser.Time(values, time.Time{}, "deleting_by", "2006-01-02")) - // We want to make sure to grab dormant workspaces since they - // are omitted by default. - if filter.DormantAt.IsZero() { - filter.DormantAt = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) - } - } - parser.ErrorExcessParams(values) - return filter, postFilter, parser.Errors + return filter, parser.Errors } func searchTerms(query string, defaultKey func(term string, values url.Values) error) (url.Values, []codersdk.ValidationError) { diff --git a/coderd/searchquery/search_test.go b/coderd/searchquery/search_test.go index 929a17b169643..9a2f1f0ed0b28 100644 --- a/coderd/searchquery/search_test.go +++ b/coderd/searchquery/search_test.go @@ -12,7 +12,6 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/coderd/searchquery" - "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" ) @@ -150,7 +149,7 @@ func TestSearchWorkspace(t *testing.T) { c := c t.Run(c.Name, func(t *testing.T) { t.Parallel() - values, postFilter, errs := searchquery.Workspaces(c.Query, codersdk.Pagination{}, 0) + values, errs := searchquery.Workspaces(c.Query, codersdk.Pagination{}, 0) if c.ExpectedErrorContains != "" { assert.True(t, len(errs) > 0, "expect some errors") var s strings.Builder @@ -159,7 +158,6 @@ func TestSearchWorkspace(t *testing.T) { } assert.Contains(t, s.String(), c.ExpectedErrorContains) } else { - assert.Empty(t, postFilter) assert.Len(t, errs, 0, "expected no error") assert.Equal(t, c.Expected, values, "expected values") } @@ -170,51 +168,10 @@ func TestSearchWorkspace(t *testing.T) { query := `` timeout := 1337 * time.Second - values, _, errs := searchquery.Workspaces(query, codersdk.Pagination{}, timeout) + values, errs := searchquery.Workspaces(query, codersdk.Pagination{}, timeout) require.Empty(t, errs) require.Equal(t, int64(timeout.Seconds()), values.AgentInactiveDisconnectTimeoutSeconds) }) - - t.Run("TestSearchWorkspacePostFilter", func(t *testing.T) { - t.Parallel() - testCases := []struct { - Name string - Query string - Expected searchquery.PostFilter - }{ - { - Name: "Empty", - Query: "", - Expected: searchquery.PostFilter{}, - }, - { - Name: "DeletingBy", - Query: "deleting_by:2023-06-09", - Expected: searchquery.PostFilter{ - DeletingBy: ptr.Ref(time.Date( - 2023, 6, 9, 0, 0, 0, 0, time.UTC)), - }, - }, - { - Name: "MultipleParams", - Query: "deleting_by:2023-06-09 name:workspace-name", - Expected: searchquery.PostFilter{ - DeletingBy: ptr.Ref(time.Date( - 2023, 6, 9, 0, 0, 0, 0, time.UTC)), - }, - }, - } - - for _, c := range testCases { - c := c - t.Run(c.Name, func(t *testing.T) { - t.Parallel() - _, postFilter, errs := searchquery.Workspaces(c.Query, codersdk.Pagination{}, 0) - assert.Len(t, errs, 0, "expected no error") - assert.Equal(t, c.Expected, postFilter, "expected values") - }) - } - }) } func TestSearchAudit(t *testing.T) { diff --git a/coderd/workspaces.go b/coderd/workspaces.go index b4f971fe974e6..d90b763fb98bb 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -131,7 +131,7 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { } queryStr := r.URL.Query().Get("q") - filter, postFilter, errs := searchquery.Workspaces(queryStr, page, api.AgentInactiveDisconnectTimeout) + filter, errs := searchquery.Workspaces(queryStr, page, api.AgentInactiveDisconnectTimeout) if len(errs) > 0 { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid workspace search query.", @@ -191,26 +191,8 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { return } - var filteredWorkspaces []codersdk.Workspace - // apply post filters, if they exist - if postFilter.DeletingBy == nil { - filteredWorkspaces = append(filteredWorkspaces, wss...) - } else { - for _, v := range wss { - if v.DeletingAt == nil { - continue - } - // get the beginning of the day on which deletion is scheduled - truncatedDeletionAt := time.Date(v.DeletingAt.Year(), v.DeletingAt.Month(), v.DeletingAt.Day(), 0, 0, 0, 0, v.DeletingAt.Location()) - if truncatedDeletionAt.After(*postFilter.DeletingBy) { - continue - } - filteredWorkspaces = append(filteredWorkspaces, v) - } - } - httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspacesResponse{ - Workspaces: filteredWorkspaces, + Workspaces: wss, Count: int(workspaceRows[0].Count), }) } diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index e6cd47524c516..8e09b82631285 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -3,7 +3,6 @@ package coderd_test import ( "context" "encoding/json" - "fmt" "net/http" "sync/atomic" "testing" @@ -17,7 +16,6 @@ import ( "github.com/coder/coder/v2/coderd/autobuild" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" - "github.com/coder/coder/v2/coderd/database/dbtestutil" agplschedule "github.com/coder/coder/v2/coderd/schedule" "github.com/coder/coder/v2/coderd/schedule/cron" "github.com/coder/coder/v2/coderd/util/ptr" @@ -741,62 +739,56 @@ func TestWorkspaceAutobuild(t *testing.T) { func TestWorkspacesFiltering(t *testing.T) { t.Parallel() - t.Run("DeletingBy", func(t *testing.T) { - t.Parallel() - - dormantTTL := 24 * time.Hour - - // nolint:gocritic // https://github.com/coder/coder/issues/9682 - db, ps := dbtestutil.NewDB(t, dbtestutil.WithTimezone("UTC")) - + t.Run("IsDormant", func(t *testing.T) { + ctx := testutil.Context(t, testutil.WaitMedium) client, user := coderdenttest.New(t, &coderdenttest.Options{ Options: &coderdtest.Options{ IncludeProvisionerDaemon: true, - Database: db, - Pubsub: ps, + TemplateScheduleStore: schedule.NewEnterpriseTemplateScheduleStore(agplUserQuietHoursScheduleStore()), }, LicenseOptions: &coderdenttest.LicenseOptions{ - Features: license.Features{ - codersdk.FeatureAdvancedTemplateScheduling: 1, - }, + Features: license.Features{codersdk.FeatureAdvancedTemplateScheduling: 1}, }, }) - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) - _ = coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + // Create a template version that passes to get a functioning workspace. + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.PlanComplete, + ProvisionApply: echo.ApplyComplete, + }) + coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - // update template with inactivity ttl - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() + dormantWS1 := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, dormantWS1.LatestBuild.ID) - template, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ - TimeTilDormantAutoDeleteMillis: dormantTTL.Milliseconds(), - }) - require.NoError(t, err) - require.Equal(t, dormantTTL.Milliseconds(), template.TimeTilDormantAutoDeleteMillis) + dormantWS2 := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, dormantWS2.LatestBuild.ID) - workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) - _ = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) + activeWS := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, activeWS.LatestBuild.ID) - // stop build so workspace is inactive - stopBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, stopBuild.ID) - err = client.UpdateWorkspaceDormancy(ctx, workspace.ID, codersdk.UpdateWorkspaceDormancy{ - Dormant: true, - }) + err := client.UpdateWorkspaceDormancy(ctx, dormantWS1.ID, codersdk.UpdateWorkspaceDormancy{Dormant: true}) + require.NoError(t, err) + + err = client.UpdateWorkspaceDormancy(ctx, dormantWS2.ID, codersdk.UpdateWorkspaceDormancy{Dormant: true}) require.NoError(t, err) - workspace = coderdtest.MustWorkspace(t, client, workspace.ID) - require.NotNil(t, workspace.DeletingAt) - res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ - // adding a second to time.Now() to give some buffer in case test runs quickly - FilterQuery: fmt.Sprintf("deleting_by:%s", time.Now().Add(time.Second).Add(dormantTTL).Format("2006-01-02")), + resp, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ + FilterQuery: "is-dormant:true", }) require.NoError(t, err) - require.Len(t, res.Workspaces, 1) - require.Equal(t, workspace.ID, res.Workspaces[0].ID) + require.Len(t, resp.Workspaces, 2) + + for _, ws := range resp.Workspaces { + if ws.ID != dormantWS1.ID && ws.ID != dormantWS2.ID { + t.Fatalf("Unexpected workspace %+v", ws) + } + } }) + } // TestWorkspacesWithoutTemplatePerms creates a workspace for a user, then drops diff --git a/site/src/components/WorkspaceDeletion/DormantWorkspaceBanner.tsx b/site/src/components/WorkspaceDeletion/DormantWorkspaceBanner.tsx index 304da5071483c..9315ecc9638b6 100644 --- a/site/src/components/WorkspaceDeletion/DormantWorkspaceBanner.tsx +++ b/site/src/components/WorkspaceDeletion/DormantWorkspaceBanner.tsx @@ -88,10 +88,7 @@ export const DormantWorkspaceBanner = ({ ) : ( <> There are{" "} - + workspaces {" "} that may be deleted soon due to inactivity. Activate the workspaces diff --git a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts index 01c9ed16a2d90..7609621fd88fc 100644 --- a/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts +++ b/site/src/pages/TemplateSettingsPage/TemplateSchedulePage/useWorkspacesToBeDeleted.ts @@ -44,7 +44,7 @@ export const useWorkspacesToBeDeleted = ( const { data } = useWorkspacesData({ page: 0, limit: 0, - query: "template:" + template.name + " dormant_at:1970-01-01", + query: "template:" + template.name + " is-dormant:true", }); return data?.workspaces?.filter((workspace: Workspace) => { if (!workspace.dormant_at || !formValues.time_til_dormant_autodelete_ms) { diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index af2bc502efbb3..4be2a492f278b 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -68,10 +68,10 @@ const WorkspacesPage: FC = () => { // are at risk of being deleted. useEffect(() => { if (schedulingEnabled) { - const includesDormant = filterProps.filter.query.includes("dormant_at"); + const includesDormant = filterProps.filter.query.includes("is-dormant"); const dormantQuery = includesDormant ? filterProps.filter.query - : filterProps.filter.query + " dormant_at:1970-01-01"; + : filterProps.filter.query + " is-dormant:true"; if (includesDormant && data) { setDormantWorkspaces(data.workspaces); diff --git a/site/src/utils/filters.ts b/site/src/utils/filters.ts index 556e679de959c..2c8dc63c07501 100644 --- a/site/src/utils/filters.ts +++ b/site/src/utils/filters.ts @@ -17,7 +17,7 @@ export const workspaceFilterQuery = { all: "", running: "status:running", failed: "status:failed", - dormant: "dormant_at:1970-01-01", + dormant: "is-dormant:true", }; export const userFilterQuery = { From ff60af3f0252131ad8707037311996389019cd71 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 19:47:59 +0000 Subject: [PATCH 02/10] fix some lingering tests --- coderd/workspaces_test.go | 61 ++------------------------------------- 1 file changed, 2 insertions(+), 59 deletions(-) diff --git a/coderd/workspaces_test.go b/coderd/workspaces_test.go index 85b0f22b29af0..2193971608a39 100644 --- a/coderd/workspaces_test.go +++ b/coderd/workspaces_test.go @@ -9,7 +9,6 @@ import ( "net/http" "os" "strings" - "sync/atomic" "testing" "time" @@ -1395,63 +1394,7 @@ func TestWorkspaceFilterManual(t *testing.T) { }, testutil.IntervalMedium, "agent status timeout") }) - t.Run("FilterQueryHasDeletingByAndUnlicensed", func(t *testing.T) { - // this test has a licensed counterpart in enterprise/coderd/workspaces_test.go: FilterQueryHasDeletingByAndLicensed - t.Parallel() - inactivityTTL := 1 * 24 * time.Hour - var setCalled int64 - - client := coderdtest.New(t, &coderdtest.Options{ - IncludeProvisionerDaemon: true, - TemplateScheduleStore: schedule.MockTemplateScheduleStore{ - SetFn: func(ctx context.Context, db database.Store, template database.Template, options schedule.TemplateScheduleOptions) (database.Template, error) { - if atomic.AddInt64(&setCalled, 1) == 2 { - assert.Equal(t, inactivityTTL, options.TimeTilDormant) - } - template.TimeTilDormant = int64(options.TimeTilDormant) - return template, nil - }, - }, - }) - user := coderdtest.CreateFirstUser(t, client) - authToken := uuid.NewString() - version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionPlan: echo.PlanComplete, - ProvisionApply: echo.ProvisionApplyWithAgent(authToken), - }) - template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) - coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) - - // update template with inactivity ttl - ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) - defer cancel() - - template, err := client.UpdateTemplateMeta(ctx, template.ID, codersdk.UpdateTemplateMeta{ - TimeTilDormantMillis: inactivityTTL.Milliseconds(), - }) - - assert.NoError(t, err) - assert.Equal(t, inactivityTTL.Milliseconds(), template.TimeTilDormantMillis) - - workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) - - // stop build so workspace is inactive - stopBuild := coderdtest.CreateWorkspaceBuild(t, client, workspace, database.WorkspaceTransitionStop) - coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, stopBuild.ID) - - res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ - FilterQuery: fmt.Sprintf("deleting_by:%s", time.Now().Add(inactivityTTL).Format("2006-01-02")), - }) - - assert.NoError(t, err) - // we are expecting that no workspaces are returned as user is unlicensed - // and template.TimeTilDormant should be 0 - assert.Len(t, res.Workspaces, 0) - }) - - t.Run("DormantAt", func(t *testing.T) { + t.Run("IsDormant", func(t *testing.T) { // this test has a licensed counterpart in enterprise/coderd/workspaces_test.go: FilterQueryHasDeletingByAndLicensed t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ @@ -1484,7 +1427,7 @@ func TestWorkspaceFilterManual(t *testing.T) { require.NoError(t, err) res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{ - FilterQuery: fmt.Sprintf("dormant_at:%s", time.Now().Add(-time.Minute).Format("2006-01-02")), + FilterQuery: "is-dormant:true", }) require.NoError(t, err) require.Len(t, res.Workspaces, 1) From 30483ea175e947e55e8c758168c5ca1e555081ed Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 19:48:24 +0000 Subject: [PATCH 03/10] make fmt --- enterprise/coderd/workspaces_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 8e09b82631285..01b02917e1497 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -788,7 +788,6 @@ func TestWorkspacesFiltering(t *testing.T) { } } }) - } // TestWorkspacesWithoutTemplatePerms creates a workspace for a user, then drops From 6fcb34d8243c57a515590cfc23992867207367e5 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 19:49:32 +0000 Subject: [PATCH 04/10] make gen --- coderd/externalauth.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coderd/externalauth.go b/coderd/externalauth.go index 775ff5436284f..31dff667c28e7 100644 --- a/coderd/externalauth.go +++ b/coderd/externalauth.go @@ -8,13 +8,14 @@ import ( "golang.org/x/sync/errgroup" + "github.com/sqlc-dev/pqtype" + "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/externalauth" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/codersdk" - "github.com/sqlc-dev/pqtype" ) // @Summary Get external auth by ID From b5411b0d18d2f4f852e655304112eb2fa395066c Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 22:17:19 +0000 Subject: [PATCH 05/10] begin debugging CI --- coderd/database/generate.sh | 2 ++ enterprise/coderd/workspaces_test.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/coderd/database/generate.sh b/coderd/database/generate.sh index e8777a036a3cf..e4034f1d09c26 100755 --- a/coderd/database/generate.sh +++ b/coderd/database/generate.sh @@ -20,6 +20,8 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") # The logic below depends on the exact version being correct :( sqlc generate + ls queries/*.sql.go + first=true for fi in queries/*.sql.go; do # Find the last line from the imports section and add 1. We have to diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 01b02917e1497..cd7bec21f281b 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -740,6 +740,8 @@ func TestWorkspacesFiltering(t *testing.T) { t.Parallel() t.Run("IsDormant", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitMedium) client, user := coderdenttest.New(t, &coderdenttest.Options{ Options: &coderdtest.Options{ From 2a6a74bfb7274a29ce913e589a1381db331528ba Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 22:33:50 +0000 Subject: [PATCH 06/10] more debugging --- coderd/database/generate.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/coderd/database/generate.sh b/coderd/database/generate.sh index e4034f1d09c26..8fedf396030d6 100755 --- a/coderd/database/generate.sh +++ b/coderd/database/generate.sh @@ -20,7 +20,9 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") # The logic below depends on the exact version being correct :( sqlc generate - ls queries/*.sql.go + cat /etc/os-release + ls --version + ls -ty -1 queries/*.sql.go first=true for fi in queries/*.sql.go; do From 805dabb5255e4799c831ac3e8959f8cbaba4d689 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 22:37:19 +0000 Subject: [PATCH 07/10] try again --- coderd/database/generate.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/coderd/database/generate.sh b/coderd/database/generate.sh index 8fedf396030d6..e3826facb24d5 100755 --- a/coderd/database/generate.sh +++ b/coderd/database/generate.sh @@ -22,10 +22,10 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") cat /etc/os-release ls --version - ls -ty -1 queries/*.sql.go + files="$(ls -1 queries/*.sql.go)" first=true - for fi in queries/*.sql.go; do + for fi in "$files"; do # Find the last line from the imports section and add 1. We have to # disable pipefail temporarily to avoid ERRPIPE errors when piping into # `head -n1`. From 7f2b46a5cf171a2176e281dd61bd9c3ee0bd75e9 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 22:50:50 +0000 Subject: [PATCH 08/10] . --- coderd/database/generate.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/coderd/database/generate.sh b/coderd/database/generate.sh index e3826facb24d5..f59911f748022 100755 --- a/coderd/database/generate.sh +++ b/coderd/database/generate.sh @@ -20,12 +20,9 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") # The logic below depends on the exact version being correct :( sqlc generate - cat /etc/os-release - ls --version - files="$(ls -1 queries/*.sql.go)" - first=true - for fi in "$files"; do + for fi in queries/*.sql.go; do + echo "$fi" # Find the last line from the imports section and add 1. We have to # disable pipefail temporarily to avoid ERRPIPE errors when piping into # `head -n1`. From 923fe916d8069519d4b7e7a32aa1a4af303e15d9 Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 23:02:28 +0000 Subject: [PATCH 09/10] locale --- coderd/database/generate.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/generate.sh b/coderd/database/generate.sh index f59911f748022..0905479405594 100755 --- a/coderd/database/generate.sh +++ b/coderd/database/generate.sh @@ -20,9 +20,9 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") # The logic below depends on the exact version being correct :( sqlc generate + locale first=true for fi in queries/*.sql.go; do - echo "$fi" # Find the last line from the imports section and add 1. We have to # disable pipefail temporarily to avoid ERRPIPE errors when piping into # `head -n1`. From b19ae06d1a695b486a21f1941cd965847c9806db Mon Sep 17 00:00:00 2001 From: Jon Ayers Date: Tue, 10 Oct 2023 23:12:49 +0000 Subject: [PATCH 10/10] woooooo --- coderd/database/generate.sh | 1 - coderd/database/queries.sql.go | 226 ++++++++++++++++----------------- 2 files changed, 113 insertions(+), 114 deletions(-) diff --git a/coderd/database/generate.sh b/coderd/database/generate.sh index 0905479405594..e8777a036a3cf 100755 --- a/coderd/database/generate.sh +++ b/coderd/database/generate.sh @@ -20,7 +20,6 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}") # The logic below depends on the exact version being correct :( sqlc generate - locale first=true for fi in queries/*.sql.go; do # Find the last line from the imports section and add 1. We have to diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 3e617fddb1b8a..418a46eae4447 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -9723,119 +9723,6 @@ func (q *sqlQuerier) InsertWorkspaceResourceMetadata(ctx context.Context, arg In return items, nil } -const getWorkspaceAgentScriptsByAgentIDs = `-- name: GetWorkspaceAgentScriptsByAgentIDs :many -SELECT workspace_agent_id, log_source_id, log_path, created_at, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds FROM workspace_agent_scripts WHERE workspace_agent_id = ANY($1 :: uuid [ ]) -` - -func (q *sqlQuerier) GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentScript, error) { - rows, err := q.db.QueryContext(ctx, getWorkspaceAgentScriptsByAgentIDs, pq.Array(ids)) - if err != nil { - return nil, err - } - defer rows.Close() - var items []WorkspaceAgentScript - for rows.Next() { - var i WorkspaceAgentScript - if err := rows.Scan( - &i.WorkspaceAgentID, - &i.LogSourceID, - &i.LogPath, - &i.CreatedAt, - &i.Script, - &i.Cron, - &i.StartBlocksLogin, - &i.RunOnStart, - &i.RunOnStop, - &i.TimeoutSeconds, - ); 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 insertWorkspaceAgentScripts = `-- name: InsertWorkspaceAgentScripts :many -INSERT INTO - workspace_agent_scripts (workspace_agent_id, created_at, log_source_id, log_path, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds) -SELECT - $1 :: uuid AS workspace_agent_id, - $2 :: timestamptz AS created_at, - unnest($3 :: uuid [ ]) AS log_source_id, - unnest($4 :: text [ ]) AS log_path, - unnest($5 :: text [ ]) AS script, - unnest($6 :: text [ ]) AS cron, - unnest($7 :: boolean [ ]) AS start_blocks_login, - unnest($8 :: boolean [ ]) AS run_on_start, - unnest($9 :: boolean [ ]) AS run_on_stop, - unnest($10 :: integer [ ]) AS timeout_seconds -RETURNING workspace_agent_scripts.workspace_agent_id, workspace_agent_scripts.log_source_id, workspace_agent_scripts.log_path, workspace_agent_scripts.created_at, workspace_agent_scripts.script, workspace_agent_scripts.cron, workspace_agent_scripts.start_blocks_login, workspace_agent_scripts.run_on_start, workspace_agent_scripts.run_on_stop, workspace_agent_scripts.timeout_seconds -` - -type InsertWorkspaceAgentScriptsParams struct { - WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - LogSourceID []uuid.UUID `db:"log_source_id" json:"log_source_id"` - LogPath []string `db:"log_path" json:"log_path"` - Script []string `db:"script" json:"script"` - Cron []string `db:"cron" json:"cron"` - StartBlocksLogin []bool `db:"start_blocks_login" json:"start_blocks_login"` - RunOnStart []bool `db:"run_on_start" json:"run_on_start"` - RunOnStop []bool `db:"run_on_stop" json:"run_on_stop"` - TimeoutSeconds []int32 `db:"timeout_seconds" json:"timeout_seconds"` -} - -func (q *sqlQuerier) InsertWorkspaceAgentScripts(ctx context.Context, arg InsertWorkspaceAgentScriptsParams) ([]WorkspaceAgentScript, error) { - rows, err := q.db.QueryContext(ctx, insertWorkspaceAgentScripts, - arg.WorkspaceAgentID, - arg.CreatedAt, - pq.Array(arg.LogSourceID), - pq.Array(arg.LogPath), - pq.Array(arg.Script), - pq.Array(arg.Cron), - pq.Array(arg.StartBlocksLogin), - pq.Array(arg.RunOnStart), - pq.Array(arg.RunOnStop), - pq.Array(arg.TimeoutSeconds), - ) - if err != nil { - return nil, err - } - defer rows.Close() - var items []WorkspaceAgentScript - for rows.Next() { - var i WorkspaceAgentScript - if err := rows.Scan( - &i.WorkspaceAgentID, - &i.LogSourceID, - &i.LogPath, - &i.CreatedAt, - &i.Script, - &i.Cron, - &i.StartBlocksLogin, - &i.RunOnStart, - &i.RunOnStop, - &i.TimeoutSeconds, - ); 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 getDeploymentWorkspaceStats = `-- name: GetDeploymentWorkspaceStats :one WITH workspaces_with_jobs AS ( SELECT @@ -10796,3 +10683,116 @@ func (q *sqlQuerier) UpdateWorkspacesDormantDeletingAtByTemplateID(ctx context.C _, err := q.db.ExecContext(ctx, updateWorkspacesDormantDeletingAtByTemplateID, arg.TimeTilDormantAutodeleteMs, arg.DormantAt, arg.TemplateID) return err } + +const getWorkspaceAgentScriptsByAgentIDs = `-- name: GetWorkspaceAgentScriptsByAgentIDs :many +SELECT workspace_agent_id, log_source_id, log_path, created_at, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds FROM workspace_agent_scripts WHERE workspace_agent_id = ANY($1 :: uuid [ ]) +` + +func (q *sqlQuerier) GetWorkspaceAgentScriptsByAgentIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceAgentScript, error) { + rows, err := q.db.QueryContext(ctx, getWorkspaceAgentScriptsByAgentIDs, pq.Array(ids)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceAgentScript + for rows.Next() { + var i WorkspaceAgentScript + if err := rows.Scan( + &i.WorkspaceAgentID, + &i.LogSourceID, + &i.LogPath, + &i.CreatedAt, + &i.Script, + &i.Cron, + &i.StartBlocksLogin, + &i.RunOnStart, + &i.RunOnStop, + &i.TimeoutSeconds, + ); 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 insertWorkspaceAgentScripts = `-- name: InsertWorkspaceAgentScripts :many +INSERT INTO + workspace_agent_scripts (workspace_agent_id, created_at, log_source_id, log_path, script, cron, start_blocks_login, run_on_start, run_on_stop, timeout_seconds) +SELECT + $1 :: uuid AS workspace_agent_id, + $2 :: timestamptz AS created_at, + unnest($3 :: uuid [ ]) AS log_source_id, + unnest($4 :: text [ ]) AS log_path, + unnest($5 :: text [ ]) AS script, + unnest($6 :: text [ ]) AS cron, + unnest($7 :: boolean [ ]) AS start_blocks_login, + unnest($8 :: boolean [ ]) AS run_on_start, + unnest($9 :: boolean [ ]) AS run_on_stop, + unnest($10 :: integer [ ]) AS timeout_seconds +RETURNING workspace_agent_scripts.workspace_agent_id, workspace_agent_scripts.log_source_id, workspace_agent_scripts.log_path, workspace_agent_scripts.created_at, workspace_agent_scripts.script, workspace_agent_scripts.cron, workspace_agent_scripts.start_blocks_login, workspace_agent_scripts.run_on_start, workspace_agent_scripts.run_on_stop, workspace_agent_scripts.timeout_seconds +` + +type InsertWorkspaceAgentScriptsParams struct { + WorkspaceAgentID uuid.UUID `db:"workspace_agent_id" json:"workspace_agent_id"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + LogSourceID []uuid.UUID `db:"log_source_id" json:"log_source_id"` + LogPath []string `db:"log_path" json:"log_path"` + Script []string `db:"script" json:"script"` + Cron []string `db:"cron" json:"cron"` + StartBlocksLogin []bool `db:"start_blocks_login" json:"start_blocks_login"` + RunOnStart []bool `db:"run_on_start" json:"run_on_start"` + RunOnStop []bool `db:"run_on_stop" json:"run_on_stop"` + TimeoutSeconds []int32 `db:"timeout_seconds" json:"timeout_seconds"` +} + +func (q *sqlQuerier) InsertWorkspaceAgentScripts(ctx context.Context, arg InsertWorkspaceAgentScriptsParams) ([]WorkspaceAgentScript, error) { + rows, err := q.db.QueryContext(ctx, insertWorkspaceAgentScripts, + arg.WorkspaceAgentID, + arg.CreatedAt, + pq.Array(arg.LogSourceID), + pq.Array(arg.LogPath), + pq.Array(arg.Script), + pq.Array(arg.Cron), + pq.Array(arg.StartBlocksLogin), + pq.Array(arg.RunOnStart), + pq.Array(arg.RunOnStop), + pq.Array(arg.TimeoutSeconds), + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []WorkspaceAgentScript + for rows.Next() { + var i WorkspaceAgentScript + if err := rows.Scan( + &i.WorkspaceAgentID, + &i.LogSourceID, + &i.LogPath, + &i.CreatedAt, + &i.Script, + &i.Cron, + &i.StartBlocksLogin, + &i.RunOnStart, + &i.RunOnStop, + &i.TimeoutSeconds, + ); 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 +}