Skip to content

Commit 3a21c5d

Browse files
committed
Merge remote-tracking branch 'origin/main' into cj/dbcrypt
2 parents 02277a8 + 37a3b42 commit 3a21c5d

File tree

8 files changed

+188
-8
lines changed

8 files changed

+188
-8
lines changed

coderd/database/dbfake/dbfake.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6097,6 +6097,18 @@ func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.
60976097
continue
60986098
}
60996099

6100+
if !arg.LastUsedBefore.IsZero() {
6101+
if workspace.LastUsedAt.After(arg.LastUsedBefore) {
6102+
continue
6103+
}
6104+
}
6105+
6106+
if !arg.LastUsedAfter.IsZero() {
6107+
if workspace.LastUsedAt.Before(arg.LastUsedAfter) {
6108+
continue
6109+
}
6110+
}
6111+
61006112
if arg.Status != "" {
61016113
build, err := q.getLatestWorkspaceBuildByWorkspaceIDNoLock(ctx, workspace.ID)
61026114
if err != nil {

coderd/database/dbgen/dbgen.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,9 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo
317317
// Make sure when we acquire the job, we only get this one.
318318
orig.Tags[id.String()] = "true"
319319
}
320+
jobID := takeFirst(orig.ID, uuid.New())
320321
job, err := db.InsertProvisionerJob(genCtx, database.InsertProvisionerJobParams{
321-
ID: takeFirst(orig.ID, uuid.New()),
322+
ID: jobID,
322323
CreatedAt: takeFirst(orig.CreatedAt, database.Now()),
323324
UpdatedAt: takeFirst(orig.UpdatedAt, database.Now()),
324325
OrganizationID: takeFirst(orig.OrganizationID, uuid.New()),
@@ -343,7 +344,7 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo
343344

344345
if !orig.CompletedAt.Time.IsZero() || orig.Error.String != "" {
345346
err := db.UpdateProvisionerJobWithCompleteByID(genCtx, database.UpdateProvisionerJobWithCompleteByIDParams{
346-
ID: job.ID,
347+
ID: jobID,
347348
UpdatedAt: job.UpdatedAt,
348349
CompletedAt: orig.CompletedAt,
349350
Error: orig.Error,
@@ -353,14 +354,14 @@ func ProvisionerJob(t testing.TB, db database.Store, orig database.ProvisionerJo
353354
}
354355
if !orig.CanceledAt.Time.IsZero() {
355356
err := db.UpdateProvisionerJobWithCancelByID(genCtx, database.UpdateProvisionerJobWithCancelByIDParams{
356-
ID: job.ID,
357+
ID: jobID,
357358
CanceledAt: orig.CanceledAt,
358359
CompletedAt: orig.CompletedAt,
359360
})
360361
require.NoError(t, err)
361362
}
362363

363-
job, err = db.GetProvisionerJobByID(genCtx, job.ID)
364+
job, err = db.GetProvisionerJobByID(genCtx, jobID)
364365
require.NoError(t, err)
365366

366367
return job

coderd/database/modelqueries.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,11 +218,13 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
218218
arg.HasAgent,
219219
arg.AgentInactiveDisconnectTimeoutSeconds,
220220
arg.LockedAt,
221+
arg.LastUsedBefore,
222+
arg.LastUsedAfter,
221223
arg.Offset,
222224
arg.Limit,
223225
)
224226
if err != nil {
225-
return nil, xerrors.Errorf("get authorized workspaces: %w", err)
227+
return nil, err
226228
}
227229
defer rows.Close()
228230
var items []GetWorkspacesRow

coderd/database/queries.sql.go

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

coderd/database/queries/workspaces.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,17 @@ WHERE
267267
ELSE
268268
locked_at IS NULL
269269
END
270+
-- Filter by last_used
271+
AND CASE
272+
WHEN @last_used_before :: timestamp with time zone > '0001-01-01 00:00:00Z' THEN
273+
workspaces.last_used_at <= @last_used_before
274+
ELSE true
275+
END
276+
AND CASE
277+
WHEN @last_used_after :: timestamp with time zone > '0001-01-01 00:00:00Z' THEN
278+
workspaces.last_used_at >= @last_used_after
279+
ELSE true
280+
END
270281
-- Authorize Filter clause will be injected below in GetAuthorizedWorkspaces
271282
-- @authorize_filter
272283
ORDER BY

coderd/searchquery/search.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ func Workspaces(query string, page codersdk.Pagination, agentInactiveDisconnectT
115115
filter.Status = string(httpapi.ParseCustom(parser, values, "", "status", httpapi.ParseEnum[database.WorkspaceStatus]))
116116
filter.HasAgent = parser.String(values, "", "has-agent")
117117
filter.LockedAt = parser.Time(values, time.Time{}, "locked_at", "2006-01-02")
118+
filter.LastUsedAfter = parser.Time3339Nano(values, time.Time{}, "last_used_after")
119+
filter.LastUsedBefore = parser.Time3339Nano(values, time.Time{}, "last_used_before")
118120

119121
if _, ok := values["deleting_by"]; ok {
120122
postFilter.DeletingBy = ptr.Ref(parser.Time(values, time.Time{}, "deleting_by", "2006-01-02"))

coderd/workspaces_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,62 @@ func TestWorkspaceFilterManual(t *testing.T) {
14471447
require.Len(t, res.Workspaces, 1)
14481448
require.NotNil(t, res.Workspaces[0].LockedAt)
14491449
})
1450+
1451+
t.Run("LastUsed", func(t *testing.T) {
1452+
t.Parallel()
1453+
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
1454+
IncludeProvisionerDaemon: true,
1455+
})
1456+
user := coderdtest.CreateFirstUser(t, client)
1457+
authToken := uuid.NewString()
1458+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
1459+
Parse: echo.ParseComplete,
1460+
ProvisionPlan: echo.ProvisionComplete,
1461+
ProvisionApply: echo.ProvisionApplyWithAgent(authToken),
1462+
})
1463+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
1464+
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
1465+
1466+
// update template with inactivity ttl
1467+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
1468+
defer cancel()
1469+
1470+
now := database.Now()
1471+
before := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
1472+
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, before.LatestBuild.ID)
1473+
1474+
after := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
1475+
_ = coderdtest.AwaitWorkspaceBuildJob(t, client, after.LatestBuild.ID)
1476+
1477+
//nolint:gocritic // Unit testing context
1478+
err := api.Database.UpdateWorkspaceLastUsedAt(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceLastUsedAtParams{
1479+
ID: before.ID,
1480+
LastUsedAt: now.UTC().Add(time.Hour * -1),
1481+
})
1482+
require.NoError(t, err)
1483+
1484+
// Unit testing context
1485+
//nolint:gocritic // Unit testing context
1486+
err = api.Database.UpdateWorkspaceLastUsedAt(dbauthz.AsSystemRestricted(ctx), database.UpdateWorkspaceLastUsedAtParams{
1487+
ID: after.ID,
1488+
LastUsedAt: now.UTC().Add(time.Hour * 1),
1489+
})
1490+
require.NoError(t, err)
1491+
1492+
beforeRes, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
1493+
FilterQuery: fmt.Sprintf("last_used_before:%q", now.Format(time.RFC3339)),
1494+
})
1495+
require.NoError(t, err)
1496+
require.Len(t, beforeRes.Workspaces, 1)
1497+
require.Equal(t, before.ID, beforeRes.Workspaces[0].ID)
1498+
1499+
afterRes, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
1500+
FilterQuery: fmt.Sprintf("last_used_after:%q", now.Format(time.RFC3339)),
1501+
})
1502+
require.NoError(t, err)
1503+
require.Len(t, afterRes.Workspaces, 1)
1504+
require.Equal(t, after.ID, afterRes.Workspaces[0].ID)
1505+
})
14501506
}
14511507

