Skip to content

Commit 95ac736

Browse files
committed
fixup! feat: add audit logs for dormancy events
1 parent cd77405 commit 95ac736

File tree

3 files changed

+54
-73
lines changed

3 files changed

+54
-73
lines changed

enterprise/cli/server.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/coder/coder/v2/enterprise/dbcrypt"
2424
"github.com/coder/coder/v2/enterprise/trialer"
2525
"github.com/coder/coder/v2/tailnet"
26+
"github.com/coder/quartz"
2627
"github.com/coder/serpent"
2728

2829
agplcoderd "github.com/coder/coder/v2/coderd"
@@ -95,7 +96,7 @@ func (r *RootCmd) Server(_ func()) *serpent.Command {
9596
DefaultQuietHoursSchedule: options.DeploymentValues.UserQuietHoursSchedule.DefaultSchedule.Value(),
9697
ProvisionerDaemonPSK: options.DeploymentValues.Provisioner.DaemonPSK.Value(),
9798

98-
CheckInactiveUsersCancelFunc: dormancy.CheckInactiveUsers(ctx, options.Logger, options.Database, options.Auditor),
99+
CheckInactiveUsersCancelFunc: dormancy.CheckInactiveUsers(ctx, options.Logger, quartz.NewReal(), options.Database, options.Auditor),
99100
}
100101

