Skip to content

Commit 9020f2c

Browse files
committed
Merge branch 'main' into jon/groupavatars
2 parents 867bae9 + 8ab4d26 commit 9020f2c

File tree

196 files changed

+7706
-1184
lines changed

Some content is hidden

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

196 files changed

+7706
-1184
lines changed

.github/workflows/typos.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ IST = "IST"
55
MacOS = "macOS"
66

77
[default.extend-words]
8+
# do as sudo replacement
9+
doas = "doas"
810

911
[files]
1012
extend-exclude = [

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
"derphttp",
2020
"derpmap",
2121
"devel",
22+
"dflags",
2223
"drpc",
2324
"drpcconn",
2425
"drpcmux",
2526
"drpcserver",
2627
"Dsts",
2728
"enablements",
29+
"eventsourcemock",
2830
"fatih",
2931
"Formik",
3032
"gitsshkey",
@@ -85,8 +87,10 @@
8587
"ptytest",
8688
"quickstart",
8789
"reconfig",
90+
"replicasync",
8891
"retrier",
8992
"rpty",
93+
"SCIM",
9094
"sdkproto",
9195
"sdktrace",
9296
"Signup",

agent/agent.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ func (a *agent) runTailnet(ctx context.Context, derpMap *tailcfg.DERPMap) {
170170
if a.isClosed() {
171171
return
172172
}
173+
a.logger.Debug(ctx, "running tailnet with derpmap", slog.F("derpmap", derpMap))
173174
if a.network != nil {
174175
a.network.SetDERPMap(derpMap)
175176
return
@@ -878,12 +879,22 @@ func (r *reconnectingPTY) Close() {
878879
// after one or both of them are done writing. If the context is canceled, both
879880
// of the connections will be closed.
880881
func Bicopy(ctx context.Context, c1, c2 io.ReadWriteCloser) {
881-
defer c1.Close()
882-
defer c2.Close()
882+
ctx, cancel := context.WithCancel(ctx)
883+
defer cancel()
884+
885+
defer func() {
886+
_ = c1.Close()
887+
_ = c2.Close()
888+
}()
883889

884890
var wg sync.WaitGroup
885891
copyFunc := func(dst io.WriteCloser, src io.Reader) {
886-
defer wg.Done()
892+
defer func() {
893+
wg.Done()
894+
// If one side of the copy fails, ensure the other one exits as
895+
// well.
896+
cancel()
897+
}()
887898
_, _ = io.Copy(dst, src)
888899
}
889900

agent/agent_test.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,7 @@ func TestAgent(t *testing.T) {
465465

466466
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{}, 0)
467467
require.Eventually(t, func() bool {
468-
_, err := conn.Ping()
468+
_, err := conn.Ping(context.Background())
469469
return err == nil
470470
}, testutil.WaitMedium, testutil.IntervalFast)
471471
conn1, err := conn.DialContext(context.Background(), l.Addr().Network(), l.Addr().String())
@@ -483,9 +483,7 @@ func TestAgent(t *testing.T) {
483483

484484
t.Run("Speedtest", func(t *testing.T) {
485485
t.Parallel()
486-
if testing.Short() {
487-
t.Skip("The minimum duration for a speedtest is hardcoded in Tailscale to 5s!")
488-
}
486+
t.Skip("This test is relatively flakey because of Tailscale's speedtest code...")
489487
derpMap := tailnettest.RunDERPAndSTUN(t)
490488
conn, _ := setupAgent(t, codersdk.WorkspaceAgentMetadata{
491489
DERPMap: derpMap,

cli/agent_test.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import (
77
"github.com/stretchr/testify/assert"
88
"github.com/stretchr/testify/require"
99

10-
"cdr.dev/slog"
11-
1210
"github.com/coder/coder/cli/clitest"
1311
"github.com/coder/coder/coderd/coderdtest"
1412
"github.com/coder/coder/provisioner/echo"
@@ -67,11 +65,11 @@ func TestWorkspaceAgent(t *testing.T) {
6765
if assert.NotEmpty(t, workspace.LatestBuild.Resources) && assert.NotEmpty(t, resources[0].Agents) {
6866
assert.NotEmpty(t, resources[0].Agents[0].Version)
6967
}
70-
dialer, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, resources[0].Agents[0].ID)
68+
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
7169
require.NoError(t, err)
7270
defer dialer.Close()
7371
require.Eventually(t, func() bool {
74-
_, err := dialer.Ping()
72+
_, err := dialer.Ping(ctx)
7573
return err == nil
7674
}, testutil.WaitMedium, testutil.IntervalFast)
7775
cancelFunc()
@@ -128,11 +126,11 @@ func TestWorkspaceAgent(t *testing.T) {
128126
if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) {
129127
assert.NotEmpty(t, resources[0].Agents[0].Version)
130128
}
131-
dialer, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, resources[0].Agents[0].ID)
129+
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
132130
require.NoError(t, err)
133131
defer dialer.Close()
134132
require.Eventually(t, func() bool {
135-
_, err := dialer.Ping()
133+
_, err := dialer.Ping(ctx)
136134
return err == nil
137135
}, testutil.WaitMedium, testutil.IntervalFast)
138136
cancelFunc()
@@ -189,11 +187,11 @@ func TestWorkspaceAgent(t *testing.T) {
189187
if assert.NotEmpty(t, resources) && assert.NotEmpty(t, resources[0].Agents) {
190188
assert.NotEmpty(t, resources[0].Agents[0].Version)
191189
}
192-
dialer, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, resources[0].Agents[0].ID)
190+
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil)
193191
require.NoError(t, err)
194192
defer dialer.Close()
195193
require.Eventually(t, func() bool {
196-
_, err := dialer.Ping()
194+
_, err := dialer.Ping(ctx)
197195
return err == nil
198196
}, testutil.WaitMedium, testutil.IntervalFast)
199197
cancelFunc()

cli/config/file.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ func (r Root) Session() File {
1313
return File(filepath.Join(string(r), "session"))
1414
}
1515

16+
// ReplicaID is a unique identifier for the Coder server.
17+
func (r Root) ReplicaID() File {
18+
return File(filepath.Join(string(r), "replica_id"))
19+
}
20+
1621
func (r Root) URL() File {
1722
return File(filepath.Join(string(r), "url"))
1823
}

cli/configssh_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
"github.com/stretchr/testify/assert"
2020
"github.com/stretchr/testify/require"
2121

22-
"cdr.dev/slog"
2322
"cdr.dev/slog/sloggers/slogtest"
2423

2524
"github.com/coder/coder/agent"
@@ -115,7 +114,7 @@ func TestConfigSSH(t *testing.T) {
115114
_ = agentCloser.Close()
116115
}()
117116
resources := coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)
118-
agentConn, err := client.DialWorkspaceAgentTailnet(context.Background(), slog.Logger{}, resources[0].Agents[0].ID)
117+
agentConn, err := client.DialWorkspaceAgent(context.Background(), resources[0].Agents[0].ID, nil)
119118
require.NoError(t, err)
120119
defer agentConn.Close()
121120

cli/deployment/flags.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func Flags() *codersdk.DeploymentFlags {
3232
Name: "Wildcard Address URL",
3333
Flag: "wildcard-access-url",
3434
EnvVar: "CODER_WILDCARD_ACCESS_URL",
35-
Description: `Specifies the wildcard hostname to use for workspace applications in the form "*.example.com".`,
35+
Description: `Specifies the wildcard hostname to use for workspace applications in the form "*.example.com" or "*-suffix.example.com". Ports or schemes should not be included. The scheme will be copied from the access URL.`,
3636
},
3737
Address: &codersdk.StringFlag{
3838
Name: "Bind Address",
@@ -85,6 +85,13 @@ func Flags() *codersdk.DeploymentFlags {
8585
Description: "Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections.",
8686
Default: []string{"stun.l.google.com:19302"},
8787
},
88+
DerpServerRelayAddress: &codersdk.StringFlag{
89+
Name: "DERP Server Relay Address",
90+
Flag: "derp-server-relay-address",
91+
EnvVar: "CODER_DERP_SERVER_RELAY_URL",
92+
Description: "An HTTP address that is accessible by other replicas to relay DERP traffic. Required for high availability.",
93+
Enterprise: true,
94+
},
8895
DerpConfigURL: &codersdk.StringFlag{
8996
Name: "DERP Config URL",
9097
Flag: "derp-config-url",

cli/portforward.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import (
1616
"github.com/spf13/cobra"
1717
"golang.org/x/xerrors"
1818

19-
"cdr.dev/slog"
2019
"github.com/coder/coder/agent"
2120
"github.com/coder/coder/cli/cliflag"
2221
"github.com/coder/coder/cli/cliui"
@@ -96,7 +95,7 @@ func portForward() *cobra.Command {
9695
return xerrors.Errorf("await agent: %w", err)
9796
}
9897

99-
conn, err := client.DialWorkspaceAgentTailnet(ctx, slog.Logger{}, workspaceAgent.ID)
98+
conn, err := client.DialWorkspaceAgent(ctx, workspaceAgent.ID, nil)
10099
if err != nil {
101100
return err
102101
}
@@ -139,8 +138,7 @@ func portForward() *cobra.Command {
139138
case <-ctx.Done():
140139
closeErr = ctx.Err()
141140
case <-sigs:
142-
_, _ = fmt.Fprintln(cmd.OutOrStderr(), "Received signal, closing all listeners and active connections")
143-
closeErr = xerrors.New("signal received")
141+
_, _ = fmt.Fprintln(cmd.OutOrStderr(), "\nReceived signal, closing all listeners and active connections")
144142
}
145143

146144
cancel()
@@ -156,7 +154,7 @@ func portForward() *cobra.Command {
156154
case <-ticker.C:
157155
}
158156

159-
_, err = conn.Ping()
157+
_, err = conn.Ping(ctx)
160158
if err != nil {
161159
continue
162160
}
@@ -214,7 +212,11 @@ func listenAndPortForward(ctx context.Context, cmd *cobra.Command, conn *codersd
214212
for {
215213
netConn, err := l.Accept()
216214
if err != nil {
217-
_, _ = fmt.Fprintf(cmd.OutOrStderr(), "Error accepting connection from '%v://%v': %+v\n", spec.listenNetwork, spec.listenAddress, err)
215+
// Silently ignore net.ErrClosed errors.
216+
if xerrors.Is(err, net.ErrClosed) {
217+
return
218+
}
219+
_, _ = fmt.Fprintf(cmd.OutOrStderr(), "Error accepting connection from '%v://%v': %v\n", spec.listenNetwork, spec.listenAddress, err)
218220
_, _ = fmt.Fprintln(cmd.OutOrStderr(), "Killing listener")
219221
return
220222
}

cli/root.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"flag"
66
"fmt"
7+
"io"
78
"net/http"
89
"net/url"
910
"os"
@@ -100,8 +101,9 @@ func Core() []*cobra.Command {
100101
}
101102

102103
func AGPL() []*cobra.Command {
103-
all := append(Core(), Server(deployment.Flags(), func(_ context.Context, o *coderd.Options) (*coderd.API, error) {
104-
return coderd.New(o), nil
104+
all := append(Core(), Server(deployment.Flags(), func(_ context.Context, o *coderd.Options) (*coderd.API, io.Closer, error) {
105+
api := coderd.New(o)
106+
return api, api, nil
105107
}))
106108
return all
107109
}

cli/server.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"os/signal"
1818
"os/user"
1919
"path/filepath"
20+
"regexp"
2021
"strconv"
2122
"strings"
2223
"sync"
@@ -53,6 +54,7 @@ import (
5354
"github.com/coder/coder/coderd/database/migrations"
5455
"github.com/coder/coder/coderd/devtunnel"
5556
"github.com/coder/coder/coderd/gitsshkey"
57+
"github.com/coder/coder/coderd/httpapi"
5658
"github.com/coder/coder/coderd/prometheusmetrics"
5759
"github.com/coder/coder/coderd/telemetry"
5860
"github.com/coder/coder/coderd/tracing"
@@ -67,7 +69,7 @@ import (
6769
)
6870

6971
// nolint:gocyclo
70-
func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) *cobra.Command {
72+
func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *coderd.Options) (*coderd.API, io.Closer, error)) *cobra.Command {
7173
root := &cobra.Command{
7274
Use: "server",
7375
Short: "Start a Coder server",
@@ -165,9 +167,10 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code
165167
}
166168
defer listener.Close()
167169

170+
var tlsConfig *tls.Config
168171
if dflags.TLSEnable.Value {
169-
listener, err = configureServerTLS(
170-
listener, dflags.TLSMinVersion.Value,
172+
tlsConfig, err = configureTLS(
173+
dflags.TLSMinVersion.Value,
171174
dflags.TLSClientAuth.Value,
172175
dflags.TLSCertFiles.Value,
173176
dflags.TLSKeyFiles.Value,
@@ -176,6 +179,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code
176179
if err != nil {
177180
return xerrors.Errorf("configure tls: %w", err)
178181
}
182+
listener = tls.NewListener(listener, tlsConfig)
179183
}
180184

181185
tcpAddr, valid := listener.Addr().(*net.TCPAddr)
@@ -297,13 +301,19 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code
297301
return xerrors.Errorf("create derp map: %w", err)
298302
}
299303

300-
appHostname := strings.TrimPrefix(dflags.WildcardAccessURL.Value, "http://")
301-
appHostname = strings.TrimPrefix(appHostname, "https://")
302-
appHostname = strings.TrimPrefix(appHostname, "*.")
304+
appHostname := strings.TrimSpace(dflags.WildcardAccessURL.Value)
305+
var appHostnameRegex *regexp.Regexp
306+
if appHostname != "" {
307+
appHostnameRegex, err = httpapi.CompileHostnamePattern(appHostname)
308+
if err != nil {
309+
return xerrors.Errorf("parse wildcard access URL %q: %w", appHostname, err)
310+
}
311+
}
303312

304313
options := &coderd.Options{
305314
AccessURL: accessURLParsed,
306315
AppHostname: appHostname,
316+
AppHostnameRegex: appHostnameRegex,
307317
Logger: logger.Named("coderd"),
308318
Database: databasefake.New(),
309319
DERPMap: derpMap,
@@ -320,6 +330,9 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code
320330
Experimental: ExperimentalEnabled(cmd),
321331
DeploymentFlags: dflags,
322332
}
333+
if tlsConfig != nil {
334+
options.TLSCertificates = tlsConfig.Certificates
335+
}
323336

324337
if dflags.OAuth2GithubClientSecret.Value != "" {
325338
options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed,
@@ -463,11 +476,14 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code
463476
), dflags.PromAddress.Value, "prometheus")()
464477
}
465478

466-
coderAPI, err := newAPI(ctx, options)
479+
// We use a separate closer so the Enterprise API
480+
// can have it's own close functions. This is cleaner
481+
// than abstracting the Coder API itself.
482+
coderAPI, closer, err := newAPI(ctx, options)
467483
if err != nil {
468484
return err
469485
}
470-
defer coderAPI.Close()
486+
defer closer.Close()
471487

472488
client := codersdk.New(localURL)
473489
if dflags.TLSEnable.Value {
@@ -885,7 +901,7 @@ func loadCertificates(tlsCertFiles, tlsKeyFiles []string) ([]tls.Certificate, er
885901
return certs, nil
886902
}
887903

888-
func configureServerTLS(listener net.Listener, tlsMinVersion, tlsClientAuth string, tlsCertFiles, tlsKeyFiles []string, tlsClientCAFile string) (net.Listener, error) {
904+
func configureTLS(tlsMinVersion, tlsClientAuth string, tlsCertFiles, tlsKeyFiles []string, tlsClientCAFile string) (*tls.Config, error) {
889905
tlsConfig := &tls.Config{
890906
MinVersion: tls.VersionTLS12,
891907
}
@@ -921,6 +937,7 @@ func configureServerTLS(listener net.Listener, tlsMinVersion, tlsClientAuth stri
921937
if err != nil {
922938
return nil, xerrors.Errorf("load certificates: %w", err)
923939
}
940+
tlsConfig.Certificates = certs
924941
tlsConfig.GetCertificate = func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
925942
// If there's only one certificate, return it.
926943
if len(certs) == 1 {
@@ -955,7 +972,7 @@ func configureServerTLS(listener net.Listener, tlsMinVersion, tlsClientAuth stri
955972
tlsConfig.ClientCAs = caPool
956973
}
957974

958-
return tls.NewListener(listener, tlsConfig), nil
975+
return tlsConfig, nil
959976
}
960977

961978
func configureGithubOAuth2(accessURL *url.URL, clientID, clientSecret string, allowSignups bool, allowOrgs []string, rawTeams []string, enterpriseBaseURL string) (*coderd.GithubOAuth2Config, error) {

0 commit comments

Comments
 (0)