mirror of
https://github.com/coder/coder.git
synced 2025-07-09 11:45:56 +00:00
feat: allow http and https listening simultaneously (#5365)
This commit is contained in:
@ -32,12 +32,22 @@ func newConfig() *codersdk.DeploymentConfig {
|
|||||||
Usage: "Specifies the wildcard hostname to use for workspace applications in the form \"*.example.com\".",
|
Usage: "Specifies the wildcard hostname to use for workspace applications in the form \"*.example.com\".",
|
||||||
Flag: "wildcard-access-url",
|
Flag: "wildcard-access-url",
|
||||||
},
|
},
|
||||||
|
// DEPRECATED: Use HTTPAddress or TLS.Address instead.
|
||||||
Address: &codersdk.DeploymentConfigField[string]{
|
Address: &codersdk.DeploymentConfigField[string]{
|
||||||
Name: "Address",
|
Name: "Address",
|
||||||
Usage: "Bind address of the server.",
|
Usage: "Bind address of the server.",
|
||||||
Flag: "address",
|
Flag: "address",
|
||||||
Shorthand: "a",
|
Shorthand: "a",
|
||||||
Default: "127.0.0.1:3000",
|
// Deprecated, so we don't have a default. If set, it will overwrite
|
||||||
|
// HTTPAddress and TLS.Address and print a warning.
|
||||||
|
Hidden: true,
|
||||||
|
Default: "",
|
||||||
|
},
|
||||||
|
HTTPAddress: &codersdk.DeploymentConfigField[string]{
|
||||||
|
Name: "Address",
|
||||||
|
Usage: "HTTP bind address of the server. Unset to disable the HTTP endpoint.",
|
||||||
|
Flag: "http-address",
|
||||||
|
Default: "127.0.0.1:3000",
|
||||||
},
|
},
|
||||||
AutobuildPollInterval: &codersdk.DeploymentConfigField[time.Duration]{
|
AutobuildPollInterval: &codersdk.DeploymentConfigField[time.Duration]{
|
||||||
Name: "Autobuild Poll Interval",
|
Name: "Autobuild Poll Interval",
|
||||||
@ -267,6 +277,18 @@ func newConfig() *codersdk.DeploymentConfig {
|
|||||||
Usage: "Whether TLS will be enabled.",
|
Usage: "Whether TLS will be enabled.",
|
||||||
Flag: "tls-enable",
|
Flag: "tls-enable",
|
||||||
},
|
},
|
||||||
|
Address: &codersdk.DeploymentConfigField[string]{
|
||||||
|
Name: "TLS Address",
|
||||||
|
Usage: "HTTPS bind address of the server.",
|
||||||
|
Flag: "tls-address",
|
||||||
|
Default: "127.0.0.1:3443",
|
||||||
|
},
|
||||||
|
RedirectHTTP: &codersdk.DeploymentConfigField[bool]{
|
||||||
|
Name: "Redirect HTTP to HTTPS",
|
||||||
|
Usage: "Whether HTTP requests will be redirected to the access URL (if it's a https URL and TLS is enabled). Requests to local IP addresses are never redirected regardless of this setting.",
|
||||||
|
Flag: "tls-redirect-http-to-https",
|
||||||
|
Default: true,
|
||||||
|
},
|
||||||
CertFiles: &codersdk.DeploymentConfigField[[]string]{
|
CertFiles: &codersdk.DeploymentConfigField[[]string]{
|
||||||
Name: "TLS Certificate Files",
|
Name: "TLS Certificate Files",
|
||||||
Usage: "Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file.",
|
Usage: "Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file.",
|
||||||
|
@ -40,7 +40,7 @@ func TestResetPassword(t *testing.T) {
|
|||||||
serverDone := make(chan struct{})
|
serverDone := make(chan struct{})
|
||||||
serverCmd, cfg := clitest.New(t,
|
serverCmd, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--postgres-url", connectionURL,
|
"--postgres-url", connectionURL,
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
|
202
cli/server.go
202
cli/server.go
@ -85,6 +85,25 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("getting deployment config: %w", err)
|
return xerrors.Errorf("getting deployment config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate bind addresses.
|
||||||
|
if cfg.Address.Value != "" {
|
||||||
|
cmd.PrintErr(cliui.Styles.Warn.Render("WARN:") + " --address and -a are deprecated, please use --http-address and --tls-address instead")
|
||||||
|
if cfg.TLS.Enable.Value {
|
||||||
|
cfg.HTTPAddress.Value = ""
|
||||||
|
cfg.TLS.Address.Value = cfg.Address.Value
|
||||||
|
} else {
|
||||||
|
cfg.HTTPAddress.Value = cfg.Address.Value
|
||||||
|
cfg.TLS.Address.Value = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.TLS.Enable.Value && cfg.TLS.Address.Value == "" {
|
||||||
|
return xerrors.Errorf("TLS address must be set if TLS is enabled")
|
||||||
|
}
|
||||||
|
if !cfg.TLS.Enable.Value && cfg.HTTPAddress.Value == "" {
|
||||||
|
return xerrors.Errorf("either HTTP or TLS must be enabled")
|
||||||
|
}
|
||||||
|
|
||||||
printLogo(cmd)
|
printLogo(cmd)
|
||||||
logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr()))
|
logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr()))
|
||||||
if ok, _ := cmd.Flags().GetBool(varVerbose); ok {
|
if ok, _ := cmd.Flags().GetBool(varVerbose); ok {
|
||||||
@ -186,14 +205,41 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", cfg.Address.Value)
|
var (
|
||||||
if err != nil {
|
httpListener net.Listener
|
||||||
return xerrors.Errorf("listen %q: %w", cfg.Address.Value, err)
|
httpURL *url.URL
|
||||||
}
|
)
|
||||||
defer listener.Close()
|
if cfg.HTTPAddress.Value != "" {
|
||||||
|
httpListener, err = net.Listen("tcp", cfg.HTTPAddress.Value)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("listen %q: %w", cfg.HTTPAddress.Value, err)
|
||||||
|
}
|
||||||
|
defer httpListener.Close()
|
||||||
|
|
||||||
var tlsConfig *tls.Config
|
tcpAddr, tcpAddrValid := httpListener.Addr().(*net.TCPAddr)
|
||||||
|
if !tcpAddrValid {
|
||||||
|
return xerrors.Errorf("invalid TCP address type %T", httpListener.Addr())
|
||||||
|
}
|
||||||
|
if tcpAddr.IP.IsUnspecified() {
|
||||||
|
tcpAddr.IP = net.IPv4(127, 0, 0, 1)
|
||||||
|
}
|
||||||
|
httpURL = &url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Host: tcpAddr.String(),
|
||||||
|
}
|
||||||
|
cmd.Println("Started HTTP listener at " + httpURL.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
httpsListener net.Listener
|
||||||
|
httpsURL *url.URL
|
||||||
|
)
|
||||||
if cfg.TLS.Enable.Value {
|
if cfg.TLS.Enable.Value {
|
||||||
|
if cfg.TLS.Address.Value == "" {
|
||||||
|
return xerrors.New("tls address must be set if tls is enabled")
|
||||||
|
}
|
||||||
|
|
||||||
tlsConfig, err = configureTLS(
|
tlsConfig, err = configureTLS(
|
||||||
cfg.TLS.MinVersion.Value,
|
cfg.TLS.MinVersion.Value,
|
||||||
cfg.TLS.ClientAuth.Value,
|
cfg.TLS.ClientAuth.Value,
|
||||||
@ -204,7 +250,38 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("configure tls: %w", err)
|
return xerrors.Errorf("configure tls: %w", err)
|
||||||
}
|
}
|
||||||
listener = tls.NewListener(listener, tlsConfig)
|
httpsListenerInner, err := net.Listen("tcp", cfg.TLS.Address.Value)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("listen %q: %w", cfg.TLS.Address.Value, err)
|
||||||
|
}
|
||||||
|
defer httpsListenerInner.Close()
|
||||||
|
|
||||||
|
httpsListener = tls.NewListener(httpsListenerInner, tlsConfig)
|
||||||
|
defer httpsListener.Close()
|
||||||
|
|
||||||
|
tcpAddr, tcpAddrValid := httpsListener.Addr().(*net.TCPAddr)
|
||||||
|
if !tcpAddrValid {
|
||||||
|
return xerrors.Errorf("invalid TCP address type %T", httpsListener.Addr())
|
||||||
|
}
|
||||||
|
if tcpAddr.IP.IsUnspecified() {
|
||||||
|
tcpAddr.IP = net.IPv4(127, 0, 0, 1)
|
||||||
|
}
|
||||||
|
httpsURL = &url.URL{
|
||||||
|
Scheme: "https",
|
||||||
|
Host: tcpAddr.String(),
|
||||||
|
}
|
||||||
|
cmd.Println("Started TLS/HTTPS listener at " + httpsURL.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check that at least one listener was started.
|
||||||
|
if httpListener == nil && httpsListener == nil {
|
||||||
|
return xerrors.New("must listen on at least one address")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer HTTP because it's less prone to TLS errors over localhost.
|
||||||
|
localURL := httpsURL
|
||||||
|
if httpURL != nil {
|
||||||
|
localURL = httpURL
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, httpClient, err := configureHTTPClient(
|
ctx, httpClient, err := configureHTTPClient(
|
||||||
@ -217,24 +294,6 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||||||
return xerrors.Errorf("configure http client: %w", err)
|
return xerrors.Errorf("configure http client: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tcpAddr, valid := listener.Addr().(*net.TCPAddr)
|
|
||||||
if !valid {
|
|
||||||
return xerrors.New("must be listening on tcp")
|
|
||||||
}
|
|
||||||
// If just a port is specified, assume localhost.
|
|
||||||
if tcpAddr.IP.IsUnspecified() {
|
|
||||||
tcpAddr.IP = net.IPv4(127, 0, 0, 1)
|
|
||||||
}
|
|
||||||
// If no access URL is specified, fallback to the
|
|
||||||
// bounds URL.
|
|
||||||
localURL := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: tcpAddr.String(),
|
|
||||||
}
|
|
||||||
if cfg.TLS.Enable.Value {
|
|
||||||
localURL.Scheme = "https"
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ctxTunnel, closeTunnel = context.WithCancel(ctx)
|
ctxTunnel, closeTunnel = context.WithCancel(ctx)
|
||||||
tunnel *devtunnel.Tunnel
|
tunnel *devtunnel.Tunnel
|
||||||
@ -289,6 +348,15 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||||||
cmd.Printf("%s The access URL %s %s, this may cause unexpected problems when creating workspaces. Generate a unique *.try.coder.app URL by not specifying an access URL.\n", cliui.Styles.Warn.Render("Warning:"), cliui.Styles.Field.Render(accessURLParsed.String()), reason)
|
cmd.Printf("%s The access URL %s %s, this may cause unexpected problems when creating workspaces. Generate a unique *.try.coder.app URL by not specifying an access URL.\n", cliui.Styles.Warn.Render("Warning:"), cliui.Styles.Field.Render(accessURLParsed.String()), reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redirect from the HTTP listener to the access URL if:
|
||||||
|
// 1. The redirect flag is enabled.
|
||||||
|
// 2. HTTP listening is enabled (obviously).
|
||||||
|
// 3. TLS is enabled (otherwise they're likely using a reverse proxy
|
||||||
|
// which can do this instead).
|
||||||
|
// 4. The access URL has been set manually (not a tunnel).
|
||||||
|
// 5. The access URL is HTTPS.
|
||||||
|
shouldRedirectHTTPToAccessURL := cfg.TLS.RedirectHTTP.Value && cfg.HTTPAddress.Value != "" && cfg.TLS.Enable.Value && tunnel == nil && accessURLParsed.Scheme == "https"
|
||||||
|
|
||||||
// A newline is added before for visibility in terminal output.
|
// A newline is added before for visibility in terminal output.
|
||||||
cmd.Printf("\nView the Web UI: %s\n", accessURLParsed.String())
|
cmd.Printf("\nView the Web UI: %s\n", accessURLParsed.String())
|
||||||
|
|
||||||
@ -630,6 +698,11 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||||||
defer client.HTTPClient.CloseIdleConnections()
|
defer client.HTTPClient.CloseIdleConnections()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is helpful for tests, but can be silently ignored.
|
||||||
|
// Coder may be ran as users that don't have permission to write in the homedir,
|
||||||
|
// such as via the systemd service.
|
||||||
|
_ = config.URL().Write(client.URL.String())
|
||||||
|
|
||||||
// Since errCh only has one buffered slot, all routines
|
// Since errCh only has one buffered slot, all routines
|
||||||
// sending on it must be wrapped in a select/default to
|
// sending on it must be wrapped in a select/default to
|
||||||
// avoid leaving dangling goroutines waiting for the
|
// avoid leaving dangling goroutines waiting for the
|
||||||
@ -657,40 +730,65 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||||||
shutdownConnsCtx, shutdownConns := context.WithCancel(ctx)
|
shutdownConnsCtx, shutdownConns := context.WithCancel(ctx)
|
||||||
defer shutdownConns()
|
defer shutdownConns()
|
||||||
|
|
||||||
// ReadHeaderTimeout is purposefully not enabled. It caused some issues with
|
// Wrap the server in middleware that redirects to the access URL if
|
||||||
// websockets over the dev tunnel.
|
// the request is not to a local IP.
|
||||||
|
var handler http.Handler = coderAPI.RootHandler
|
||||||
|
if shouldRedirectHTTPToAccessURL {
|
||||||
|
handler = redirectHTTPToAccessURL(handler, accessURLParsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadHeaderTimeout is purposefully not enabled. It caused some
|
||||||
|
// issues with websockets over the dev tunnel.
|
||||||
// See: https://github.com/coder/coder/pull/3730
|
// See: https://github.com/coder/coder/pull/3730
|
||||||
//nolint:gosec
|
//nolint:gosec
|
||||||
server := &http.Server{
|
httpServer := &http.Server{
|
||||||
// These errors are typically noise like "TLS: EOF". Vault does similar:
|
// These errors are typically noise like "TLS: EOF". Vault does
|
||||||
|
// similar:
|
||||||
// https://github.com/hashicorp/vault/blob/e2490059d0711635e529a4efcbaa1b26998d6e1c/command/server.go#L2714
|
// https://github.com/hashicorp/vault/blob/e2490059d0711635e529a4efcbaa1b26998d6e1c/command/server.go#L2714
|
||||||
ErrorLog: log.New(io.Discard, "", 0),
|
ErrorLog: log.New(io.Discard, "", 0),
|
||||||
Handler: coderAPI.RootHandler,
|
Handler: handler,
|
||||||
BaseContext: func(_ net.Listener) context.Context {
|
BaseContext: func(_ net.Listener) context.Context {
|
||||||
return shutdownConnsCtx
|
return shutdownConnsCtx
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = shutdownWithTimeout(server.Shutdown, 5*time.Second)
|
_ = shutdownWithTimeout(httpServer.Shutdown, 5*time.Second)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
eg := errgroup.Group{}
|
// We call this in the routine so we can kill the other listeners if
|
||||||
eg.Go(func() error {
|
// one of them fails.
|
||||||
// Make sure to close the tunnel listener if we exit so the
|
closeListenersNow := func() {
|
||||||
// errgroup doesn't wait forever!
|
if httpListener != nil {
|
||||||
if tunnel != nil {
|
_ = httpListener.Close()
|
||||||
defer tunnel.Listener.Close()
|
|
||||||
}
|
}
|
||||||
|
if httpsListener != nil {
|
||||||
|
_ = httpsListener.Close()
|
||||||
|
}
|
||||||
|
if tunnel != nil {
|
||||||
|
_ = tunnel.Listener.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return server.Serve(listener)
|
eg := errgroup.Group{}
|
||||||
})
|
if httpListener != nil {
|
||||||
if tunnel != nil {
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
defer listener.Close()
|
defer closeListenersNow()
|
||||||
|
return httpServer.Serve(httpListener)
|
||||||
return server.Serve(tunnel.Listener)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if httpsListener != nil {
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer closeListenersNow()
|
||||||
|
return httpServer.Serve(httpsListener)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if tunnel != nil {
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer closeListenersNow()
|
||||||
|
return httpServer.Serve(tunnel.Listener)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case errCh <- eg.Wait():
|
case errCh <- eg.Wait():
|
||||||
@ -718,11 +816,6 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||||||
autobuildExecutor := executor.New(ctx, options.Database, logger, autobuildPoller.C)
|
autobuildExecutor := executor.New(ctx, options.Database, logger, autobuildPoller.C)
|
||||||
autobuildExecutor.Run()
|
autobuildExecutor.Run()
|
||||||
|
|
||||||
// This is helpful for tests, but can be silently ignored.
|
|
||||||
// Coder may be ran as users that don't have permission to write in the homedir,
|
|
||||||
// such as via the systemd service.
|
|
||||||
_ = config.URL().Write(client.URL.String())
|
|
||||||
|
|
||||||
// Currently there is no way to ask the server to shut
|
// Currently there is no way to ask the server to shut
|
||||||
// itself down, so any exit signal will result in a non-zero
|
// itself down, so any exit signal will result in a non-zero
|
||||||
// exit of the server.
|
// exit of the server.
|
||||||
@ -759,7 +852,7 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
|
|||||||
// in-flight requests, give in-flight requests 5 seconds to
|
// in-flight requests, give in-flight requests 5 seconds to
|
||||||
// complete.
|
// complete.
|
||||||
cmd.Println("Shutting down API server...")
|
cmd.Println("Shutting down API server...")
|
||||||
err = shutdownWithTimeout(server.Shutdown, 3*time.Second)
|
err = shutdownWithTimeout(httpServer.Shutdown, 3*time.Second)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.Printf("API server shutdown took longer than 3s: %s\n", err)
|
cmd.Printf("API server shutdown took longer than 3s: %s\n", err)
|
||||||
} else {
|
} else {
|
||||||
@ -1357,3 +1450,14 @@ func configureHTTPClient(ctx context.Context, clientCertFile, clientKeyFile stri
|
|||||||
}
|
}
|
||||||
return ctx, &http.Client{}, nil
|
return ctx, &http.Client{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func redirectHTTPToAccessURL(handler http.Handler, accessURL *url.URL) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.TLS == nil {
|
||||||
|
http.Redirect(w, r, accessURL.String(), http.StatusTemporaryRedirect)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -55,7 +55,7 @@ func TestServer(t *testing.T) {
|
|||||||
|
|
||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--postgres-url", connectionURL,
|
"--postgres-url", connectionURL,
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
@ -89,7 +89,7 @@ func TestServer(t *testing.T) {
|
|||||||
|
|
||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
)
|
)
|
||||||
@ -129,7 +129,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://localhost:3000/",
|
"--access-url", "http://localhost:3000/",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
)
|
)
|
||||||
@ -161,7 +161,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "https://foobarbaz.mydomain",
|
"--access-url", "https://foobarbaz.mydomain",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
)
|
)
|
||||||
@ -191,7 +191,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "https://google.com",
|
"--access-url", "https://google.com",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
)
|
)
|
||||||
@ -220,7 +220,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, _ := clitest.New(t,
|
root, _ := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "google.com",
|
"--access-url", "google.com",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
)
|
)
|
||||||
@ -236,9 +236,10 @@ func TestServer(t *testing.T) {
|
|||||||
root, _ := clitest.New(t,
|
root, _ := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", "",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--tls-enable",
|
"--tls-enable",
|
||||||
|
"--tls-address", ":0",
|
||||||
"--tls-min-version", "tls9",
|
"--tls-min-version", "tls9",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
)
|
)
|
||||||
@ -253,9 +254,10 @@ func TestServer(t *testing.T) {
|
|||||||
root, _ := clitest.New(t,
|
root, _ := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", "",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--tls-enable",
|
"--tls-enable",
|
||||||
|
"--tls-address", ":0",
|
||||||
"--tls-client-auth", "something",
|
"--tls-client-auth", "something",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
)
|
)
|
||||||
@ -310,7 +312,7 @@ func TestServer(t *testing.T) {
|
|||||||
args := []string{
|
args := []string{
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
}
|
}
|
||||||
@ -331,9 +333,10 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", "",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--tls-enable",
|
"--tls-enable",
|
||||||
|
"--tls-address", ":0",
|
||||||
"--tls-cert-file", certPath,
|
"--tls-cert-file", certPath,
|
||||||
"--tls-key-file", keyPath,
|
"--tls-key-file", keyPath,
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
@ -371,9 +374,10 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", "",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--tls-enable",
|
"--tls-enable",
|
||||||
|
"--tls-address", ":0",
|
||||||
"--tls-cert-file", cert1Path,
|
"--tls-cert-file", cert1Path,
|
||||||
"--tls-key-file", key1Path,
|
"--tls-key-file", key1Path,
|
||||||
"--tls-cert-file", cert2Path,
|
"--tls-cert-file", cert2Path,
|
||||||
@ -443,6 +447,334 @@ func TestServer(t *testing.T) {
|
|||||||
cancelFunc()
|
cancelFunc()
|
||||||
require.NoError(t, <-errC)
|
require.NoError(t, <-errC)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("TLSAndHTTP", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
certPath, keyPath := generateTLSCertificate(t)
|
||||||
|
root, _ := clitest.New(t,
|
||||||
|
"server",
|
||||||
|
"--in-memory",
|
||||||
|
"--http-address", ":0",
|
||||||
|
"--access-url", "https://example.com",
|
||||||
|
"--tls-enable",
|
||||||
|
"--tls-redirect-http-to-https=false",
|
||||||
|
"--tls-address", ":0",
|
||||||
|
"--tls-cert-file", certPath,
|
||||||
|
"--tls-key-file", keyPath,
|
||||||
|
"--cache-dir", t.TempDir(),
|
||||||
|
)
|
||||||
|
pty := ptytest.New(t)
|
||||||
|
root.SetOutput(pty.Output())
|
||||||
|
root.SetErr(pty.Output())
|
||||||
|
|
||||||
|
errC := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
errC <- root.ExecuteContext(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// We can't use waitAccessURL as it will only return the HTTP URL.
|
||||||
|
const httpLinePrefix = "Started HTTP listener at "
|
||||||
|
pty.ExpectMatch(httpLinePrefix)
|
||||||
|
httpLine := pty.ReadLine()
|
||||||
|
httpAddr := strings.TrimSpace(strings.TrimPrefix(httpLine, httpLinePrefix))
|
||||||
|
require.NotEmpty(t, httpAddr)
|
||||||
|
const tlsLinePrefix = "Started TLS/HTTPS listener at "
|
||||||
|
pty.ExpectMatch(tlsLinePrefix)
|
||||||
|
tlsLine := pty.ReadLine()
|
||||||
|
tlsAddr := strings.TrimSpace(strings.TrimPrefix(tlsLine, tlsLinePrefix))
|
||||||
|
require.NotEmpty(t, tlsAddr)
|
||||||
|
|
||||||
|
// Verify HTTP
|
||||||
|
httpURL, err := url.Parse(httpAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
client := codersdk.New(httpURL)
|
||||||
|
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
_, err = client.HasFirstUser(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify TLS
|
||||||
|
tlsURL, err := url.Parse(tlsAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
client = codersdk.New(tlsURL)
|
||||||
|
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
client.HTTPClient = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
//nolint:gosec
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = client.HasFirstUser(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cancelFunc()
|
||||||
|
require.NoError(t, <-errC)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("TLSRedirect", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
httpListener bool
|
||||||
|
tlsListener bool
|
||||||
|
accessURL string
|
||||||
|
// Empty string means no redirect.
|
||||||
|
expectRedirect string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OK",
|
||||||
|
httpListener: true,
|
||||||
|
tlsListener: true,
|
||||||
|
accessURL: "https://example.com",
|
||||||
|
expectRedirect: "https://example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoTLSListener",
|
||||||
|
httpListener: true,
|
||||||
|
tlsListener: false,
|
||||||
|
accessURL: "https://example.com",
|
||||||
|
expectRedirect: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "NoHTTPListener",
|
||||||
|
httpListener: false,
|
||||||
|
tlsListener: true,
|
||||||
|
accessURL: "https://example.com",
|
||||||
|
expectRedirect: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
c := c
|
||||||
|
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
certPath, keyPath := generateTLSCertificate(t)
|
||||||
|
flags := []string{
|
||||||
|
"server",
|
||||||
|
"--in-memory",
|
||||||
|
"--cache-dir", t.TempDir(),
|
||||||
|
}
|
||||||
|
if c.httpListener {
|
||||||
|
flags = append(flags, "--http-address", ":0")
|
||||||
|
}
|
||||||
|
if c.tlsListener {
|
||||||
|
flags = append(flags,
|
||||||
|
"--tls-enable",
|
||||||
|
"--tls-address", ":0",
|
||||||
|
"--tls-cert-file", certPath,
|
||||||
|
"--tls-key-file", keyPath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if c.accessURL != "" {
|
||||||
|
flags = append(flags, "--access-url", c.accessURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
root, _ := clitest.New(t, flags...)
|
||||||
|
pty := ptytest.New(t)
|
||||||
|
root.SetOutput(pty.Output())
|
||||||
|
root.SetErr(pty.Output())
|
||||||
|
|
||||||
|
errC := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
errC <- root.ExecuteContext(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var (
|
||||||
|
httpAddr string
|
||||||
|
tlsAddr string
|
||||||
|
)
|
||||||
|
// We can't use waitAccessURL as it will only return the HTTP URL.
|
||||||
|
if c.httpListener {
|
||||||
|
const httpLinePrefix = "Started HTTP listener at "
|
||||||
|
pty.ExpectMatch(httpLinePrefix)
|
||||||
|
httpLine := pty.ReadLine()
|
||||||
|
httpAddr = strings.TrimSpace(strings.TrimPrefix(httpLine, httpLinePrefix))
|
||||||
|
require.NotEmpty(t, httpAddr)
|
||||||
|
}
|
||||||
|
if c.tlsListener {
|
||||||
|
const tlsLinePrefix = "Started TLS/HTTPS listener at "
|
||||||
|
pty.ExpectMatch(tlsLinePrefix)
|
||||||
|
tlsLine := pty.ReadLine()
|
||||||
|
tlsAddr = strings.TrimSpace(strings.TrimPrefix(tlsLine, tlsLinePrefix))
|
||||||
|
require.NotEmpty(t, tlsAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify HTTP redirects (or not)
|
||||||
|
if c.httpListener {
|
||||||
|
httpURL, err := url.Parse(httpAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
client := codersdk.New(httpURL)
|
||||||
|
client.HTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
}
|
||||||
|
resp, err := client.Request(ctx, http.MethodGet, "/api/v2/buildinfo", nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if c.expectRedirect == "" {
|
||||||
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
} else {
|
||||||
|
require.Equal(t, http.StatusTemporaryRedirect, resp.StatusCode)
|
||||||
|
require.Equal(t, c.expectRedirect, resp.Header.Get("Location"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify TLS
|
||||||
|
if c.tlsListener {
|
||||||
|
tlsURL, err := url.Parse(tlsAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
client := codersdk.New(tlsURL)
|
||||||
|
client.HTTPClient = &http.Client{
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
//nolint:gosec
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = client.HasFirstUser(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cancelFunc()
|
||||||
|
require.NoError(t, <-errC)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoAddress", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
root, _ := clitest.New(t,
|
||||||
|
"server",
|
||||||
|
"--in-memory",
|
||||||
|
"--http-address", "",
|
||||||
|
"--tls-enable=false",
|
||||||
|
"--tls-address", "",
|
||||||
|
)
|
||||||
|
err := root.ExecuteContext(ctx)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "either HTTP or TLS must be enabled")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("NoTLSAddress", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
root, _ := clitest.New(t,
|
||||||
|
"server",
|
||||||
|
"--in-memory",
|
||||||
|
"--tls-enable=true",
|
||||||
|
"--tls-address", "",
|
||||||
|
)
|
||||||
|
err := root.ExecuteContext(ctx)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "TLS address must be set if TLS is enabled")
|
||||||
|
})
|
||||||
|
|
||||||
|
// DeprecatedAddress is a test for the deprecated --address flag. If
|
||||||
|
// specified, --http-address and --tls-address are both ignored, a warning
|
||||||
|
// is printed, and the server will either be HTTP-only or TLS-only depending
|
||||||
|
// on if --tls-enable is set.
|
||||||
|
t.Run("DeprecatedAddress", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
t.Run("HTTP", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
root, cfg := clitest.New(t,
|
||||||
|
"server",
|
||||||
|
"--in-memory",
|
||||||
|
"--address", ":0",
|
||||||
|
"--access-url", "http://example.com",
|
||||||
|
"--cache-dir", t.TempDir(),
|
||||||
|
)
|
||||||
|
pty := ptytest.New(t)
|
||||||
|
root.SetOutput(pty.Output())
|
||||||
|
root.SetErr(pty.Output())
|
||||||
|
errC := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
errC <- root.ExecuteContext(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
pty.ExpectMatch("--address and -a are deprecated")
|
||||||
|
|
||||||
|
accessURL := waitAccessURL(t, cfg)
|
||||||
|
require.Equal(t, "http", accessURL.Scheme)
|
||||||
|
client := codersdk.New(accessURL)
|
||||||
|
_, err := client.HasFirstUser(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cancelFunc()
|
||||||
|
require.NoError(t, <-errC)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("TLS", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
defer cancelFunc()
|
||||||
|
|
||||||
|
certPath, keyPath := generateTLSCertificate(t)
|
||||||
|
root, cfg := clitest.New(t,
|
||||||
|
"server",
|
||||||
|
"--in-memory",
|
||||||
|
"--address", ":0",
|
||||||
|
"--access-url", "http://example.com",
|
||||||
|
"--tls-enable",
|
||||||
|
"--tls-cert-file", certPath,
|
||||||
|
"--tls-key-file", keyPath,
|
||||||
|
"--cache-dir", t.TempDir(),
|
||||||
|
)
|
||||||
|
pty := ptytest.New(t)
|
||||||
|
root.SetOutput(pty.Output())
|
||||||
|
root.SetErr(pty.Output())
|
||||||
|
errC := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
errC <- root.ExecuteContext(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
|
pty.ExpectMatch("--address and -a are deprecated")
|
||||||
|
|
||||||
|
accessURL := waitAccessURL(t, cfg)
|
||||||
|
require.Equal(t, "https", accessURL.Scheme)
|
||||||
|
client := codersdk.New(accessURL)
|
||||||
|
client.HTTPClient = &http.Client{
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{
|
||||||
|
//nolint:gosec
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := client.HasFirstUser(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cancelFunc()
|
||||||
|
require.NoError(t, <-errC)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// This cannot be ran in parallel because it uses a signal.
|
// This cannot be ran in parallel because it uses a signal.
|
||||||
//nolint:paralleltest
|
//nolint:paralleltest
|
||||||
t.Run("Shutdown", func(t *testing.T) {
|
t.Run("Shutdown", func(t *testing.T) {
|
||||||
@ -456,7 +788,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--provisioner-daemons", "1",
|
"--provisioner-daemons", "1",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
@ -483,7 +815,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, _ := clitest.New(t,
|
root, _ := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--trace=true",
|
"--trace=true",
|
||||||
"--cache-dir", t.TempDir(),
|
"--cache-dir", t.TempDir(),
|
||||||
@ -521,7 +853,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, _ := clitest.New(t,
|
root, _ := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--telemetry",
|
"--telemetry",
|
||||||
"--telemetry-url", server.URL,
|
"--telemetry-url", server.URL,
|
||||||
@ -552,7 +884,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--provisioner-daemons", "1",
|
"--provisioner-daemons", "1",
|
||||||
"--prometheus-enable",
|
"--prometheus-enable",
|
||||||
@ -605,7 +937,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--oauth2-github-allow-everyone",
|
"--oauth2-github-allow-everyone",
|
||||||
"--oauth2-github-client-id", "fake",
|
"--oauth2-github-client-id", "fake",
|
||||||
@ -646,7 +978,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
)
|
)
|
||||||
serverErr := make(chan error, 1)
|
serverErr := make(chan error, 1)
|
||||||
@ -674,7 +1006,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--api-rate-limit", val,
|
"--api-rate-limit", val,
|
||||||
)
|
)
|
||||||
@ -702,7 +1034,7 @@ func TestServer(t *testing.T) {
|
|||||||
root, cfg := clitest.New(t,
|
root, cfg := clitest.New(t,
|
||||||
"server",
|
"server",
|
||||||
"--in-memory",
|
"--in-memory",
|
||||||
"--address", ":0",
|
"--http-address", ":0",
|
||||||
"--access-url", "http://example.com",
|
"--access-url", "http://example.com",
|
||||||
"--api-rate-limit", "-1",
|
"--api-rate-limit", "-1",
|
||||||
)
|
)
|
||||||
|
17
cli/testdata/coder_server_--help.golden
vendored
17
cli/testdata/coder_server_--help.golden
vendored
@ -14,9 +14,6 @@ Flags:
|
|||||||
This must be accessible by all
|
This must be accessible by all
|
||||||
provisioned workspaces.
|
provisioned workspaces.
|
||||||
Consumes $CODER_ACCESS_URL
|
Consumes $CODER_ACCESS_URL
|
||||||
-a, --address string Bind address of the server.
|
|
||||||
Consumes $CODER_ADDRESS (default
|
|
||||||
"127.0.0.1:3000")
|
|
||||||
--api-rate-limit int Maximum number of requests per minute
|
--api-rate-limit int Maximum number of requests per minute
|
||||||
allowed to the API per user, or per IP
|
allowed to the API per user, or per IP
|
||||||
address for unauthenticated users.
|
address for unauthenticated users.
|
||||||
@ -65,6 +62,10 @@ Flags:
|
|||||||
production.
|
production.
|
||||||
Consumes $CODER_EXPERIMENTAL
|
Consumes $CODER_EXPERIMENTAL
|
||||||
-h, --help help for server
|
-h, --help help for server
|
||||||
|
--http-address string HTTP bind address of the server. Unset to
|
||||||
|
disable the HTTP endpoint.
|
||||||
|
Consumes $CODER_HTTP_ADDRESS (default
|
||||||
|
"127.0.0.1:3000")
|
||||||
--max-token-lifetime duration The maximum lifetime duration for any
|
--max-token-lifetime duration The maximum lifetime duration for any
|
||||||
user creating a token.
|
user creating a token.
|
||||||
Consumes $CODER_MAX_TOKEN_LIFETIME
|
Consumes $CODER_MAX_TOKEN_LIFETIME
|
||||||
@ -178,6 +179,9 @@ Flags:
|
|||||||
product. Disabling telemetry also
|
product. Disabling telemetry also
|
||||||
disables this option.
|
disables this option.
|
||||||
Consumes $CODER_TELEMETRY_TRACE
|
Consumes $CODER_TELEMETRY_TRACE
|
||||||
|
--tls-address string HTTPS bind address of the server.
|
||||||
|
Consumes $CODER_TLS_ADDRESS (default
|
||||||
|
"127.0.0.1:3443")
|
||||||
--tls-cert-file strings Path to each certificate for TLS. It
|
--tls-cert-file strings Path to each certificate for TLS. It
|
||||||
requires a PEM-encoded file. To configure
|
requires a PEM-encoded file. To configure
|
||||||
the listener to use a CA certificate,
|
the listener to use a CA certificate,
|
||||||
@ -216,6 +220,13 @@ Flags:
|
|||||||
"tls12" or "tls13"
|
"tls12" or "tls13"
|
||||||
Consumes $CODER_TLS_MIN_VERSION (default
|
Consumes $CODER_TLS_MIN_VERSION (default
|
||||||
"tls12")
|
"tls12")
|
||||||
|
--tls-redirect-http-to-https Whether HTTP requests will be redirected
|
||||||
|
to the access URL (if it's a https URL
|
||||||
|
and TLS is enabled). Requests to local IP
|
||||||
|
addresses are never redirected regardless
|
||||||
|
of this setting.
|
||||||
|
Consumes $CODER_TLS_REDIRECT_HTTP
|
||||||
|
(default true)
|
||||||
--trace Whether application tracing data is
|
--trace Whether application tracing data is
|
||||||
collected. It exports to a backend
|
collected. It exports to a backend
|
||||||
configured by environment variables. See:
|
configured by environment variables. See:
|
||||||
|
@ -11,9 +11,11 @@ import (
|
|||||||
|
|
||||||
// DeploymentConfig is the central configuration for the coder server.
|
// DeploymentConfig is the central configuration for the coder server.
|
||||||
type DeploymentConfig struct {
|
type DeploymentConfig struct {
|
||||||
AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"`
|
AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"`
|
||||||
WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"`
|
WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"`
|
||||||
|
// DEPRECATED: Use HTTPAddress or TLS.Address instead.
|
||||||
Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
|
Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
|
||||||
|
HTTPAddress *DeploymentConfigField[string] `json:"http_address" typescript:",notnull"`
|
||||||
AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"`
|
AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"`
|
||||||
DERP *DERP `json:"derp" typescript:",notnull"`
|
DERP *DERP `json:"derp" typescript:",notnull"`
|
||||||
GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"`
|
GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"`
|
||||||
@ -106,6 +108,8 @@ type TelemetryConfig struct {
|
|||||||
|
|
||||||
type TLSConfig struct {
|
type TLSConfig struct {
|
||||||
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
|
Enable *DeploymentConfigField[bool] `json:"enable" typescript:",notnull"`
|
||||||
|
Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"`
|
||||||
|
RedirectHTTP *DeploymentConfigField[bool] `json:"redirect_http" typescript:",notnull"`
|
||||||
CertFiles *DeploymentConfigField[[]string] `json:"cert_file" typescript:",notnull"`
|
CertFiles *DeploymentConfigField[[]string] `json:"cert_file" typescript:",notnull"`
|
||||||
ClientAuth *DeploymentConfigField[string] `json:"client_auth" typescript:",notnull"`
|
ClientAuth *DeploymentConfigField[string] `json:"client_auth" typescript:",notnull"`
|
||||||
ClientCAFile *DeploymentConfigField[string] `json:"client_ca_file" typescript:",notnull"`
|
ClientCAFile *DeploymentConfigField[string] `json:"client_ca_file" typescript:",notnull"`
|
||||||
|
@ -43,43 +43,42 @@ Coder Docker image URI
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Coder listen port (must be > 1024)
|
Coder TLS enabled.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "coder.port" }}
|
{{- define "coder.tlsEnabled" -}}
|
||||||
{{- if .Values.coder.tls.secretNames -}}
|
{{- if .Values.coder.tls.secretNames -}}
|
||||||
8443
|
true
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
8080
|
false
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Coder service port
|
Coder TLS environment variables.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "coder.servicePort" }}
|
{{- define "coder.tlsEnv" }}
|
||||||
{{- if .Values.coder.tls.secretNames -}}
|
{{- if eq (include "coder.tlsEnabled" .) "true" }}
|
||||||
443
|
- name: CODER_TLS_ENABLE
|
||||||
{{- else -}}
|
value: "true"
|
||||||
80
|
- name: CODER_TLS_ADDRESS
|
||||||
{{- end -}}
|
value: "0.0.0.0:8443"
|
||||||
|
- name: CODER_TLS_CERT_FILE
|
||||||
|
value: "{{ range $idx, $secretName := .Values.coder.tls.secretNames -}}{{ if $idx }},{{ end }}/etc/ssl/certs/coder/{{ $secretName }}/tls.crt{{- end }}"
|
||||||
|
- name: CODER_TLS_KEY_FILE
|
||||||
|
value: "{{ range $idx, $secretName := .Values.coder.tls.secretNames -}}{{ if $idx }},{{ end }}/etc/ssl/certs/coder/{{ $secretName }}/tls.key{{- end }}"
|
||||||
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Port name
|
Coder default access URL
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "coder.portName" }}
|
{{- define "coder.defaultAccessURL" }}
|
||||||
{{- if .Values.coder.tls.secretNames -}}
|
{{- if eq (include "coder.tlsEnabled" .) "true" -}}
|
||||||
https
|
https
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
http
|
http
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end }}
|
://coder.{{ .Release.Namespace }}.svc.cluster.local
|
||||||
|
|
||||||
{{/*
|
|
||||||
Scheme
|
|
||||||
*/}}
|
|
||||||
{{- define "coder.scheme" }}
|
|
||||||
{{- include "coder.portName" . | upper -}}
|
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
@ -139,20 +138,6 @@ volumeMounts: []
|
|||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
|
||||||
Coder TLS environment variables.
|
|
||||||
*/}}
|
|
||||||
{{- define "coder.tlsEnv" }}
|
|
||||||
{{- if .Values.coder.tls.secretNames }}
|
|
||||||
- name: CODER_TLS_ENABLE
|
|
||||||
value: "true"
|
|
||||||
- name: CODER_TLS_CERT_FILE
|
|
||||||
value: "{{ range $idx, $secretName := .Values.coder.tls.secretNames -}}{{ if $idx }},{{ end }}/etc/ssl/certs/coder/{{ $secretName }}/tls.crt{{- end }}"
|
|
||||||
- name: CODER_TLS_KEY_FILE
|
|
||||||
value: "{{ range $idx, $secretName := .Values.coder.tls.secretNames -}}{{ if $idx }},{{ end }}/etc/ssl/certs/coder/{{ $secretName }}/tls.key{{- end }}"
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
Coder ingress wildcard hostname with the wildcard suffix stripped.
|
Coder ingress wildcard hostname with the wildcard suffix stripped.
|
||||||
*/}}
|
*/}}
|
||||||
|
@ -52,8 +52,10 @@ spec:
|
|||||||
resources:
|
resources:
|
||||||
{{- toYaml .Values.coder.resources | nindent 12 }}
|
{{- toYaml .Values.coder.resources | nindent 12 }}
|
||||||
env:
|
env:
|
||||||
- name: CODER_ADDRESS
|
- name: CODER_HTTP_ADDRESS
|
||||||
value: "0.0.0.0:{{ include "coder.port" . }}"
|
value: "0.0.0.0:8080"
|
||||||
|
- name: CODER_PROMETHEUS_ADDRESS
|
||||||
|
value: "0.0.0.0:6060"
|
||||||
# Set the default access URL so a `helm apply` works by default.
|
# Set the default access URL so a `helm apply` works by default.
|
||||||
# See: https://github.com/coder/coder/issues/5024
|
# See: https://github.com/coder/coder/issues/5024
|
||||||
{{- $hasAccessURL := false }}
|
{{- $hasAccessURL := false }}
|
||||||
@ -64,7 +66,7 @@ spec:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if not $hasAccessURL }}
|
{{- if not $hasAccessURL }}
|
||||||
- name: CODER_ACCESS_URL
|
- name: CODER_ACCESS_URL
|
||||||
value: "{{ include "coder.portName" . }}://coder.{{.Release.Namespace}}.svc.cluster.local"
|
value: {{ include "coder.defaultAccessURL" . | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
# Used for inter-pod communication with high-availability.
|
# Used for inter-pod communication with high-availability.
|
||||||
- name: KUBE_POD_IP
|
- name: KUBE_POD_IP
|
||||||
@ -72,38 +74,37 @@ spec:
|
|||||||
fieldRef:
|
fieldRef:
|
||||||
fieldPath: status.podIP
|
fieldPath: status.podIP
|
||||||
- name: CODER_DERP_SERVER_RELAY_URL
|
- name: CODER_DERP_SERVER_RELAY_URL
|
||||||
value: "{{ include "coder.portName" . }}://$(KUBE_POD_IP):{{ include "coder.port" . }}"
|
value: "http://$(KUBE_POD_IP):8080"
|
||||||
{{- include "coder.tlsEnv" . | nindent 12 }}
|
{{- include "coder.tlsEnv" . | nindent 12 }}
|
||||||
{{- with .Values.coder.env -}}
|
{{- with .Values.coder.env -}}
|
||||||
{{ toYaml . | nindent 12 }}
|
{{ toYaml . | nindent 12 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- range .Values.coder.env }}
|
|
||||||
{{- if eq .name "CODER_PROMETHEUS_ADDRESS" }}
|
|
||||||
- name: CODER_PROMETHEUS_ENABLE
|
|
||||||
value: "true"
|
|
||||||
{{- end }}
|
|
||||||
{{- end }}
|
|
||||||
ports:
|
ports:
|
||||||
- name: {{ include "coder.portName" . | quote }}
|
- name: "http"
|
||||||
containerPort: {{ include "coder.port" . }}
|
containerPort: 8080
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
{{- if eq (include "coder.tlsEnabled" .) "true" }}
|
||||||
|
- name: "https"
|
||||||
|
containerPort: 8443
|
||||||
|
protocol: TCP
|
||||||
|
{{- end }}
|
||||||
{{- range .Values.coder.env }}
|
{{- range .Values.coder.env }}
|
||||||
{{- if eq .name "CODER_PROMETHEUS_ADDRESS" }}
|
{{- if and (eq .name "CODER_PROMETHEUS_ENABLE") (eq .value "true") }}
|
||||||
- name: "prometheus-http"
|
- name: "prometheus-http"
|
||||||
containerPort: {{ (splitList ":" .value) | last }}
|
containerPort: 6060
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /api/v2/buildinfo
|
path: /api/v2/buildinfo
|
||||||
port: {{ include "coder.portName" . | quote }}
|
port: "http"
|
||||||
scheme: {{ include "coder.scheme" . | quote }}
|
scheme: "HTTP"
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /api/v2/buildinfo
|
path: /api/v2/buildinfo
|
||||||
port: {{ include "coder.portName" . | quote }}
|
port: "http"
|
||||||
scheme: {{ include "coder.scheme" . | quote }}
|
scheme: "HTTP"
|
||||||
{{- include "coder.volumeMounts" . | nindent 10 }}
|
{{- include "coder.volumeMounts" . | nindent 10 }}
|
||||||
|
|
||||||
{{- include "coder.volumes" . | nindent 6 }}
|
{{- include "coder.volumes" . | nindent 6 }}
|
||||||
|
@ -25,7 +25,7 @@ spec:
|
|||||||
service:
|
service:
|
||||||
name: coder
|
name: coder
|
||||||
port:
|
port:
|
||||||
name: {{ include "coder.portName" . | quote }}
|
name: "http"
|
||||||
|
|
||||||
{{- if .Values.coder.ingress.wildcardHost }}
|
{{- if .Values.coder.ingress.wildcardHost }}
|
||||||
- host: {{ include "coder.ingressWildcardHost" . | quote }}
|
- host: {{ include "coder.ingressWildcardHost" . | quote }}
|
||||||
@ -37,7 +37,7 @@ spec:
|
|||||||
service:
|
service:
|
||||||
name: coder
|
name: coder
|
||||||
port:
|
port:
|
||||||
name: {{ include "coder.portName" . | quote }}
|
name: "http"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{- if .Values.coder.ingress.tls.enable }}
|
{{- if .Values.coder.ingress.tls.enable }}
|
||||||
|
@ -12,10 +12,16 @@ spec:
|
|||||||
type: {{ .Values.coder.service.type }}
|
type: {{ .Values.coder.service.type }}
|
||||||
sessionAffinity: ClientIP
|
sessionAffinity: ClientIP
|
||||||
ports:
|
ports:
|
||||||
- name: {{ include "coder.portName" . | quote }}
|
- name: "http"
|
||||||
port: {{ include "coder.servicePort" . }}
|
port: 80
|
||||||
targetPort: {{ include "coder.portName" . | quote }}
|
targetPort: "http"
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
{{- if eq (include "coder.tlsEnabled" .) "true" }}
|
||||||
|
- name: "https"
|
||||||
|
port: 443
|
||||||
|
targetPort: "https"
|
||||||
|
protocol: TCP
|
||||||
|
{{- end }}
|
||||||
{{- if eq "LoadBalancer" .Values.coder.service.type }}
|
{{- if eq "LoadBalancer" .Values.coder.service.type }}
|
||||||
{{- with .Values.coder.service.loadBalancerIP }}
|
{{- with .Values.coder.service.loadBalancerIP }}
|
||||||
loadBalancerIP: {{ . | quote }}
|
loadBalancerIP: {{ . | quote }}
|
||||||
|
@ -45,11 +45,13 @@ coder:
|
|||||||
# for information about what environment variables can be set.
|
# for information about what environment variables can be set.
|
||||||
# Note: The following environment variables are set by default and cannot be
|
# Note: The following environment variables are set by default and cannot be
|
||||||
# overridden:
|
# overridden:
|
||||||
# - CODER_ADDRESS: set to 0.0.0.0:80 and cannot be changed.
|
# - CODER_HTTP_ADDRESS: set to 0.0.0.0:8080 and cannot be changed.
|
||||||
|
# - CODER_TLS_ADDRESS: set to 0.0.0.0:8443 if tls.secretName is not empty.
|
||||||
# - CODER_TLS_ENABLE: set if tls.secretName is not empty.
|
# - CODER_TLS_ENABLE: set if tls.secretName is not empty.
|
||||||
# - CODER_TLS_CERT_FILE: set if tls.secretName is not empty.
|
# - CODER_TLS_CERT_FILE: set if tls.secretName is not empty.
|
||||||
# - CODER_TLS_KEY_FILE: set if tls.secretName is not empty.
|
# - CODER_TLS_KEY_FILE: set if tls.secretName is not empty.
|
||||||
# - CODER_PROMETHEUS_ENABLE: set if CODER_PROMETHEUS_ADDRESS is not empty.
|
# - CODER_PROMETHEUS_ADDRESS: set to 0.0.0.0:6060 and cannot be changed.
|
||||||
|
# Prometheus must still be enabled by setting CODER_PROMETHEUS_ENABLE.
|
||||||
env: []
|
env: []
|
||||||
|
|
||||||
# coder.tls -- The TLS configuration for Coder.
|
# coder.tls -- The TLS configuration for Coder.
|
||||||
|
@ -182,6 +182,74 @@ func (p *PTY) ExpectMatch(str string) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PTY) ReadLine() string {
|
||||||
|
p.t.Helper()
|
||||||
|
|
||||||
|
// timeout, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
|
||||||
|
timeout, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
match := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
defer close(match)
|
||||||
|
match <- func() error {
|
||||||
|
for {
|
||||||
|
r, _, err := p.runeReader.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if r == '\n' {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if r == '\r' {
|
||||||
|
// Peek the next rune to see if it's an LF and then consume
|
||||||
|
// it.
|
||||||
|
|
||||||
|
// Unicode code points can be up to 4 bytes, but the
|
||||||
|
// ones we're looking for are only 1 byte.
|
||||||
|
b, _ := p.runeReader.Peek(1)
|
||||||
|
if len(b) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r, _ = utf8.DecodeRune(b)
|
||||||
|
if r == '\n' {
|
||||||
|
_, _, err = p.runeReader.ReadRune()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = buffer.WriteRune(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-match:
|
||||||
|
if err != nil {
|
||||||
|
p.fatalf("read error", "%v (wanted newline; got %q)", err, buffer.String())
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
p.logf("matched newline = %q", buffer.String())
|
||||||
|
return buffer.String()
|
||||||
|
case <-timeout.Done():
|
||||||
|
// Ensure goroutine is cleaned up before test exit.
|
||||||
|
_ = p.close("expect match timeout")
|
||||||
|
<-match
|
||||||
|
|
||||||
|
p.fatalf("match exceeded deadline", "wanted newline; got %q", buffer.String())
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *PTY) Write(r rune) {
|
func (p *PTY) Write(r rune) {
|
||||||
p.t.Helper()
|
p.t.Helper()
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package ptytest_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -21,6 +22,23 @@ func TestPtytest(t *testing.T) {
|
|||||||
pty.WriteLine("read")
|
pty.WriteLine("read")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("ReadLine", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("ReadLine is glitchy on windows when it comes to the final line of output it seems")
|
||||||
|
}
|
||||||
|
|
||||||
|
pty := ptytest.New(t)
|
||||||
|
|
||||||
|
// The PTY expands these to \r\n (even on linux).
|
||||||
|
pty.Output().Write([]byte("line 1\nline 2\nline 3\nline 4\nline 5"))
|
||||||
|
require.Equal(t, "line 1", pty.ReadLine())
|
||||||
|
require.Equal(t, "line 2", pty.ReadLine())
|
||||||
|
require.Equal(t, "line 3", pty.ReadLine())
|
||||||
|
require.Equal(t, "line 4", pty.ReadLine())
|
||||||
|
require.Equal(t, "line 5", pty.ExpectMatch("5"))
|
||||||
|
})
|
||||||
|
|
||||||
// See https://github.com/coder/coder/issues/2122 for the motivation
|
// See https://github.com/coder/coder/issues/2122 for the motivation
|
||||||
// behind this test.
|
// behind this test.
|
||||||
t.Run("Cobra ptytest should not hang when output is not consumed", func(t *testing.T) {
|
t.Run("Cobra ptytest should not hang when output is not consumed", func(t *testing.T) {
|
||||||
|
@ -121,7 +121,7 @@ fatal() {
|
|||||||
trap 'fatal "Script encountered an error"' ERR
|
trap 'fatal "Script encountered an error"' ERR
|
||||||
|
|
||||||
cdroot
|
cdroot
|
||||||
start_cmd API "" "${CODER_DEV_SHIM}" server --address 0.0.0.0:3000
|
start_cmd API "" "${CODER_DEV_SHIM}" server --http-address 0.0.0.0:3000
|
||||||
|
|
||||||
echo '== Waiting for Coder to become ready'
|
echo '== Waiting for Coder to become ready'
|
||||||
# Start the timeout in the background so interrupting this script
|
# Start the timeout in the background so interrupting this script
|
||||||
|
@ -277,6 +277,7 @@ export interface DeploymentConfig {
|
|||||||
readonly access_url: DeploymentConfigField<string>
|
readonly access_url: DeploymentConfigField<string>
|
||||||
readonly wildcard_access_url: DeploymentConfigField<string>
|
readonly wildcard_access_url: DeploymentConfigField<string>
|
||||||
readonly address: DeploymentConfigField<string>
|
readonly address: DeploymentConfigField<string>
|
||||||
|
readonly http_address: DeploymentConfigField<string>
|
||||||
readonly autobuild_poll_interval: DeploymentConfigField<number>
|
readonly autobuild_poll_interval: DeploymentConfigField<number>
|
||||||
readonly derp: DERP
|
readonly derp: DERP
|
||||||
readonly gitauth: DeploymentConfigField<GitAuthConfig[]>
|
readonly gitauth: DeploymentConfigField<GitAuthConfig[]>
|
||||||
@ -618,6 +619,8 @@ export interface ServiceBanner {
|
|||||||
// From codersdk/deploymentconfig.go
|
// From codersdk/deploymentconfig.go
|
||||||
export interface TLSConfig {
|
export interface TLSConfig {
|
||||||
readonly enable: DeploymentConfigField<boolean>
|
readonly enable: DeploymentConfigField<boolean>
|
||||||
|
readonly address: DeploymentConfigField<string>
|
||||||
|
readonly redirect_http: DeploymentConfigField<boolean>
|
||||||
readonly cert_file: DeploymentConfigField<string[]>
|
readonly cert_file: DeploymentConfigField<string[]>
|
||||||
readonly client_auth: DeploymentConfigField<string>
|
readonly client_auth: DeploymentConfigField<string>
|
||||||
readonly client_ca_file: DeploymentConfigField<string>
|
readonly client_ca_file: DeploymentConfigField<string>
|
||||||
|
Reference in New Issue
Block a user