101102
if encKeys := options.DeploymentValues.ExternalTokenEncryptionKeys.Value(); len(encKeys) != 0 {

enterprise/coderd/dormancy/dormantusersjob.go

Lines changed: 31 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package dormancy
33
import (
44
"context"
55
"database/sql"
6-
"encoding/json"
76
"net/http"
87
"time"
98

@@ -14,6 +13,7 @@ import (
1413
"github.com/coder/coder/v2/coderd/audit"
1514
"github.com/coder/coder/v2/coderd/database"
1615
"github.com/coder/coder/v2/coderd/database/dbtime"
16+
"github.com/coder/quartz"
1717
)
1818

1919
const (
@@ -25,71 +25,49 @@ const (
2525

2626
// CheckInactiveUsers function updates status of inactive users from active to dormant
2727
// using default parameters.
28-
func CheckInactiveUsers(ctx context.Context, logger slog.Logger, db database.Store, auditor audit.Auditor) func() {
29-
return CheckInactiveUsersWithOptions(ctx, logger, db, auditor, jobInterval, accountDormancyPeriod)
28+
func CheckInactiveUsers(ctx context.Context, logger slog.Logger, clk quartz.Clock, db database.Store, auditor audit.Auditor) func() {
29+
return CheckInactiveUsersWithOptions(ctx, logger, clk, db, auditor, jobInterval, accountDormancyPeriod)
3030
}
3131

3232
// CheckInactiveUsersWithOptions function updates status of inactive users from active to dormant
3333
// using provided parameters.
34-
func CheckInactiveUsersWithOptions(ctx context.Context, logger slog.Logger, db database.Store, auditor audit.Auditor, checkInterval, dormancyPeriod time.Duration) func() {
34+
func CheckInactiveUsersWithOptions(ctx context.Context, logger slog.Logger, clk quartz.Clock, db database.Store, auditor audit.Auditor, checkInterval, dormancyPeriod time.Duration) func() {
3535
logger = logger.Named("dormancy")
3636

3737
ctx, cancelFunc := context.WithCancel(ctx)
38-
done := make(chan struct{})
39-
ticker := time.NewTicker(checkInterval)
40-
go func() {
41-
defer close(done)
42-
defer ticker.Stop()
43-
for {
44-
select {
45-
case <-ctx.Done():
46-
return
47-
case <-ticker.C:
48-
}
38+
ticker := clk.TickerFunc(ctx, checkInterval, func() error {
39+
startTime := time.Now()
40+
lastSeenAfter := dbtime.Now().Add(-dormancyPeriod)
41+
logger.Debug(ctx, "check inactive user accounts", slog.F("dormancy_period", dormancyPeriod), slog.F("last_seen_after", lastSeenAfter))
4942

50-
startTime := time.Now()
51-
lastSeenAfter := dbtime.Now().Add(-dormancyPeriod)
52-
logger.Debug(ctx, "check inactive user accounts", slog.F("dormancy_period", dormancyPeriod), slog.F("last_seen_after", lastSeenAfter))
43+
updatedUsers, err := db.UpdateInactiveUsersToDormant(ctx, database.UpdateInactiveUsersToDormantParams{
44+
LastSeenAfter: lastSeenAfter,
45+
UpdatedAt: dbtime.Now(),
46+
})
47+
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
48+
logger.Error(ctx, "can't mark inactive users as dormant", slog.Error(err))
49+
return nil
50+
}
5351

54-
updatedUsers, err := db.UpdateInactiveUsersToDormant(ctx, database.UpdateInactiveUsersToDormantParams{
55-
LastSeenAfter: lastSeenAfter,
56-
UpdatedAt: dbtime.Now(),
52+
for _, u := range updatedUsers {
53+
logger.Info(ctx, "account has been marked as dormant", slog.F("email", u.Email), slog.F("last_seen_at", u.LastSeenAt))
54+
audit.BackgroundAudit(ctx, &audit.BackgroundAuditParams[database.User]{
55+
Audit: auditor,
56+
Log: logger,
57+
UserID: u.ID,
58+
Action: database.AuditActionWrite,
59+
Old: database.User{ID: u.ID, Username: u.Username, Status: database.UserStatusActive},
60+
New: database.User{ID: u.ID, Username: u.Username, Status: database.UserStatusDormant},
61+
Status: http.StatusOK,
62+
AdditionalFields: audit.BackgroundTaskFields(ctx, logger, audit.BackgroundSubsystemDormancy),
5763
})
58-
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
59-
logger.Error(ctx, "can't mark inactive users as dormant", slog.Error(err))
60-
continue
61-
}
62-
63-
af := map[string]string{
64-
"automatic_actor": "coder",
65-
"automatic_subsystem": "dormancy",
66-
}
67-
68-
wriBytes, err := json.Marshal(af)
69-
if err != nil {
70-
logger.Error(ctx, "marshal additional fields", slog.Error(err))
71-
wriBytes = []byte("{}")
72-
}
73-
74-
for _, u := range updatedUsers {
75-
logger.Info(ctx, "account has been marked as dormant", slog.F("email", u.Email), slog.F("last_seen_at", u.LastSeenAt))
76-
audit.BackgroundAudit(ctx, &audit.BackgroundAuditParams[database.User]{
77-
Audit: auditor,
78-
Log: logger,
79-
UserID: u.ID,
80-
Action: database.AuditActionWrite,
81-
Old: database.User{ID: u.ID, Username: u.Username, Status: database.UserStatusActive},
82-
New: database.User{ID: u.ID, Username: u.Username, Status: database.UserStatusDormant},
83-
Status: http.StatusOK,
84-
AdditionalFields: wriBytes,
85-
})
86-
}
87-
logger.Debug(ctx, "checking user accounts is done", slog.F("num_dormant_accounts", len(updatedUsers)), slog.F("execution_time", time.Since(startTime)))
8864
}
89-
}()
65+
logger.Debug(ctx, "checking user accounts is done", slog.F("num_dormant_accounts", len(updatedUsers)), slog.F("execution_time", time.Since(startTime)))
66+
return nil
67+
})
9068

9169
return func() {
9270
cancelFunc()
93-
<-done
71+
ticker.Wait()
9472
}
9573
}

enterprise/coderd/dormancy/dormantusersjob_test.go

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
"github.com/coder/coder/v2/coderd/database"
1515
"github.com/coder/coder/v2/coderd/database/dbmem"
1616
"github.com/coder/coder/v2/enterprise/coderd/dormancy"
17-
"github.com/coder/coder/v2/testutil"
17+
"github.com/coder/quartz"
1818
)
1919

2020
func TestCheckInactiveUsers(t *testing.T) {
@@ -44,29 +44,31 @@ func TestCheckInactiveUsers(t *testing.T) {
4444
suspendedUser3 := setupUser(ctx, t, db, "suspended-user-3@coder.com", database.UserStatusSuspended, time.Now().Add(-dormancyPeriod).Add(-6*time.Hour))
4545

4646
mAudit := audit.NewMock()
47+
mClock := quartz.NewMock(t)
4748
// Run the periodic job
48-
closeFunc := dormancy.CheckInactiveUsersWithOptions(ctx, logger, db, mAudit, interval, dormancyPeriod)
49+
closeFunc := dormancy.CheckInactiveUsersWithOptions(ctx, logger, mClock, db, mAudit, interval, dormancyPeriod)
4950
t.Cleanup(closeFunc)
5051

51-
var rows []database.GetUsersRow
52-
var err error
53-
require.Eventually(t, func() bool {
54-
rows, err = db.GetUsers(ctx, database.GetUsersParams{})
55-
if err != nil {
56-
return false
57-
}
52+
dur, w := mClock.AdvanceNext()
53+
require.Equal(t, interval, dur)
54+
w.MustWait(ctx)
55+
56+
rows, err := db.GetUsers(ctx, database.GetUsersParams{})
57+
require.NoError(t, err)
5858

59-
var dormant, suspended int
60-
for _, row := range rows {
61-
if row.Status == database.UserStatusDormant {
62-
dormant++
63-
} else if row.Status == database.UserStatusSuspended {
64-
suspended++
65-
}
59+
var dormant, suspended int
60+
for _, row := range rows {
61+
if row.Status == database.UserStatusDormant {
62+
dormant++
63+
} else if row.Status == database.UserStatusSuspended {
64+
suspended++
6665
}
67-
// 6 users in total, 3 dormant, 3 suspended
68-
return len(rows) == 9 && dormant == 3 && suspended == 3
69-
}, testutil.WaitShort, testutil.IntervalMedium)
66+
}
67+
68+
// 9 users in total, 3 active, 3 dormant, 3 suspended
69+
require.Len(t, rows, 9)
70+
require.Equal(t, 3, dormant)
71+
require.Equal(t, 3, suspended)
7072

7173
require.Len(t, mAudit.AuditLogs(), 3)
7274

0 commit comments

Comments
 (0)