Skip to content

Commit 1d107d8

Browse files
committed
Merge branch 'main' into add_license_settings
2 parents eaba405 + 6f06f8d commit 1d107d8

File tree

181 files changed

+8969
-2616
lines changed

Some content is hidden

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

181 files changed

+8969
-2616
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ docs/admin/audit-logs.md: scripts/auditdocgen/main.go enterprise/audit/table.go
515515
cd site
516516
yarn run format:write:only ../docs/admin/audit-logs.md
517517

518-
coderd/apidoc/swagger.json: $(shell find ./scripts/apidocgen $(FIND_EXCLUSIONS) -type f) $(wildcard coderd/*.go) $(wildcard enterprise/coderd/*.go) $(wildcard codersdk/*.go) .swaggo docs/manifest.json coderd/rbac/object_gen.go
518+
coderd/apidoc/swagger.json: $(shell find ./scripts/apidocgen $(FIND_EXCLUSIONS) -type f) $(wildcard coderd/*.go) $(wildcard enterprise/coderd/*.go) $(wildcard codersdk/*.go) $(wildcard enterprise/wsproxy/wsproxysdk/*.go) coderd/database/querier.go .swaggo docs/manifest.json coderd/rbac/object_gen.go
519519
./scripts/apidocgen/generate.sh
520520
yarn run --cwd=site format:write:only ../docs/api ../docs/manifest.json ../coderd/apidoc/swagger.json
521521

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ coder server --postgres-url <url> --access-url <url>
8484

8585
> <sup>1</sup> For production deployments, set up an external PostgreSQL instance for reliability.
8686
87-
Use `coder --help` to get a list of flags and environment variables. Use our [install guides](https://coder.com/docs/v2/latest/guides) for a full walkthrough.
87+
Use `coder --help` to get a list of flags and environment variables. Use our [install guides](https://coder.com/docs/v2/latest/install) for a full walkthrough.
8888

8989
## Documentation
9090

agent/agent_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -733,12 +733,15 @@ func TestAgent_UnixRemoteForwarding(t *testing.T) {
733733

734734
// It's possible that the socket is created but the server is not ready to
735735
// accept connections yet. We need to retry until we can connect.
736+
//
737+
// Note that we wait long here because if the tailnet connection has trouble
738+
// connecting, it could take 5 seconds or more to reconnect.
736739
var conn net.Conn
737740
require.Eventually(t, func() bool {
738741
var err error
739742
conn, err = net.Dial("unix", remoteSocketPath)
740743
return err == nil
741-
}, testutil.WaitShort, testutil.IntervalFast)
744+
}, testutil.WaitLong, testutil.IntervalFast)
742745
defer conn.Close()
743746
_, err = conn.Write([]byte("test"))
744747
require.NoError(t, err)

agent/reaper/reaper_test.go

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import (
1818
"github.com/coder/coder/testutil"
1919
)
2020

21-
//nolint:paralleltest // Non-parallel subtest.
21+
// TestReap checks that's the reaper is successfully reaping
22+
// exited processes and passing the PIDs through the shared
23+
// channel.
24+
//
25+
//nolint:paralleltest
2226
func TestReap(t *testing.T) {
2327
// Don't run the reaper test in CI. It does weird
2428
// things like forkexecing which may have unintended
@@ -27,45 +31,38 @@ func TestReap(t *testing.T) {
2731
t.Skip("Detected CI, skipping reaper tests")
2832
}
2933

30-
// OK checks that's the reaper is successfully reaping
31-
// exited processes and passing the PIDs through the shared
32-
// channel.
33-
34-
//nolint:paralleltest // Signal handling.
35-
t.Run("OK", func(t *testing.T) {
36-
pids := make(reap.PidCh, 1)
37-
err := reaper.ForkReap(
38-
reaper.WithPIDCallback(pids),
39-
// Provide some argument that immediately exits.
40-
reaper.WithExecArgs("/bin/sh", "-c", "exit 0"),
41-
)
42-
require.NoError(t, err)
34+
pids := make(reap.PidCh, 1)
35+
err := reaper.ForkReap(
36+
reaper.WithPIDCallback(pids),
37+
// Provide some argument that immediately exits.
38+
reaper.WithExecArgs("/bin/sh", "-c", "exit 0"),
39+
)
40+
require.NoError(t, err)
4341

44-
cmd := exec.Command("tail", "-f", "/dev/null")
45-
err = cmd.Start()
46-
require.NoError(t, err)
42+
cmd := exec.Command("tail", "-f", "/dev/null")
43+
err = cmd.Start()
44+
require.NoError(t, err)
4745

48-
cmd2 := exec.Command("tail", "-f", "/dev/null")
49-
err = cmd2.Start()
50-
require.NoError(t, err)
46+
cmd2 := exec.Command("tail", "-f", "/dev/null")
47+
err = cmd2.Start()
48+
require.NoError(t, err)
5149

52-
err = cmd.Process.Kill()
53-
require.NoError(t, err)
50+
err = cmd.Process.Kill()
51+
require.NoError(t, err)
5452

55-
err = cmd2.Process.Kill()
56-
require.NoError(t, err)
53+
err = cmd2.Process.Kill()
54+
require.NoError(t, err)
5755

58-
expectedPIDs := []int{cmd.Process.Pid, cmd2.Process.Pid}
56+
expectedPIDs := []int{cmd.Process.Pid, cmd2.Process.Pid}
5957

60-
for i := 0; i < len(expectedPIDs); i++ {
61-
select {
62-
case <-time.After(testutil.WaitShort):
63-
t.Fatalf("Timed out waiting for process")
64-
case pid := <-pids:
65-
require.Contains(t, expectedPIDs, pid)
66-
}
58+
for i := 0; i < len(expectedPIDs); i++ {
59+
select {
60+
case <-time.After(testutil.WaitShort):
61+
t.Fatalf("Timed out waiting for process")
62+
case pid := <-pids:
63+
require.Contains(t, expectedPIDs, pid)
6764
}
68-
})
65+
}
6966
}
7067

7168
//nolint:paralleltest // Signal handling.

cli/agent.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"cloud.google.com/go/compute/metadata"
1919
"golang.org/x/xerrors"
2020
"gopkg.in/natefinch/lumberjack.v2"
21+
"tailscale.com/util/clientmetric"
2122

2223
"cdr.dev/slog"
2324
"cdr.dev/slog/sloggers/sloghuman"
@@ -36,6 +37,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
3637
noReap bool
3738
sshMaxTimeout time.Duration
3839
tailnetListenPort int64
40+
prometheusAddress string
3941
)
4042
cmd := &clibase.Cmd{
4143
Use: "agent",
@@ -126,6 +128,13 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
126128
agentPorts[port] = "pprof"
127129
}
128130

131+
prometheusSrvClose := ServeHandler(ctx, logger, prometheusMetricsHandler(), prometheusAddress, "prometheus")
132+
defer prometheusSrvClose()
133+
// Do a best effort here. If this fails, it's not a big deal.
134+
if port, err := urlPort(prometheusAddress); err == nil {
135+
agentPorts[port] = "prometheus"
136+
}
137+
129138
// exchangeToken returns a session token.
130139
// This is abstracted to allow for the same looping condition
131140
// regardless of instance identity auth type.
@@ -257,6 +266,13 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
257266
Description: "Specify a static port for Tailscale to use for listening.",
258267
Value: clibase.Int64Of(&tailnetListenPort),
259268
},
269+
{
270+
Flag: "prometheus-address",
271+
Default: "127.0.0.1:2112",
272+
Env: "CODER_AGENT_PROMETHEUS_ADDRESS",
273+
Value: clibase.StringOf(&prometheusAddress),
274+
Description: "The bind address to serve Prometheus metrics.",
275+
},
260276
}
261277

262278
return cmd
@@ -343,3 +359,12 @@ func urlPort(u string) (int, error) {
343359
}
344360
return -1, xerrors.Errorf("invalid port: %s", u)
345361
}
362+
363+
func prometheusMetricsHandler() http.Handler {
364+
// We don't have any other internal metrics so far, so it's safe to expose metrics this way.
365+
// Based on: https://github.com/tailscale/tailscale/blob/280255acae604796a1113861f5a84e6fa2dc6121/ipn/localapi/localapi.go#L489
366+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
367+
w.Header().Set("Content-Type", "text/plain")
368+
clientmetric.WritePrometheusExpositionFormat(w)
369+
})
370+
}

cli/clibase/option.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ func (s *OptionSet) Add(opts ...Option) {
8080
*s = append(*s, opts...)
8181
}
8282

83+
// Filter will only return options that match the given filter. (return true)
84+
func (s OptionSet) Filter(filter func(opt Option) bool) OptionSet {
85+
cpy := make(OptionSet, 0)
86+
for _, opt := range s {
87+
if filter(opt) {
88+
cpy = append(cpy, opt)
89+
}
90+
}
91+
return cpy
92+
}
93+
8394
// FlagSet returns a pflag.FlagSet for the OptionSet.
8495
func (s *OptionSet) FlagSet() *pflag.FlagSet {
8596
if s == nil {

cli/clitest/clitest.go

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -127,26 +127,29 @@ func extractTar(t *testing.T, data []byte, directory string) {
127127
}
128128
}
129129

130-
// Start runs the command in a goroutine and cleans it up when
131-
// the test completed.
130+
// Start runs the command in a goroutine and cleans it up when the test
131+
// completed.
132132
func Start(t *testing.T, inv *clibase.Invocation) {
133133
t.Helper()
134134

135135
closeCh := make(chan struct{})
136+
// StartWithWaiter adds its own `t.Cleanup`, so we need to be sure it's added
137+
// before ours.
138+
waiter := StartWithWaiter(t, inv)
139+
t.Cleanup(func() {
140+
<-closeCh
141+
})
142+
136143
go func() {
137144
defer close(closeCh)
138-
err := StartWithWaiter(t, inv).Wait()
145+
err := waiter.Wait()
139146
switch {
140147
case errors.Is(err, context.Canceled):
141148
return
142149
default:
143150
assert.NoError(t, err)
144151
}
145152
}()
146-
147-
t.Cleanup(func() {
148-
<-closeCh
149-
})
150153
}
151154

152155
// Run runs the command and asserts that there is no error.
@@ -170,7 +173,7 @@ func (w *ErrorWaiter) Wait() error {
170173
var ok bool
171174
w.cachedError, ok = <-w.c
172175
if !ok {
173-
panic("unexpoected channel close")
176+
panic("unexpected channel close")
174177
}
175178
})
176179
return w.cachedError
@@ -196,18 +199,18 @@ func (w *ErrorWaiter) RequireAs(want interface{}) {
196199
require.ErrorAs(w.t, w.Wait(), want)
197200
}
198201

199-
// StartWithWaiter runs the command in a goroutine but returns the error
200-
// instead of asserting it. This is useful for testing error cases.
202+
// StartWithWaiter runs the command in a goroutine but returns the error instead
203+
// of asserting it. This is useful for testing error cases.
201204
func StartWithWaiter(t *testing.T, inv *clibase.Invocation) *ErrorWaiter {
202205
t.Helper()
203206

204-
errCh := make(chan error, 1)
205-
206-
var cleaningUp atomic.Bool
207-
208207
var (
209208
ctx = inv.Context()
210209
cancel func()
210+
211+
cleaningUp atomic.Bool
212+
errCh = make(chan error, 1)
213+
doneCh = make(chan struct{})
211214
)
212215
if _, ok := ctx.Deadline(); !ok {
213216
ctx, cancel = context.WithDeadline(ctx, time.Now().Add(testutil.WaitMedium))
@@ -218,12 +221,13 @@ func StartWithWaiter(t *testing.T, inv *clibase.Invocation) *ErrorWaiter {
218221
inv = inv.WithContext(ctx)
219222

220223
go func() {
224+
defer close(doneCh)
221225
defer close(errCh)
222226
err := inv.Run()
223227
if cleaningUp.Load() && errors.Is(err, context.DeadlineExceeded) {
224-
// If we're cleaning up, this error is likely related to the
225-
// CLI teardown process. E.g., the server could be slow to shut
226-
// down Postgres.
228+
// If we're cleaning up, this error is likely related to the CLI
229+
// teardown process. E.g., the server could be slow to shut down
230+
// Postgres.
227231
t.Logf("command %q timed out during test cleanup", inv.Command.FullName())
228232
}
229233
// Whether or not this fails the test is left to the caller.
@@ -235,7 +239,7 @@ func StartWithWaiter(t *testing.T, inv *clibase.Invocation) *ErrorWaiter {
235239
t.Cleanup(func() {
236240
cancel()
237241
cleaningUp.Store(true)
238-
<-errCh
242+
<-doneCh
239243
})
240244
return &ErrorWaiter{c: errCh, t: t}
241245
}

cli/cliui/output.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,35 @@ func (textFormat) AttachOptions(_ *clibase.OptionSet) {}
192192
func (textFormat) Format(_ context.Context, data any) (string, error) {
193193
return fmt.Sprintf("%s", data), nil
194194
}
195+
196+
// DataChangeFormat allows manipulating the data passed to an output format.
197+
// This is because sometimes the data needs to be manipulated before it can be
198+
// passed to the output format.
199+
// For example, you may want to pass something different to the text formatter
200+
// than what you pass to the json formatter.
201+
type DataChangeFormat struct {
202+
format OutputFormat
203+
change func(data any) (any, error)
204+
}
205+
206+
// ChangeFormatterData allows manipulating the data passed to an output
207+
// format.
208+
func ChangeFormatterData(format OutputFormat, change func(data any) (any, error)) *DataChangeFormat {
209+
return &DataChangeFormat{format: format, change: change}
210+
}
211+
212+
func (d *DataChangeFormat) ID() string {
213+
return d.format.ID()
214+
}
215+
216+
func (d *DataChangeFormat) AttachOptions(opts *clibase.OptionSet) {
217+
d.format.AttachOptions(opts)
218+
}
219+
220+
func (d *DataChangeFormat) Format(ctx context.Context, data any) (string, error) {
221+
newData, err := d.change(data)
222+
if err != nil {
223+
return "", err
224+
}
225+
return d.format.Format(ctx, newData)
226+
}

cli/cliui/select.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,22 @@ type RichSelectOptions struct {
7070
// RichSelect displays a list of user options including name and description.
7171
func RichSelect(inv *clibase.Invocation, richOptions RichSelectOptions) (*codersdk.TemplateVersionParameterOption, error) {
7272
opts := make([]string, len(richOptions.Options))
73+
var defaultOpt string
7374
for i, option := range richOptions.Options {
7475
line := option.Name
7576
if len(option.Description) > 0 {
7677
line += ": " + option.Description
7778
}
7879
opts[i] = line
80+
81+
if option.Value == richOptions.Default {
82+
defaultOpt = line
83+
}
7984
}
8085

8186
selected, err := Select(inv, SelectOptions{
8287
Options: opts,
83-
Default: richOptions.Default,
88+
Default: defaultOpt,
8489
Size: richOptions.Size,
8590
HideSearch: richOptions.HideSearch,
8691
})

0 commit comments

Comments
 (0)