Skip to content

Commit b0afe7d

Browse files
authored
Merge branch 'main' into matifali/pr-deployment-namespace-access
2 parents 219a44b + 00a8221 commit b0afe7d

Some content is hidden

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

65 files changed

+2382
-297
lines changed

agent/agent.go

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"time"
2323

2424
"github.com/armon/circbuf"
25+
"github.com/go-chi/chi/v5"
2526
"github.com/google/uuid"
2627
"github.com/prometheus/client_golang/prometheus"
2728
"github.com/spf13/afero"
@@ -63,7 +64,7 @@ type Options struct {
6364
IgnorePorts map[int]string
6465
SSHMaxTimeout time.Duration
6566
TailnetListenPort uint16
66-
Subsystem codersdk.AgentSubsystem
67+
Subsystems []codersdk.AgentSubsystem
6768
Addresses []netip.Prefix
6869
PrometheusRegistry *prometheus.Registry
6970
ReportMetadataInterval time.Duration
@@ -144,7 +145,7 @@ func New(options Options) Agent {
144145
reportMetadataInterval: options.ReportMetadataInterval,
145146
serviceBannerRefreshInterval: options.ServiceBannerRefreshInterval,
146147
sshMaxTimeout: options.SSHMaxTimeout,
147-
subsystem: options.Subsystem,
148+
subsystems: options.Subsystems,
148149
addresses: options.Addresses,
149150

150151
prometheusRegistry: prometheusRegistry,
@@ -166,7 +167,7 @@ type agent struct {
166167
// listing all listening ports. This is helpful to hide ports that
167168
// are used by the agent, that the user does not care about.
168169
ignorePorts map[int]string
169-
subsystem codersdk.AgentSubsystem
170+
subsystems []codersdk.AgentSubsystem
170171

171172
reconnectingPTYs sync.Map
172173
reconnectingPTYTimeout time.Duration
@@ -608,7 +609,7 @@ func (a *agent) run(ctx context.Context) error {
608609
err = a.client.PostStartup(ctx, agentsdk.PostStartupRequest{
609610
Version: buildinfo.Version(),
610611
ExpandedDirectory: manifest.Directory,
611-
Subsystem: a.subsystem,
612+
Subsystems: a.subsystems,
612613
})
613614
if err != nil {
614615
return xerrors.Errorf("update workspace agent version: %w", err)
@@ -1408,24 +1409,57 @@ func (a *agent) isClosed() bool {
14081409
}
14091410

14101411
func (a *agent) HTTPDebug() http.Handler {
1411-
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
1412+
r := chi.NewRouter()
1413+
1414+
requireNetwork := func(w http.ResponseWriter) (*tailnet.Conn, bool) {
14121415
a.closeMutex.Lock()
14131416
network := a.network
14141417
a.closeMutex.Unlock()
14151418

14161419
if network == nil {
1417-
w.WriteHeader(http.StatusOK)
1420+
w.WriteHeader(http.StatusNotFound)
14181421
_, _ = w.Write([]byte("network is not ready yet"))
1422+
return nil, false
1423+
}
1424+
1425+
return network, true
1426+
}
1427+
1428+
r.Get("/debug/magicsock", func(w http.ResponseWriter, r *http.Request) {
1429+
network, ok := requireNetwork(w)
1430+
if !ok {
14191431
return
14201432
}
1433+
network.MagicsockServeHTTPDebug(w, r)
1434+
})
14211435

1422-
if r.URL.Path == "/debug/magicsock" {
1423-
network.MagicsockServeHTTPDebug(w, r)
1424-
} else {
1425-
w.WriteHeader(http.StatusNotFound)
1426-
_, _ = w.Write([]byte("404 not found"))
1436+
r.Get("/debug/magicsock/debug-logging/{state}", func(w http.ResponseWriter, r *http.Request) {
1437+
state := chi.URLParam(r, "state")
1438+
stateBool, err := strconv.ParseBool(state)
1439+
if err != nil {
1440+
w.WriteHeader(http.StatusBadRequest)
1441+
_, _ = fmt.Fprintf(w, "invalid state %q, must be a boolean", state)
1442+
return
1443+
}
1444+
1445+
network, ok := requireNetwork(w)
1446+
if !ok {
1447+
return
14271448
}
1449+
1450+
network.MagicsockSetDebugLoggingEnabled(stateBool)
1451+
a.logger.Info(r.Context(), "updated magicsock debug logging due to debug request", slog.F("new_state", stateBool))
1452+
1453+
w.WriteHeader(http.StatusOK)
1454+
_, _ = fmt.Fprintf(w, "updated magicsock debug logging to %v", stateBool)
14281455
})
1456+
1457+
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
1458+
w.WriteHeader(http.StatusNotFound)
1459+
_, _ = w.Write([]byte("404 not found"))
1460+
})
1461+
1462+
return r
14291463
}
14301464

14311465
func (a *agent) Close() error {

agent/agent_test.go

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1932,6 +1932,96 @@ func TestAgent_WriteVSCodeConfigs(t *testing.T) {
19321932
}, testutil.WaitShort, testutil.IntervalFast)
19331933
}
19341934

1935+
func TestAgent_DebugServer(t *testing.T) {
1936+
t.Parallel()
1937+
1938+
derpMap, _ := tailnettest.RunDERPAndSTUN(t)
1939+
//nolint:dogsled
1940+
conn, _, _, _, agnt := setupAgent(t, agentsdk.Manifest{
1941+
DERPMap: derpMap,
1942+
}, 0)
1943+
1944+
awaitReachableCtx := testutil.Context(t, testutil.WaitLong)
1945+
ok := conn.AwaitReachable(awaitReachableCtx)
1946+
require.True(t, ok)
1947+
_ = conn.Close()
1948+
1949+
srv := httptest.NewServer(agnt.HTTPDebug())
1950+
t.Cleanup(srv.Close)
1951+
1952+
t.Run("MagicsockDebug", func(t *testing.T) {
1953+
t.Parallel()
1954+
1955+
ctx := testutil.Context(t, testutil.WaitLong)
1956+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/debug/magicsock", nil)
1957+
require.NoError(t, err)
1958+
1959+
res, err := srv.Client().Do(req)
1960+
require.NoError(t, err)
1961+
defer res.Body.Close()
1962+
require.Equal(t, http.StatusOK, res.StatusCode)
1963+
1964+
resBody, err := io.ReadAll(res.Body)
1965+
require.NoError(t, err)
1966+
require.Contains(t, string(resBody), "<h1>magicsock</h1>")
1967+
})
1968+
1969+
t.Run("MagicsockDebugLogging", func(t *testing.T) {
1970+
t.Parallel()
1971+
1972+
t.Run("Enable", func(t *testing.T) {
1973+
t.Parallel()
1974+
1975+
ctx := testutil.Context(t, testutil.WaitLong)
1976+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/debug/magicsock/debug-logging/t", nil)
1977+
require.NoError(t, err)
1978+
1979+
res, err := srv.Client().Do(req)
1980+
require.NoError(t, err)
1981+
defer res.Body.Close()
1982+
require.Equal(t, http.StatusOK, res.StatusCode)
1983+
1984+
resBody, err := io.ReadAll(res.Body)
1985+
require.NoError(t, err)
1986+
require.Contains(t, string(resBody), "updated magicsock debug logging to true")
1987+
})
1988+
1989+
t.Run("Disable", func(t *testing.T) {
1990+
t.Parallel()
1991+
1992+
ctx := testutil.Context(t, testutil.WaitLong)
1993+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/debug/magicsock/debug-logging/0", nil)
1994+
require.NoError(t, err)
1995+
1996+
res, err := srv.Client().Do(req)
1997+
require.NoError(t, err)
1998+
defer res.Body.Close()
1999+
require.Equal(t, http.StatusOK, res.StatusCode)
2000+
2001+
resBody, err := io.ReadAll(res.Body)
2002+
require.NoError(t, err)
2003+
require.Contains(t, string(resBody), "updated magicsock debug logging to false")
2004+
})
2005+
2006+
t.Run("Invalid", func(t *testing.T) {
2007+
t.Parallel()
2008+
2009+
ctx := testutil.Context(t, testutil.WaitLong)
2010+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/debug/magicsock/debug-logging/blah", nil)
2011+
require.NoError(t, err)
2012+
2013+
res, err := srv.Client().Do(req)
2014+
require.NoError(t, err)
2015+
defer res.Body.Close()
2016+
require.Equal(t, http.StatusBadRequest, res.StatusCode)
2017+
2018+
resBody, err := io.ReadAll(res.Body)
2019+
require.NoError(t, err)
2020+
require.Contains(t, string(resBody), `invalid state "blah", must be a boolean`)
2021+
})
2022+
})
2023+
}
2024+
19352025
func setupSSHCommand(t *testing.T, beforeArgs []string, afterArgs []string) (*ptytest.PTYCmd, pty.Process) {
19362026
//nolint:dogsled
19372027
agentConn, _, _, _, _ := setupAgent(t, agentsdk.Manifest{}, 0)
@@ -2013,7 +2103,7 @@ func setupAgent(t *testing.T, metadata agentsdk.Manifest, ptyTimeout time.Durati
20132103
*agenttest.Client,
20142104
<-chan *agentsdk.Stats,
20152105
afero.Fs,
2016-
io.Closer,
2106+
agent.Agent,
20172107
) {
20182108
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
20192109
if metadata.DERPMap == nil {

cli/agent.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"path/filepath"
1313
"runtime"
1414
"strconv"
15+
"strings"
1516
"sync"
1617
"time"
1718

@@ -253,7 +254,19 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
253254
}
254255

255256
prometheusRegistry := prometheus.NewRegistry()
256-
subsystem := inv.Environ.Get(agent.EnvAgentSubsystem)
257+
subsystemsRaw := inv.Environ.Get(agent.EnvAgentSubsystem)
258+
subsystems := []codersdk.AgentSubsystem{}
259+
for _, s := range strings.Split(subsystemsRaw, ",") {
260+
subsystem := codersdk.AgentSubsystem(strings.TrimSpace(s))
261+
if subsystem == "" {
262+
continue
263+
}
264+
if !subsystem.Valid() {
265+
return xerrors.Errorf("invalid subsystem %q", subsystem)
266+
}
267+
subsystems = append(subsystems, subsystem)
268+
}
269+
257270
agnt := agent.New(agent.Options{
258271
Client: client,
259272
Logger: logger,
@@ -275,7 +288,7 @@ func (r *RootCmd) workspaceAgent() *clibase.Cmd {
275288
},
276289
IgnorePorts: ignorePorts,
277290
SSHMaxTimeout: sshMaxTimeout,
278-
Subsystem: codersdk.AgentSubsystem(subsystem),
291+
Subsystems: subsystems,
279292

280293
PrometheusRegistry: prometheusRegistry,
281294
})

cli/agent_test.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cli_test
22

33
import (
44
"context"
5+
"fmt"
56
"os"
67
"path/filepath"
78
"runtime"
@@ -264,8 +265,8 @@ func TestWorkspaceAgent(t *testing.T) {
264265
"--agent-url", client.URL.String(),
265266
"--log-dir", logDir,
266267
)
267-
// Set the subsystem for the agent.
268-
inv.Environ.Set(agent.EnvAgentSubsystem, string(codersdk.AgentSubsystemEnvbox))
268+
// Set the subsystems for the agent.
269+
inv.Environ.Set(agent.EnvAgentSubsystem, fmt.Sprintf("%s,%s", codersdk.AgentSubsystemExectrace, codersdk.AgentSubsystemEnvbox))
269270

270271
pty := ptytest.New(t).Attach(inv)
271272

@@ -275,6 +276,9 @@ func TestWorkspaceAgent(t *testing.T) {
275276
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
276277
require.Len(t, resources, 1)
277278
require.Len(t, resources[0].Agents, 1)
278-
require.Equal(t, codersdk.AgentSubsystemEnvbox, resources[0].Agents[0].Subsystem)
279+
require.Len(t, resources[0].Agents[0].Subsystems, 2)
280+
// Sorted
281+
require.Equal(t, codersdk.AgentSubsystemEnvbox, resources[0].Agents[0].Subsystems[0])
282+
require.Equal(t, codersdk.AgentSubsystemExectrace, resources[0].Agents[0].Subsystems[1])
279283
})
280284
}

cli/clibase/option_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,40 @@ func TestOptionSet_ParseFlags(t *testing.T) {
7272
err := os.FlagSet().Parse([]string{"--some-unknown", "foo"})
7373
require.Error(t, err)
7474
})
75+
76+
t.Run("RegexValid", func(t *testing.T) {
77+
t.Parallel()
78+
79+
var regexpString clibase.Regexp
80+
81+
os := clibase.OptionSet{
82+
clibase.Option{
83+
Name: "RegexpString",
84+
Value: &regexpString,
85+
Flag: "regexp-string",
86+
},
87+
}
88+
89+
err := os.FlagSet().Parse([]string{"--regexp-string", "$test^"})
90+
require.NoError(t, err)
91+
})
92+
93+
t.Run("RegexInvalid", func(t *testing.T) {
94+
t.Parallel()
95+
96+
var regexpString clibase.Regexp
97+
98+
os := clibase.OptionSet{
99+
clibase.Option{
100+
Name: "RegexpString",
101+
Value: &regexpString,
102+
Flag: "regexp-string",
103+
},
104+
}
105+
106+
err := os.FlagSet().Parse([]string{"--regexp-string", "(("})
107+
require.Error(t, err)
108+
})
75109
}
76110

77111
func TestOptionSet_ParseEnv(t *testing.T) {

cli/clibase/values.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"net"
88
"net/url"
99
"reflect"
10+
"regexp"
1011
"strconv"
1112
"strings"
1213
"time"
@@ -461,6 +462,43 @@ func (e *Enum) String() string {
461462
return *e.Value
462463
}
463464

465+
type Regexp regexp.Regexp
466+
467+
func (r *Regexp) MarshalYAML() (interface{}, error) {
468+
return yaml.Node{
469+
Kind: yaml.ScalarNode,
470+
Value: r.String(),
471+
}, nil
472+
}
473+
474+
func (r *Regexp) UnmarshalYAML(n *yaml.Node) error {
475+
return r.Set(n.Value)
476+
}
477+
478+
func (r *Regexp) Set(v string) error {
479+
exp, err := regexp.Compile(v)
480+
if err != nil {
481+
return xerrors.Errorf("invalid regex expression: %w", err)
482+
}
483+
*r = Regexp(*exp)
484+
return nil
485+
}
486+
487+
func (r Regexp) String() string {
488+
return r.Value().String()
489+
}
490+
491+
func (r *Regexp) Value() *regexp.Regexp {
492+
if r == nil {
493+
return nil
494+
}
495+
return (*regexp.Regexp)(r)
496+
}
497+
498+
func (Regexp) Type() string {
499+
return "regexp"
500+
}
501+
464502
var _ pflag.Value = (*YAMLConfigPath)(nil)
465503

466504
// YAMLConfigPath is a special value type that encodes a path to a YAML

cli/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,8 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
597597
AuthURLParams: cfg.OIDC.AuthURLParams.Value,
598598
IgnoreUserInfo: cfg.OIDC.IgnoreUserInfo.Value(),
599599
GroupField: cfg.OIDC.GroupField.String(),
600+
GroupFilter: cfg.OIDC.GroupRegexFilter.Value(),
601+
CreateMissingGroups: cfg.OIDC.GroupAutoCreate.Value(),
600602
GroupMapping: cfg.OIDC.GroupMapping.Value,
601603
UserRoleField: cfg.OIDC.UserRoleField.String(),
602604
UserRoleMapping: cfg.OIDC.UserRoleMapping.Value,

0 commit comments

Comments
 (0)