14521508
func TestOffsetLimit(t *testing.T) {

scripts/dev-oidc.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env bash
2+
3+
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
4+
# shellcheck source=scripts/lib.sh
5+
source "${SCRIPT_DIR}/lib.sh"
6+
7+
# Allow toggling verbose output
8+
[[ -n ${VERBOSE:-} ]] && set -x
9+
set -euo pipefail
10+
11+
KEYCLOAK_VERSION="${KEYCLOAK_VERSION:-22.0}"
12+
13+
cat <<EOF >/tmp/example-realm.json
14+
{
15+
"realm": "coder",
16+
"enabled": true,
17+
"sslRequired": "none",
18+
"registrationAllowed": true,
19+
"privateKey": "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y=",
20+
"publicKey": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
21+
"requiredCredentials": ["password"],
22+
"users": [
23+
{
24+
"username": "oidcuser",
25+
"email": "oidcuser@coder.com",
26+
"emailVerified": true,
27+
"enabled": true,
28+
"credentials": [
29+
{
30+
"type": "password",
31+
"value": "password"
32+
}
33+
],
34+
"clientRoles": {
35+
"realm-management": ["realm-admin"],
36+
"account": ["manage-account"]
37+
}
38+
}
39+
],
40+
"clients": [
41+
{
42+
"clientId": "coder",
43+
"directAccessGrantsEnabled": true,
44+
"enabled": true,
45+
"fullScopeAllowed": true,
46+
"baseUrl": "/coder",
47+
"redirectUris": ["*"],
48+
"secret": "coder"
49+
}
50+
]
51+
}
52+
EOF
53+
54+
echo '== Starting Keycloak'
55+
docker rm -f keycloak || true
56+
# Start Keycloak
57+
docker run --rm -d \
58+
--name keycloak \
59+
-p 9080:8080 \
60+
-e KEYCLOAK_ADMIN=admin \
61+
-e KEYCLOAK_ADMIN_PASSWORD=password \
62+
-v /tmp/example-realm.json:/opt/keycloak/data/import/example-realm.json \
63+
"quay.io/keycloak/keycloak:${KEYCLOAK_VERSION}" \
64+
start-dev \
65+
--import-realm
66+
67+
echo '== Waiting for keycloak to become ready'
68+
# Start the timeout in the background so interrupting this script
69+
# doesn't hang for 60s.
70+
timeout 60s bash -c 'until curl -s --fail http://localhost:9080/realms/coder/.well-known/openid-configuration > /dev/null 2>&1; do sleep 0.5; done' ||
71+
fatal 'Keycloak did not become ready in time' &
72+
wait $!
73+
74+
echo '== Starting Coder'
75+
hostname=$(hostname -f)
76+
export CODER_OIDC_ISSUER_URL="http://${hostname}:9080/realms/coder"
77+
export CODER_OIDC_CLIENT_ID=coder
78+
export CODER_OIDC_CLIENT_SECRET=coder
79+
export CODER_DEV_ACCESS_URL="http://${hostname}:8080"
80+
81+
exec "${SCRIPT_DIR}/develop.sh" "$@"

0 commit comments

Comments
 (0)