Skip to content

Commit 42b1a66

Browse files
committed
Merge remote-tracking branch 'origin/main' into bp-security
2 parents 7cdb640 + 50ff06c commit 42b1a66

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2716
-265
lines changed

.github/workflows/ci.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ jobs:
895895
needs: changes
896896
# We always build the dylibs on Go changes to verify we're not merging unbuildable code,
897897
# but they need only be signed and uploaded on coder/coder main.
898-
if: needs.changes.outputs.docs-only == 'false' || github.ref == 'refs/heads/main'
898+
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
899899
runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest' }}
900900
steps:
901901
- name: Harden Runner
@@ -976,7 +976,7 @@ jobs:
976976
- changes
977977
- build-dylib
978978
if: github.ref == 'refs/heads/main' && needs.changes.outputs.docs-only == 'false' && !github.event.pull_request.head.repo.fork
979-
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
979+
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-22.04' }}
980980
permissions:
981981
packages: write # Needed to push images to ghcr.io
982982
env:

coderd/apidoc/docs.go

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/autobuild/lifecycle_executor.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package autobuild
33
import (
44
"context"
55
"database/sql"
6+
"fmt"
67
"net/http"
78
"sync"
89
"sync/atomic"
@@ -177,6 +178,15 @@ func (e *Executor) runOnce(t time.Time) Stats {
177178
err := e.db.InTx(func(tx database.Store) error {
178179
var err error
179180

181+
ok, err := tx.TryAcquireLock(e.ctx, database.GenLockID(fmt.Sprintf("lifecycle-executor:%s", wsID)))
182+
if err != nil {
183+
return xerrors.Errorf("try acquire lifecycle executor lock: %w", err)
184+
}
185+
if !ok {
186+
log.Debug(e.ctx, "unable to acquire lock for workspace, skipping")
187+
return nil
188+
}
189+
180190
// Re-check eligibility since the first check was outside the
181191
// transaction and the workspace settings may have changed.
182192
ws, err = tx.GetWorkspaceByID(e.ctx, wsID)
@@ -389,7 +399,7 @@ func (e *Executor) runOnce(t time.Time) Stats {
389399
}
390400
return nil
391401
}()
392-
if err != nil {
402+
if err != nil && !xerrors.Is(err, context.Canceled) {
393403
log.Error(e.ctx, "failed to transition workspace", slog.Error(err))
394404
statsMu.Lock()
395405
stats.Errors[wsID] = err

coderd/autobuild/lifecycle_executor_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/coder/coder/v2/coderd/coderdtest"
1919
"github.com/coder/coder/v2/coderd/database"
2020
"github.com/coder/coder/v2/coderd/database/dbauthz"
21+
"github.com/coder/coder/v2/coderd/database/dbtestutil"
2122
"github.com/coder/coder/v2/coderd/notifications"
2223
"github.com/coder/coder/v2/coderd/notifications/notificationstest"
2324
"github.com/coder/coder/v2/coderd/schedule"
@@ -72,6 +73,76 @@ func TestExecutorAutostartOK(t *testing.T) {
7273
require.Equal(t, template.AutostartRequirement.DaysOfWeek, []string{"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"})
7374
}
7475

76+
func TestMultipleLifecycleExecutors(t *testing.T) {
77+
t.Parallel()
78+
79+
db, ps := dbtestutil.NewDB(t)
80+
81+
var (
82+
sched = mustSchedule(t, "CRON_TZ=UTC 0 * * * *")
83+
// Create our first client
84+
tickCh = make(chan time.Time, 2)
85+
statsChA = make(chan autobuild.Stats)
86+
clientA = coderdtest.New(t, &coderdtest.Options{
87+
IncludeProvisionerDaemon: true,
88+
AutobuildTicker: tickCh,
89+
AutobuildStats: statsChA,
90+
Database: db,
91+
Pubsub: ps,
92+
})
93+
// ... And then our second client
94+
statsChB = make(chan autobuild.Stats)
95+
_ = coderdtest.New(t, &coderdtest.Options{
96+
IncludeProvisionerDaemon: true,
97+
AutobuildTicker: tickCh,
98+
AutobuildStats: statsChB,
99+
Database: db,
100+
Pubsub: ps,
101+
})
102+
// Now create a workspace (we can use either client, it doesn't matter)
103+
workspace = mustProvisionWorkspace(t, clientA, func(cwr *codersdk.CreateWorkspaceRequest) {
104+
cwr.AutostartSchedule = ptr.Ref(sched.String())
105+
})
106+
)
107+
108+
// Have the workspace stopped so we can perform an autostart
109+
workspace = coderdtest.MustTransitionWorkspace(t, clientA, workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
110+
111+
// Get both clients to perform a lifecycle execution tick
112+
next := sched.Next(workspace.LatestBuild.CreatedAt)
113+
114+
startCh := make(chan struct{})
115+
go func() {
116+
<-startCh
117+
tickCh <- next
118+
}()
119+
go func() {
120+
<-startCh
121+
tickCh <- next
122+
}()
123+
close(startCh)
124+
125+
// Now we want to check the stats for both clients
126+
statsA := <-statsChA
127+
statsB := <-statsChB
128+
129+
// We expect there to be no errors
130+
assert.Len(t, statsA.Errors, 0)
131+
assert.Len(t, statsB.Errors, 0)
132+
133+
// We also expect there to have been only one transition
134+
require.Equal(t, 1, len(statsA.Transitions)+len(statsB.Transitions))
135+
136+
stats := statsA
137+
if len(statsB.Transitions) == 1 {
138+
stats = statsB
139+
}
140+
141+
// And we expect this transition to have been a start transition
142+
assert.Contains(t, stats.Transitions, workspace.ID)
143+
assert.Equal(t, database.WorkspaceTransitionStart, stats.Transitions[workspace.ID])
144+
}
145+
75146
func TestExecutorAutostartTemplateUpdated(t *testing.T) {
76147
t.Parallel()
77148

coderd/database/dbmem/dbmem.go

Lines changed: 85 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3804,35 +3804,92 @@ func (q *FakeQuerier) GetProvisionerJobsByIDsWithQueuePosition(_ context.Context
38043804
q.mutex.RLock()
38053805
defer q.mutex.RUnlock()
38063806

3807-
jobs := make([]database.GetProvisionerJobsByIDsWithQueuePositionRow, 0)
3808-
queuePosition := int64(1)
3807+
// WITH pending_jobs AS (
3808+
// SELECT
3809+
// id, created_at
3810+
// FROM
3811+
// provisioner_jobs
3812+
// WHERE
3813+
// started_at IS NULL
3814+
// AND
3815+
// canceled_at IS NULL
3816+
// AND
3817+
// completed_at IS NULL
3818+
// AND
3819+
// error IS NULL
3820+
// ),
3821+
type pendingJobRow struct {
3822+
ID uuid.UUID
3823+
CreatedAt time.Time
3824+
}
3825+
pendingJobs := make([]pendingJobRow, 0)
38093826
for _, job := range q.provisionerJobs {
3810-
for _, id := range ids {
3811-
if id == job.ID {
3812-
// clone the Tags before appending, since maps are reference types and
3813-
// we don't want the caller to be able to mutate the map we have inside
3814-
// dbmem!
3815-
job.Tags = maps.Clone(job.Tags)
3816-
job := database.GetProvisionerJobsByIDsWithQueuePositionRow{
3817-
ProvisionerJob: job,
3818-
}
3819-
if !job.ProvisionerJob.StartedAt.Valid {
3820-
job.QueuePosition = queuePosition
3821-
}
3822-
jobs = append(jobs, job)
3823-
break
3824-
}
3825-
}
3826-
if !job.StartedAt.Valid {
3827-
queuePosition++
3827+
if job.StartedAt.Valid ||
3828+
job.CanceledAt.Valid ||
3829+
job.CompletedAt.Valid ||
3830+
job.Error.Valid {
3831+
continue
38283832
}
3833+
pendingJobs = append(pendingJobs, pendingJobRow{
3834+
ID: job.ID,
3835+
CreatedAt: job.CreatedAt,
3836+
})
38293837
}
3830-
for _, job := range jobs {
3831-
if !job.ProvisionerJob.StartedAt.Valid {
3832-
// Set it to the max position!
3833-
job.QueueSize = queuePosition
3838+
3839+
// queue_position AS (
3840+
// SELECT
3841+
// id,
3842+
// ROW_NUMBER() OVER (ORDER BY created_at ASC) AS queue_position
3843+
// FROM
3844+
// pending_jobs
3845+
// ),
3846+
slices.SortFunc(pendingJobs, func(a, b pendingJobRow) int {
3847+
c := a.CreatedAt.Compare(b.CreatedAt)
3848+
return c
3849+
})
3850+
3851+
queuePosition := make(map[uuid.UUID]int64)
3852+
for idx, pj := range pendingJobs {
3853+
queuePosition[pj.ID] = int64(idx + 1)
3854+
}
3855+
3856+
// queue_size AS (
3857+
// SELECT COUNT(*) AS count FROM pending_jobs
3858+
// ),
3859+
queueSize := len(pendingJobs)
3860+
3861+
// SELECT
3862+
// sqlc.embed(pj),
3863+
// COALESCE(qp.queue_position, 0) AS queue_position,
3864+
// COALESCE(qs.count, 0) AS queue_size
3865+
// FROM
3866+
// provisioner_jobs pj
3867+
// LEFT JOIN
3868+
// queue_position qp ON pj.id = qp.id
3869+
// LEFT JOIN
3870+
// queue_size qs ON TRUE
3871+
// WHERE
3872+
// pj.id IN (...)
3873+
jobs := make([]database.GetProvisionerJobsByIDsWithQueuePositionRow, 0)
3874+
for _, job := range q.provisionerJobs {
3875+
if !slices.Contains(ids, job.ID) {
3876+
continue
3877+
}
3878+
// clone the Tags before appending, since maps are reference types and
3879+
// we don't want the caller to be able to mutate the map we have inside
3880+
// dbmem!
3881+
job.Tags = maps.Clone(job.Tags)
3882+
job := database.GetProvisionerJobsByIDsWithQueuePositionRow{
3883+
// sqlc.embed(pj),
3884+
ProvisionerJob: job,
3885+
// COALESCE(qp.queue_position, 0) AS queue_position,
3886+
QueuePosition: queuePosition[job.ID],
3887+
// COALESCE(qs.count, 0) AS queue_size
3888+
QueueSize: int64(queueSize),
38343889
}
3890+
jobs = append(jobs, job)
38353891
}
3892+
38363893
return jobs, nil
38373894
}
38383895

@@ -8621,6 +8678,10 @@ func (q *FakeQuerier) OrganizationMembers(_ context.Context, arg database.Organi
86218678
tmp = append(tmp, database.OrganizationMembersRow{
86228679
OrganizationMember: organizationMember,
86238680
Username: user.Username,
8681+
AvatarURL: user.AvatarURL,
8682+
Name: user.Name,
8683+
Email: user.Email,
8684+
GlobalRoles: user.RBACRoles,
86248685
})
86258686
}
86268687
return tmp, nil

0 commit comments

Comments
 (0)