Skip to content

Commit 0903742

Browse files
authored
Merge branch 'main' into restructure-new
2 parents cf5bc72 + 0ef8514 commit 0903742

40 files changed

+2008
-392
lines changed

cli/server.go

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ import (
5555

5656
"cdr.dev/slog"
5757
"cdr.dev/slog/sloggers/sloghuman"
58+
"github.com/coder/coder/v2/coderd/entitlements"
5859
"github.com/coder/pretty"
5960
"github.com/coder/quartz"
6061
"github.com/coder/retry"
@@ -605,6 +606,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
605606
SSHConfigOptions: configSSHOptions,
606607
},
607608
AllowWorkspaceRenames: vals.AllowWorkspaceRenames.Value(),
609+
Entitlements: entitlements.New(),
608610
NotificationsEnqueuer: notifications.NewNoopEnqueuer(), // Changed further down if notifications enabled.
609611
}
610612
if httpServers.TLSConfig != nil {

cli/testdata/coder_server_--help.golden

+13
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,11 @@ OIDC OPTIONS:
433433
groups. This filter is applied after the group mapping and before the
434434
regex filter.
435435

436+
--oidc-organization-assign-default bool, $CODER_OIDC_ORGANIZATION_ASSIGN_DEFAULT (default: true)
437+
If set to true, users will always be added to the default
438+
organization. If organization sync is enabled, then the default org is
439+
always added to the user's set of expectedorganizations.
440+
436441
--oidc-auth-url-params struct[map[string]string], $CODER_OIDC_AUTH_URL_PARAMS (default: {"access_type": "offline"})
437442
OIDC auth URL parameters to pass to the upstream provider.
438443

@@ -479,6 +484,14 @@ OIDC OPTIONS:
479484
--oidc-name-field string, $CODER_OIDC_NAME_FIELD (default: name)
480485
OIDC claim field to use as the name.
481486

487+
--oidc-organization-field string, $CODER_OIDC_ORGANIZATION_FIELD
488+
This field must be set if using the organization sync feature. Set to
489+
the claim to be used for organizations.
490+
491+
--oidc-organization-mapping struct[map[string][]uuid.UUID], $CODER_OIDC_ORGANIZATION_MAPPING (default: {})
492+
A map of OIDC claims and the organizations in Coder it should map to.
493+
This is required because organization IDs must be used within Coder.
494+
482495
--oidc-group-regex-filter regexp, $CODER_OIDC_GROUP_REGEX_FILTER (default: .*)
483496
If provided any group name not matching the regex is ignored. This
484497
allows for filtering out groups that are not needed. This filter is

cli/testdata/server-config.yaml.golden

+13-3
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,19 @@ oidc:
319319
# Ignore the userinfo endpoint and only use the ID token for user information.
320320
# (default: false, type: bool)
321321
ignoreUserInfo: false
322+
# This field must be set if using the organization sync feature. Set to the claim
323+
# to be used for organizations.
324+
# (default: <unset>, type: string)
325+
organizationField: ""
326+
# If set to true, users will always be added to the default organization. If
327+
# organization sync is enabled, then the default org is always added to the user's
328+
# set of expectedorganizations.
329+
# (default: true, type: bool)
330+
organizationAssignDefault: true
331+
# A map of OIDC claims and the organizations in Coder it should map to. This is
332+
# required because organization IDs must be used within Coder.
333+
# (default: {}, type: struct[map[string][]uuid.UUID])
334+
organizationMapping: {}
322335
# This field must be set if using the group sync feature and the scope name is not
323336
# 'groups'. Set to the claim to be used for groups.
324337
# (default: <unset>, type: string)
@@ -529,9 +542,6 @@ notifications:
529542
# Username to use with PLAIN/LOGIN authentication.
530543
# (default: <unset>, type: string)
531544
username: ""
532-
# Password to use with PLAIN/LOGIN authentication.
533-
# (default: <unset>, type: string)
534-
password: ""
535545
# File from which to load password for use with PLAIN/LOGIN authentication.
536546
# (default: <unset>, type: string)
537547
passwordFile: ""

coderd/apidoc/docs.go

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

+11
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import (
3838

3939
"cdr.dev/slog"
4040
"github.com/coder/coder/v2/coderd/entitlements"
41+
"github.com/coder/coder/v2/coderd/idpsync"
4142
"github.com/coder/quartz"
4243
"github.com/coder/serpent"
4344

@@ -243,6 +244,9 @@ type Options struct {
243244
WorkspaceUsageTracker *workspacestats.UsageTracker
244245
// NotificationsEnqueuer handles enqueueing notifications for delivery by SMTP, webhook, etc.
245246
NotificationsEnqueuer notifications.Enqueuer
247+
248+
// IDPSync holds all configured values for syncing external IDP users into Coder.
249+
IDPSync idpsync.IDPSync
246250
}
247251

248252
// @title Coder API
@@ -270,6 +274,13 @@ func New(options *Options) *API {
270274
if options.Entitlements == nil {
271275
options.Entitlements = entitlements.New()
272276
}
277+
if options.IDPSync == nil {
278+
options.IDPSync = idpsync.NewAGPLSync(options.Logger, idpsync.SyncSettings{
279+
OrganizationField: options.DeploymentValues.OIDC.OrganizationField.Value(),
280+
OrganizationMapping: options.DeploymentValues.OIDC.OrganizationMapping.Value,
281+
OrganizationAssignDefault: options.DeploymentValues.OIDC.OrganizationAssignDefault.Value(),
282+
})
283+
}
273284
if options.NewTicker == nil {
274285
options.NewTicker = func(duration time.Duration) (tick <-chan time.Time, done func()) {
275286
ticker := time.NewTicker(duration)

coderd/database/dbauthz/dbauthz.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ var (
243243
rbac.ResourceAssignOrgRole.Type: rbac.ResourceAssignOrgRole.AvailableActions(),
244244
rbac.ResourceSystem.Type: {policy.WildcardSymbol},
245245
rbac.ResourceOrganization.Type: {policy.ActionCreate, policy.ActionRead},
246-
rbac.ResourceOrganizationMember.Type: {policy.ActionCreate},
246+
rbac.ResourceOrganizationMember.Type: {policy.ActionCreate, policy.ActionDelete, policy.ActionRead},
247247
rbac.ResourceProvisionerDaemon.Type: {policy.ActionCreate, policy.ActionUpdate},
248248
rbac.ResourceProvisionerKeys.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
249249
rbac.ResourceUser.Type: rbac.ResourceUser.AvailableActions(),

coderd/database/dbmem/dbmem.go

+81-10
Original file line numberDiff line numberDiff line change
@@ -1710,19 +1710,90 @@ func (q *FakeQuerier) DeleteOldWorkspaceAgentLogs(_ context.Context, threshold t
17101710
q.mutex.Lock()
17111711
defer q.mutex.Unlock()
17121712

1713-
var validLogs []database.WorkspaceAgentLog
1714-
for _, log := range q.workspaceAgentLogs {
1715-
var toBeDeleted bool
1716-
for _, agent := range q.workspaceAgents {
1717-
if agent.ID == log.AgentID && agent.LastConnectedAt.Valid && agent.LastConnectedAt.Time.Before(threshold) {
1718-
toBeDeleted = true
1719-
break
1720-
}
1713+
/*
1714+
WITH
1715+
latest_builds AS (
1716+
SELECT
1717+
workspace_id, max(build_number) AS max_build_number
1718+
FROM
1719+
workspace_builds
1720+
GROUP BY
1721+
workspace_id
1722+
),
1723+
*/
1724+
latestBuilds := make(map[uuid.UUID]int32)
1725+
for _, wb := range q.workspaceBuilds {
1726+
if lastBuildNumber, found := latestBuilds[wb.WorkspaceID]; found && lastBuildNumber > wb.BuildNumber {
1727+
continue
17211728
}
1729+
// not found or newer build number
1730+
latestBuilds[wb.WorkspaceID] = wb.BuildNumber
1731+
}
17221732

1723-
if !toBeDeleted {
1724-
validLogs = append(validLogs, log)
1733+
/*
1734+
old_agents AS (
1735+
SELECT
1736+
wa.id
1737+
FROM
1738+
workspace_agents AS wa
1739+
JOIN
1740+
workspace_resources AS wr
1741+
ON
1742+
wa.resource_id = wr.id
1743+
JOIN
1744+
workspace_builds AS wb
1745+
ON
1746+
wb.job_id = wr.job_id
1747+
LEFT JOIN
1748+
latest_builds
1749+
ON
1750+
latest_builds.workspace_id = wb.workspace_id
1751+
AND
1752+
latest_builds.max_build_number = wb.build_number
1753+
WHERE
1754+
-- Filter out the latest builds for each workspace.
1755+
latest_builds.workspace_id IS NULL
1756+
AND CASE
1757+
-- If the last time the agent connected was before @threshold
1758+
WHEN wa.last_connected_at IS NOT NULL THEN
1759+
wa.last_connected_at < @threshold :: timestamptz
1760+
-- The agent never connected, and was created before @threshold
1761+
ELSE wa.created_at < @threshold :: timestamptz
1762+
END
1763+
)
1764+
*/
1765+
oldAgents := make(map[uuid.UUID]struct{})
1766+
for _, wa := range q.workspaceAgents {
1767+
for _, wr := range q.workspaceResources {
1768+
if wr.ID != wa.ResourceID {
1769+
continue
1770+
}
1771+
for _, wb := range q.workspaceBuilds {
1772+
if wb.JobID != wr.JobID {
1773+
continue
1774+
}
1775+
latestBuildNumber, found := latestBuilds[wb.WorkspaceID]
1776+
if !found {
1777+
panic("workspaceBuilds got modified somehow while q was locked! This is a bug in dbmem!")
1778+
}
1779+
if latestBuildNumber == wb.BuildNumber {
1780+
continue
1781+
}
1782+
if wa.LastConnectedAt.Valid && wa.LastConnectedAt.Time.Before(threshold) || wa.CreatedAt.Before(threshold) {
1783+
oldAgents[wa.ID] = struct{}{}
1784+
}
1785+
}
1786+
}
1787+
}
1788+
/*
1789+
DELETE FROM workspace_agent_logs WHERE agent_id IN (SELECT id FROM old_agents);
1790+
*/
1791+
var validLogs []database.WorkspaceAgentLog
1792+
for _, log := range q.workspaceAgentLogs {
1793+
if _, found := oldAgents[log.AgentID]; found {
1794+
continue
17251795
}
1796+
validLogs = append(validLogs, log)
17261797
}
17271798
q.workspaceAgentLogs = validLogs
17281799
return nil

coderd/database/dbpurge/dbpurge.go

+8-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/coder/coder/v2/coderd/database"
1313
"github.com/coder/coder/v2/coderd/database/dbauthz"
14+
"github.com/coder/coder/v2/coderd/database/dbtime"
1415
"github.com/coder/quartz"
1516
)
1617

@@ -30,7 +31,8 @@ func New(ctx context.Context, logger slog.Logger, db database.Store, clk quartz.
3031
//nolint:gocritic // The system purges old db records without user input.
3132
ctx = dbauthz.AsSystemRestricted(ctx)
3233

33-
ticker := clk.NewTicker(time.Nanosecond)
34+
// Start the ticker with the initial delay.
35+
ticker := clk.NewTicker(delay)
3436
doTick := func(start time.Time) {
3537
defer ticker.Reset(delay)
3638
// Start a transaction to grab advisory lock, we don't want to run
@@ -47,7 +49,8 @@ func New(ctx context.Context, logger slog.Logger, db database.Store, clk quartz.
4749
return nil
4850
}
4951

50-
if err := tx.DeleteOldWorkspaceAgentLogs(ctx, start.Add(-maxAgentLogAge)); err != nil {
52+
deleteOldWorkspaceAgentLogsBefore := start.Add(-maxAgentLogAge)
53+
if err := tx.DeleteOldWorkspaceAgentLogs(ctx, deleteOldWorkspaceAgentLogsBefore); err != nil {
5154
return xerrors.Errorf("failed to delete old workspace agent logs: %w", err)
5255
}
5356
if err := tx.DeleteOldWorkspaceAgentStats(ctx); err != nil {
@@ -72,13 +75,15 @@ func New(ctx context.Context, logger slog.Logger, db database.Store, clk quartz.
7275
go func() {
7376
defer close(closed)
7477
defer ticker.Stop()
78+
// Force an initial tick.
79+
doTick(dbtime.Time(clk.Now()).UTC())
7580
for {
7681
select {
7782
case <-ctx.Done():
7883
return
7984
case tick := <-ticker.C:
8085
ticker.Stop()
81-
doTick(tick)
86+
doTick(dbtime.Time(tick).UTC())
8287
}
8388
}
8489
}()

0 commit comments

Comments
 (0)