Skip to content

Commit 82d4f8f

Browse files
committed
Merge remote-tracking branch 'origin/main' into coder_app-open_in
2 parents 803fe24 + 137dc6e commit 82d4f8f

File tree

152 files changed

+2036
-2294
lines changed

Some content is hidden

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

152 files changed

+2036
-2294
lines changed

Makefile

+4-1
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,10 @@ examples/examples.gen.json: scripts/examplegen/main.go examples/examples.go $(sh
657657
go run ./scripts/examplegen/main.go > examples/examples.gen.json
658658

659659
coderd/rbac/object_gen.go: scripts/typegen/rbacobject.gotmpl scripts/typegen/main.go coderd/rbac/object.go coderd/rbac/policy/policy.go
660-
go run scripts/typegen/main.go rbac object > coderd/rbac/object_gen.go
660+
tempdir=$(shell mktemp -d /tmp/typegen_rbac_object.XXXXXX)
661+
go run ./scripts/typegen/main.go rbac object > "$$tempdir/object_gen.go"
662+
mv -v "$$tempdir/object_gen.go" coderd/rbac/object_gen.go
663+
rmdir -v "$$tempdir"
661664

662665
codersdk/rbacresources_gen.go: scripts/typegen/codersdk.gotmpl scripts/typegen/main.go coderd/rbac/object.go coderd/rbac/policy/policy.go
663666
# Do no overwrite codersdk/rbacresources_gen.go directly, as it would make the file empty, breaking

agent/stats.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package agent
22

33
import (
44
"context"
5+
"maps"
56
"sync"
67
"time"
78

@@ -32,7 +33,7 @@ type statsDest interface {
3233
// statsDest (agent API in prod)
3334
type statsReporter struct {
3435
*sync.Cond
35-
networkStats *map[netlogtype.Connection]netlogtype.Counts
36+
networkStats map[netlogtype.Connection]netlogtype.Counts
3637
unreported bool
3738
lastInterval time.Duration
3839

@@ -54,8 +55,15 @@ func (s *statsReporter) callback(_, _ time.Time, virtual, _ map[netlogtype.Conne
5455
s.L.Lock()
5556
defer s.L.Unlock()
5657
s.logger.Debug(context.Background(), "got stats callback")
57-
s.networkStats = &virtual
58-
s.unreported = true
58+
// Accumulate stats until they've been reported.
59+
if s.unreported && len(s.networkStats) > 0 {
60+
for k, v := range virtual {
61+
s.networkStats[k] = s.networkStats[k].Add(v)
62+
}
63+
} else {
64+
s.networkStats = maps.Clone(virtual)
65+
s.unreported = true
66+
}
5967
s.Broadcast()
6068
}
6169

@@ -96,9 +104,8 @@ func (s *statsReporter) reportLoop(ctx context.Context, dest statsDest) error {
96104
if ctxDone {
97105
return nil
98106
}
99-
networkStats := *s.networkStats
100107
s.unreported = false
101-
if err = s.reportLocked(ctx, dest, networkStats); err != nil {
108+
if err = s.reportLocked(ctx, dest, s.networkStats); err != nil {
102109
return xerrors.Errorf("report stats: %w", err)
103110
}
104111
}

agent/stats_internal_test.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestStatsReporter(t *testing.T) {
6464
require.Equal(t, netStats, gotNetStats)
6565

6666
// while we are collecting the stats, send in two new netStats to simulate
67-
// what happens if we don't keep up. Only the latest should be kept.
67+
// what happens if we don't keep up. The stats should be accumulated.
6868
netStats0 := map[netlogtype.Connection]netlogtype.Counts{
6969
{
7070
Proto: ipproto.TCP,
@@ -102,9 +102,21 @@ func TestStatsReporter(t *testing.T) {
102102
require.Equal(t, stats, update.Stats)
103103
testutil.RequireSendCtx(ctx, t, fDest.resps, &proto.UpdateStatsResponse{ReportInterval: durationpb.New(interval)})
104104

105-
// second update -- only netStats1 is reported
105+
// second update -- netStat0 and netStats1 are accumulated and reported
106+
wantNetStats := map[netlogtype.Connection]netlogtype.Counts{
107+
{
108+
Proto: ipproto.TCP,
109+
Src: netip.MustParseAddrPort("192.168.1.33:4887"),
110+
Dst: netip.MustParseAddrPort("192.168.2.99:9999"),
111+
}: {
112+
TxPackets: 21,
113+
TxBytes: 21,
114+
RxPackets: 21,
115+
RxBytes: 21,
116+
},
117+
}
106118
gotNetStats = testutil.RequireRecvCtx(ctx, t, fCollector.calls)
107-
require.Equal(t, netStats1, gotNetStats)
119+
require.Equal(t, wantNetStats, gotNetStats)
108120
stats = &proto.Stats{SessionCountJetbrains: 66}
109121
testutil.RequireSendCtx(ctx, t, fCollector.stats, stats)
110122
update = testutil.RequireRecvCtx(ctx, t, fDest.reqs)

cli/clitest/golden.go

+25-15
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ func TestGoldenFile(t *testing.T, fileName string, actual []byte, replacements m
128128
// equality check.
129129
func normalizeGoldenFile(t *testing.T, byt []byte) []byte {
130130
// Replace any timestamps with a placeholder.
131-
byt = timestampRegex.ReplaceAll(byt, []byte("[timestamp]"))
131+
byt = timestampRegex.ReplaceAll(byt, []byte(pad("[timestamp]", 20)))
132132

133133
homeDir, err := os.UserHomeDir()
134134
require.NoError(t, err)
@@ -202,21 +202,31 @@ func prepareTestData(t *testing.T) (*codersdk.Client, map[string]string) {
202202
workspaceBuild := coderdtest.AwaitWorkspaceBuildJobCompleted(t, rootClient, workspace.LatestBuild.ID)
203203

204204
replacements := map[string]string{
205-
firstUser.UserID.String(): "[first user ID]",
206-
secondUser.ID.String(): "[second user ID]",
207-
firstUser.OrganizationID.String(): "[first org ID]",
208-
version.ID.String(): "[version ID]",
209-
version.Name: "[version name]",
210-
version.Job.ID.String(): "[version job ID]",
211-
version.Job.FileID.String(): "[version file ID]",
212-
version.Job.WorkerID.String(): "[version worker ID]",
213-
template.ID.String(): "[template ID]",
214-
workspace.ID.String(): "[workspace ID]",
215-
workspaceBuild.ID.String(): "[workspace build ID]",
216-
workspaceBuild.Job.ID.String(): "[workspace build job ID]",
217-
workspaceBuild.Job.FileID.String(): "[workspace build file ID]",
218-
workspaceBuild.Job.WorkerID.String(): "[workspace build worker ID]",
205+
firstUser.UserID.String(): pad("[first user ID]", 36),
206+
secondUser.ID.String(): pad("[second user ID]", 36),
207+
firstUser.OrganizationID.String(): pad("[first org ID]", 36),
208+
version.ID.String(): pad("[version ID]", 36),
209+
version.Name: pad("[version name]", 36),
210+
version.Job.ID.String(): pad("[version job ID]", 36),
211+
version.Job.FileID.String(): pad("[version file ID]", 36),
212+
version.Job.WorkerID.String(): pad("[version worker ID]", 36),
213+
template.ID.String(): pad("[template ID]", 36),
214+
workspace.ID.String(): pad("[workspace ID]", 36),
215+
workspaceBuild.ID.String(): pad("[workspace build ID]", 36),
216+
workspaceBuild.Job.ID.String(): pad("[workspace build job ID]", 36),
217+
workspaceBuild.Job.FileID.String(): pad("[workspace build file ID]", 36),
218+
workspaceBuild.Job.WorkerID.String(): pad("[workspace build worker ID]", 36),
219219
}
220220

221221
return rootClient, replacements
222222
}
223+
224+
func pad(s string, n int) string {
225+
if len(s) >= n {
226+
return s
227+
}
228+
n -= len(s)
229+
pre := n / 2
230+
post := n - pre
231+
return strings.Repeat("=", pre) + s + strings.Repeat("=", post)
232+
}

cli/exp_scaletest.go

+37-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"math/rand"
1111
"net/http"
12+
"net/url"
1213
"os"
1314
"os/signal"
1415
"strconv"
@@ -860,13 +861,14 @@ func (r *RootCmd) scaletestCreateWorkspaces() *serpent.Command {
860861

861862
func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
862863
var (
863-
tickInterval time.Duration
864-
bytesPerTick int64
865-
ssh bool
866-
useHostLogin bool
867-
app string
868-
template string
869-
targetWorkspaces string
864+
tickInterval time.Duration
865+
bytesPerTick int64
866+
ssh bool
867+
useHostLogin bool
868+
app string
869+
template string
870+
targetWorkspaces string
871+
workspaceProxyURL string
870872

871873
client = &codersdk.Client{}
872874
tracingFlags = &scaletestTracingFlags{}
@@ -1002,6 +1004,23 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
10021004
return xerrors.Errorf("configure workspace app: %w", err)
10031005
}
10041006

1007+
var webClient *codersdk.Client
1008+
if workspaceProxyURL != "" {
1009+
u, err := url.Parse(workspaceProxyURL)
1010+
if err != nil {
1011+
return xerrors.Errorf("parse workspace proxy URL: %w", err)
1012+
}
1013+
1014+
webClient = codersdk.New(u)
1015+
webClient.HTTPClient = client.HTTPClient
1016+
webClient.SetSessionToken(client.SessionToken())
1017+
1018+
appConfig, err = createWorkspaceAppConfig(webClient, appHost.Host, app, ws, agent)
1019+
if err != nil {
1020+
return xerrors.Errorf("configure proxy workspace app: %w", err)
1021+
}
1022+
}
1023+
10051024
// Setup our workspace agent connection.
10061025
config := workspacetraffic.Config{
10071026
AgentID: agent.ID,
@@ -1015,6 +1034,10 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
10151034
App: appConfig,
10161035
}
10171036

1037+
if webClient != nil {
1038+
config.WebClient = webClient
1039+
}
1040+
10181041
if err := config.Validate(); err != nil {
10191042
return xerrors.Errorf("validate config: %w", err)
10201043
}
@@ -1108,6 +1131,13 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
11081131
Description: "Connect as the currently logged in user.",
11091132
Value: serpent.BoolOf(&useHostLogin),
11101133
},
1134+
{
1135+
Flag: "workspace-proxy-url",
1136+
Env: "CODER_SCALETEST_WORKSPACE_PROXY_URL",
1137+
Default: "",
1138+
Description: "URL for workspace proxy to send web traffic to.",
1139+
Value: serpent.StringOf(&workspaceProxyURL),
1140+
},
11111141
}
11121142

11131143
tracingFlags.attach(&cmd.Options)

cli/schedule.go

+14-13
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ When enabling scheduled stop, enter a duration in one of the following formats:
4646
* 2m (2 minutes)
4747
* 2 (2 minutes)
4848
`
49-
scheduleOverrideDescriptionLong = `
49+
scheduleExtendDescriptionLong = `
5050
* The new stop time is calculated from *now*.
5151
* The new stop time must be at least 30 minutes in the future.
5252
* The workspace template may restrict the maximum workspace runtime.
@@ -56,7 +56,7 @@ When enabling scheduled stop, enter a duration in one of the following formats:
5656
func (r *RootCmd) schedules() *serpent.Command {
5757
scheduleCmd := &serpent.Command{
5858
Annotations: workspaceCommand,
59-
Use: "schedule { show | start | stop | override } <workspace>",
59+
Use: "schedule { show | start | stop | extend } <workspace>",
6060
Short: "Schedule automated start and stop times for workspaces",
6161
Handler: func(inv *serpent.Invocation) error {
6262
return inv.Command.HelpHandler(inv)
@@ -65,7 +65,7 @@ func (r *RootCmd) schedules() *serpent.Command {
6565
r.scheduleShow(),
6666
r.scheduleStart(),
6767
r.scheduleStop(),
68-
r.scheduleOverride(),
68+
r.scheduleExtend(),
6969
},
7070
}
7171

@@ -229,22 +229,23 @@ func (r *RootCmd) scheduleStop() *serpent.Command {
229229
}
230230
}
231231

232-
func (r *RootCmd) scheduleOverride() *serpent.Command {
232+
func (r *RootCmd) scheduleExtend() *serpent.Command {
233233
client := new(codersdk.Client)
234-
overrideCmd := &serpent.Command{
235-
Use: "override-stop <workspace-name> <duration from now>",
236-
Short: "Override the stop time of a currently running workspace instance.",
237-
Long: scheduleOverrideDescriptionLong + "\n" + FormatExamples(
234+
extendCmd := &serpent.Command{
235+
Use: "extend <workspace-name> <duration from now>",
236+
Aliases: []string{"override-stop"},
237+
Short: "Extend the stop time of a currently running workspace instance.",
238+
Long: scheduleExtendDescriptionLong + "\n" + FormatExamples(
238239
Example{
239-
Command: "coder schedule override-stop my-workspace 90m",
240+
Command: "coder schedule extend my-workspace 90m",
240241
},
241242
),
242243
Middleware: serpent.Chain(
243244
serpent.RequireNArgs(2),
244245
r.InitClient(client),
245246
),
246247
Handler: func(inv *serpent.Invocation) error {
247-
overrideDuration, err := parseDuration(inv.Args[1])
248+
extendDuration, err := parseDuration(inv.Args[1])
248249
if err != nil {
249250
return err
250251
}
@@ -259,15 +260,15 @@ func (r *RootCmd) scheduleOverride() *serpent.Command {
259260
loc = time.UTC // best effort
260261
}
261262

262-
if overrideDuration < 29*time.Minute {
263+
if extendDuration < 29*time.Minute {
263264
_, _ = fmt.Fprintf(
264265
inv.Stdout,
265266
"Please specify a duration of at least 30 minutes.\n",
266267
)
267268
return nil
268269
}
269270

270-
newDeadline := time.Now().In(loc).Add(overrideDuration)
271+
newDeadline := time.Now().In(loc).Add(extendDuration)
271272
if err := client.PutExtendWorkspace(inv.Context(), workspace.ID, codersdk.PutExtendWorkspaceRequest{
272273
Deadline: newDeadline,
273274
}); err != nil {
@@ -281,7 +282,7 @@ func (r *RootCmd) scheduleOverride() *serpent.Command {
281282
return displaySchedule(updated, inv.Stdout)
282283
},
283284
}
284-
return overrideCmd
285+
return extendCmd
285286
}
286287

287288
func displaySchedule(ws codersdk.Workspace, out io.Writer) error {

cli/schedule_test.go

+42-28
Original file line numberDiff line numberDiff line change
@@ -332,32 +332,46 @@ func TestScheduleModify(t *testing.T) {
332332

333333
//nolint:paralleltest // t.Setenv
334334
func TestScheduleOverride(t *testing.T) {
335-
// Given
336-
// Set timezone to Asia/Kolkata to surface any timezone-related bugs.
337-
t.Setenv("TZ", "Asia/Kolkata")
338-
loc, err := tz.TimezoneIANA()
339-
require.NoError(t, err)
340-
require.Equal(t, "Asia/Kolkata", loc.String())
341-
sched, err := cron.Weekly("CRON_TZ=Europe/Dublin 30 7 * * Mon-Fri")
342-
require.NoError(t, err, "invalid schedule")
343-
ownerClient, _, _, ws := setupTestSchedule(t, sched)
344-
now := time.Now()
345-
// To avoid the likelihood of time-related flakes, only matching up to the hour.
346-
expectedDeadline := time.Now().In(loc).Add(10 * time.Hour).Format("2006-01-02T15:")
347-
348-
// When: we override the stop schedule
349-
inv, root := clitest.New(t,
350-
"schedule", "override-stop", ws[0].OwnerName+"/"+ws[0].Name, "10h",
351-
)
352-
353-
clitest.SetupConfig(t, ownerClient, root)
354-
pty := ptytest.New(t).Attach(inv)
355-
require.NoError(t, inv.Run())
356-
357-
// Then: the updated schedule should be shown
358-
pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name)
359-
pty.ExpectMatch(sched.Humanize())
360-
pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339))
361-
pty.ExpectMatch("8h")
362-
pty.ExpectMatch(expectedDeadline)
335+
tests := []struct {
336+
command string
337+
}{
338+
{command: "extend"},
339+
// test for backwards compatibility
340+
{command: "override-stop"},
341+
}
342+
343+
for _, tt := range tests {
344+
tt := tt
345+
346+
t.Run(tt.command, func(t *testing.T) {
347+
// Given
348+
// Set timezone to Asia/Kolkata to surface any timezone-related bugs.
349+
t.Setenv("TZ", "Asia/Kolkata")
350+
loc, err := tz.TimezoneIANA()
351+
require.NoError(t, err)
352+
require.Equal(t, "Asia/Kolkata", loc.String())
353+
sched, err := cron.Weekly("CRON_TZ=Europe/Dublin 30 7 * * Mon-Fri")
354+
require.NoError(t, err, "invalid schedule")
355+
ownerClient, _, _, ws := setupTestSchedule(t, sched)
356+
now := time.Now()
357+
// To avoid the likelihood of time-related flakes, only matching up to the hour.
358+
expectedDeadline := time.Now().In(loc).Add(10 * time.Hour).Format("2006-01-02T15:")
359+
360+
// When: we override the stop schedule
361+
inv, root := clitest.New(t,
362+
"schedule", tt.command, ws[0].OwnerName+"/"+ws[0].Name, "10h",
363+
)
364+
365+
clitest.SetupConfig(t, ownerClient, root)
366+
pty := ptytest.New(t).Attach(inv)
367+
require.NoError(t, inv.Run())
368+
369+
// Then: the updated schedule should be shown
370+
pty.ExpectMatch(ws[0].OwnerName + "/" + ws[0].Name)
371+
pty.ExpectMatch(sched.Humanize())
372+
pty.ExpectMatch(sched.Next(now).In(loc).Format(time.RFC3339))
373+
pty.ExpectMatch("8h")
374+
pty.ExpectMatch(expectedDeadline)
375+
})
376+
}
363377
}

0 commit comments

Comments
 (0)