mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
ci: enable gocognit (#9359)
And, bring the server under 300: * Removed the undocumented "disable" STUN address in favor of the --disable-direct flag.
This commit is contained in:
@@ -190,7 +190,6 @@ func sshPrepareWorkspaceConfigs(ctx context.Context, client *codersdk.Client) (r
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (r *RootCmd) configSSH() *clibase.Cmd {
|
||||
var (
|
||||
sshConfigFile string
|
||||
|
||||
@@ -831,6 +831,13 @@ func (r *RootCmd) checkWarnings(i *clibase.Invocation, client *codersdk.Client)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Verbosef logs a message if verbose mode is enabled.
|
||||
func (r *RootCmd) Verbosef(inv *clibase.Invocation, fmtStr string, args ...interface{}) {
|
||||
if r.verbose {
|
||||
cliui.Infof(inv.Stdout, fmtStr, args...)
|
||||
}
|
||||
}
|
||||
|
||||
type headerTransport struct {
|
||||
transport http.RoundTripper
|
||||
header http.Header
|
||||
|
||||
+246
-228
@@ -176,18 +176,147 @@ func ReadGitAuthProvidersFromEnv(environ []string) ([]codersdk.GitAuthConfig, er
|
||||
return providers, nil
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func createOIDCConfig(ctx context.Context, vals *codersdk.DeploymentValues) (*coderd.OIDCConfig, error) {
|
||||
if vals.OIDC.ClientID == "" {
|
||||
return nil, xerrors.Errorf("OIDC client ID must be set!")
|
||||
}
|
||||
if vals.OIDC.IssuerURL == "" {
|
||||
return nil, xerrors.Errorf("OIDC issuer URL must be set!")
|
||||
}
|
||||
|
||||
oidcProvider, err := oidc.NewProvider(
|
||||
ctx, vals.OIDC.IssuerURL.String(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("configure oidc provider: %w", err)
|
||||
}
|
||||
redirectURL, err := vals.AccessURL.Value().Parse("/api/v2/users/oidc/callback")
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse oidc oauth callback url: %w", err)
|
||||
}
|
||||
// If the scopes contain 'groups', we enable group support.
|
||||
// Do not override any custom value set by the user.
|
||||
if slice.Contains(vals.OIDC.Scopes, "groups") && vals.OIDC.GroupField == "" {
|
||||
vals.OIDC.GroupField = "groups"
|
||||
}
|
||||
oauthCfg := &oauth2.Config{
|
||||
ClientID: vals.OIDC.ClientID.String(),
|
||||
ClientSecret: vals.OIDC.ClientSecret.String(),
|
||||
RedirectURL: redirectURL.String(),
|
||||
Endpoint: oidcProvider.Endpoint(),
|
||||
Scopes: vals.OIDC.Scopes,
|
||||
}
|
||||
|
||||
var useCfg httpmw.OAuth2Config = oauthCfg
|
||||
if vals.OIDC.ClientKeyFile != "" {
|
||||
// PKI authentication is done in the params. If a
|
||||
// counter example is found, we can add a config option to
|
||||
// change this.
|
||||
oauthCfg.Endpoint.AuthStyle = oauth2.AuthStyleInParams
|
||||
if vals.OIDC.ClientSecret != "" {
|
||||
return nil, xerrors.Errorf("cannot specify both oidc client secret and oidc client key file")
|
||||
}
|
||||
|
||||
pkiCfg, err := configureOIDCPKI(oauthCfg, vals.OIDC.ClientKeyFile.Value(), vals.OIDC.ClientCertFile.Value())
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("configure oauth pki authentication: %w", err)
|
||||
}
|
||||
useCfg = pkiCfg
|
||||
}
|
||||
return &coderd.OIDCConfig{
|
||||
OAuth2Config: useCfg,
|
||||
Provider: oidcProvider,
|
||||
Verifier: oidcProvider.Verifier(&oidc.Config{
|
||||
ClientID: vals.OIDC.ClientID.String(),
|
||||
}),
|
||||
EmailDomain: vals.OIDC.EmailDomain,
|
||||
AllowSignups: vals.OIDC.AllowSignups.Value(),
|
||||
UsernameField: vals.OIDC.UsernameField.String(),
|
||||
EmailField: vals.OIDC.EmailField.String(),
|
||||
AuthURLParams: vals.OIDC.AuthURLParams.Value,
|
||||
IgnoreUserInfo: vals.OIDC.IgnoreUserInfo.Value(),
|
||||
GroupField: vals.OIDC.GroupField.String(),
|
||||
GroupFilter: vals.OIDC.GroupRegexFilter.Value(),
|
||||
CreateMissingGroups: vals.OIDC.GroupAutoCreate.Value(),
|
||||
GroupMapping: vals.OIDC.GroupMapping.Value,
|
||||
UserRoleField: vals.OIDC.UserRoleField.String(),
|
||||
UserRoleMapping: vals.OIDC.UserRoleMapping.Value,
|
||||
UserRolesDefault: vals.OIDC.UserRolesDefault.GetSlice(),
|
||||
SignInText: vals.OIDC.SignInText.String(),
|
||||
IconURL: vals.OIDC.IconURL.String(),
|
||||
IgnoreEmailVerified: vals.OIDC.IgnoreEmailVerified.Value(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func afterCtx(ctx context.Context, fn func()) {
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
|
||||
func enablePrometheus(
|
||||
ctx context.Context,
|
||||
logger slog.Logger,
|
||||
vals *codersdk.DeploymentValues,
|
||||
options *coderd.Options,
|
||||
) (closeFn func(), err error) {
|
||||
options.PrometheusRegistry.MustRegister(collectors.NewGoCollector())
|
||||
options.PrometheusRegistry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
|
||||
|
||||
closeUsersFunc, err := prometheusmetrics.ActiveUsers(ctx, options.PrometheusRegistry, options.Database, 0)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("register active users prometheus metric: %w", err)
|
||||
}
|
||||
afterCtx(ctx, closeUsersFunc)
|
||||
|
||||
closeWorkspacesFunc, err := prometheusmetrics.Workspaces(ctx, options.PrometheusRegistry, options.Database, 0)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("register workspaces prometheus metric: %w", err)
|
||||
}
|
||||
afterCtx(ctx, closeWorkspacesFunc)
|
||||
|
||||
if vals.Prometheus.CollectAgentStats {
|
||||
closeAgentStatsFunc, err := prometheusmetrics.AgentStats(ctx, logger, options.PrometheusRegistry, options.Database, time.Now(), 0)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("register agent stats prometheus metric: %w", err)
|
||||
}
|
||||
afterCtx(ctx, closeAgentStatsFunc)
|
||||
|
||||
metricsAggregator, err := prometheusmetrics.NewMetricsAggregator(logger, options.PrometheusRegistry, 0)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't initialize metrics aggregator: %w", err)
|
||||
}
|
||||
|
||||
cancelMetricsAggregator := metricsAggregator.Run(ctx)
|
||||
afterCtx(ctx, cancelMetricsAggregator)
|
||||
|
||||
options.UpdateAgentMetrics = metricsAggregator.Update
|
||||
err = options.PrometheusRegistry.Register(metricsAggregator)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("can't register metrics aggregator as collector: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
return ServeHandler(
|
||||
ctx, logger, promhttp.InstrumentMetricHandler(
|
||||
options.PrometheusRegistry, promhttp.HandlerFor(options.PrometheusRegistry, promhttp.HandlerOpts{}),
|
||||
), vals.Prometheus.Address.String(), "prometheus",
|
||||
), nil
|
||||
}
|
||||
|
||||
func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, io.Closer, error)) *clibase.Cmd {
|
||||
var (
|
||||
cfg = new(codersdk.DeploymentValues)
|
||||
opts = cfg.Options()
|
||||
vals = new(codersdk.DeploymentValues)
|
||||
opts = vals.Options()
|
||||
)
|
||||
serverCmd := &clibase.Cmd{
|
||||
Use: "server",
|
||||
Short: "Start a Coder server",
|
||||
Options: opts,
|
||||
Middleware: clibase.Chain(
|
||||
WriteConfigMW(cfg),
|
||||
WriteConfigMW(vals),
|
||||
PrintDeprecatedOptions(),
|
||||
clibase.RequireNArgs(0),
|
||||
),
|
||||
@@ -197,32 +326,32 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
ctx, cancel := context.WithCancel(inv.Context())
|
||||
defer cancel()
|
||||
|
||||
if cfg.Config != "" {
|
||||
if vals.Config != "" {
|
||||
cliui.Warnf(inv.Stderr, "YAML support is experimental and offers no compatibility guarantees.")
|
||||
}
|
||||
|
||||
go DumpHandler(ctx)
|
||||
|
||||
// Validate bind addresses.
|
||||
if cfg.Address.String() != "" {
|
||||
if cfg.TLS.Enable {
|
||||
cfg.HTTPAddress = ""
|
||||
cfg.TLS.Address = cfg.Address
|
||||
if vals.Address.String() != "" {
|
||||
if vals.TLS.Enable {
|
||||
vals.HTTPAddress = ""
|
||||
vals.TLS.Address = vals.Address
|
||||
} else {
|
||||
_ = cfg.HTTPAddress.Set(cfg.Address.String())
|
||||
cfg.TLS.Address.Host = ""
|
||||
cfg.TLS.Address.Port = ""
|
||||
_ = vals.HTTPAddress.Set(vals.Address.String())
|
||||
vals.TLS.Address.Host = ""
|
||||
vals.TLS.Address.Port = ""
|
||||
}
|
||||
}
|
||||
if cfg.TLS.Enable && cfg.TLS.Address.String() == "" {
|
||||
if vals.TLS.Enable && vals.TLS.Address.String() == "" {
|
||||
return xerrors.Errorf("TLS address must be set if TLS is enabled")
|
||||
}
|
||||
if !cfg.TLS.Enable && cfg.HTTPAddress.String() == "" {
|
||||
if !vals.TLS.Enable && vals.HTTPAddress.String() == "" {
|
||||
return xerrors.Errorf("TLS is disabled. Enable with --tls-enable or specify a HTTP address")
|
||||
}
|
||||
|
||||
if cfg.AccessURL.String() != "" &&
|
||||
!(cfg.AccessURL.Scheme == "http" || cfg.AccessURL.Scheme == "https") {
|
||||
if vals.AccessURL.String() != "" &&
|
||||
!(vals.AccessURL.Scheme == "http" || vals.AccessURL.Scheme == "https") {
|
||||
return xerrors.Errorf("access-url must include a scheme (e.g. 'http://' or 'https://)")
|
||||
}
|
||||
|
||||
@@ -230,14 +359,14 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
// was specified.
|
||||
loginRateLimit := 60
|
||||
filesRateLimit := 12
|
||||
if cfg.RateLimit.DisableAll {
|
||||
cfg.RateLimit.API = -1
|
||||
if vals.RateLimit.DisableAll {
|
||||
vals.RateLimit.API = -1
|
||||
loginRateLimit = -1
|
||||
filesRateLimit = -1
|
||||
}
|
||||
|
||||
PrintLogo(inv, "Coder")
|
||||
logger, logCloser, err := BuildLogger(inv, cfg)
|
||||
logger, logCloser, err := BuildLogger(inv, vals)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("make logger: %w", err)
|
||||
}
|
||||
@@ -260,7 +389,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
notifyCtx, notifyStop := signal.NotifyContext(ctx, InterruptSignals...)
|
||||
defer notifyStop()
|
||||
|
||||
cacheDir := cfg.CacheDir.String()
|
||||
cacheDir := vals.CacheDir.String()
|
||||
err = os.MkdirAll(cacheDir, 0o700)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create cache directory: %w", err)
|
||||
@@ -271,14 +400,14 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
// which is caught by goleaks.
|
||||
defer http.DefaultClient.CloseIdleConnections()
|
||||
|
||||
tracerProvider, sqlDriver, closeTracing := ConfigureTraceProvider(ctx, logger, inv, cfg)
|
||||
tracerProvider, sqlDriver, closeTracing := ConfigureTraceProvider(ctx, logger, inv, vals)
|
||||
defer func() {
|
||||
logger.Debug(ctx, "closing tracing")
|
||||
traceCloseErr := shutdownWithTimeout(closeTracing, 5*time.Second)
|
||||
logger.Debug(ctx, "tracing closed", slog.Error(traceCloseErr))
|
||||
}()
|
||||
|
||||
httpServers, err := ConfigureHTTPServers(inv, cfg)
|
||||
httpServers, err := ConfigureHTTPServers(inv, vals)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("configure http(s): %w", err)
|
||||
}
|
||||
@@ -288,7 +417,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
|
||||
builtinPostgres := false
|
||||
// Only use built-in if PostgreSQL URL isn't specified!
|
||||
if !cfg.InMemoryDatabase && cfg.PostgresURL == "" {
|
||||
if !vals.InMemoryDatabase && vals.PostgresURL == "" {
|
||||
var closeFunc func() error
|
||||
cliui.Infof(inv.Stdout, "Using built-in PostgreSQL (%s)", config.PostgresPath())
|
||||
pgURL, closeFunc, err := startBuiltinPostgres(ctx, config, logger)
|
||||
@@ -296,7 +425,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
return err
|
||||
}
|
||||
|
||||
err = cfg.PostgresURL.Set(pgURL)
|
||||
err = vals.PostgresURL.Set(pgURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -320,9 +449,9 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
|
||||
ctx, httpClient, err := ConfigureHTTPClient(
|
||||
ctx,
|
||||
cfg.TLS.ClientCertFile.String(),
|
||||
cfg.TLS.ClientKeyFile.String(),
|
||||
cfg.TLS.ClientCAFile.String(),
|
||||
vals.TLS.ClientCertFile.String(),
|
||||
vals.TLS.ClientKeyFile.String(),
|
||||
vals.TLS.ClientCAFile.String(),
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("configure http client: %w", err)
|
||||
@@ -334,30 +463,30 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
tunnel *tunnelsdk.Tunnel
|
||||
tunnelDone <-chan struct{} = make(chan struct{}, 1)
|
||||
)
|
||||
if cfg.AccessURL.String() == "" {
|
||||
if vals.AccessURL.String() == "" {
|
||||
cliui.Infof(inv.Stderr, "Opening tunnel so workspaces can connect to your deployment. For production scenarios, specify an external access URL")
|
||||
tunnel, err = devtunnel.New(ctx, logger.Named("net.devtunnel"), cfg.WgtunnelHost.String())
|
||||
tunnel, err = devtunnel.New(ctx, logger.Named("net.devtunnel"), vals.WgtunnelHost.String())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create tunnel: %w", err)
|
||||
}
|
||||
defer tunnel.Close()
|
||||
tunnelDone = tunnel.Wait()
|
||||
cfg.AccessURL = clibase.URL(*tunnel.URL)
|
||||
vals.AccessURL = clibase.URL(*tunnel.URL)
|
||||
|
||||
if cfg.WildcardAccessURL.String() == "" {
|
||||
if vals.WildcardAccessURL.String() == "" {
|
||||
// Suffixed wildcard access URL.
|
||||
u, err := url.Parse(fmt.Sprintf("*--%s", tunnel.URL.Hostname()))
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse wildcard url: %w", err)
|
||||
}
|
||||
cfg.WildcardAccessURL = clibase.URL(*u)
|
||||
vals.WildcardAccessURL = clibase.URL(*u)
|
||||
}
|
||||
}
|
||||
|
||||
_, accessURLPortRaw, _ := net.SplitHostPort(cfg.AccessURL.Host)
|
||||
_, accessURLPortRaw, _ := net.SplitHostPort(vals.AccessURL.Host)
|
||||
if accessURLPortRaw == "" {
|
||||
accessURLPortRaw = "80"
|
||||
if cfg.AccessURL.Scheme == "https" {
|
||||
if vals.AccessURL.Scheme == "https" {
|
||||
accessURLPortRaw = "443"
|
||||
}
|
||||
}
|
||||
@@ -367,8 +496,8 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
return xerrors.Errorf("parse access URL port: %w", err)
|
||||
}
|
||||
|
||||
// Warn the user if the access URL appears to be a loopback address.
|
||||
isLocal, err := IsLocalURL(ctx, cfg.AccessURL.Value())
|
||||
// Warn the user if the access URL is loopback or unresolvable.
|
||||
isLocal, err := IsLocalURL(ctx, vals.AccessURL.Value())
|
||||
if isLocal || err != nil {
|
||||
reason := "could not be resolved"
|
||||
if isLocal {
|
||||
@@ -377,12 +506,12 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
cliui.Warnf(
|
||||
inv.Stderr,
|
||||
"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.DefaultStyles.Field.Render(cfg.AccessURL.String()), reason,
|
||||
cliui.DefaultStyles.Field.Render(vals.AccessURL.String()), reason,
|
||||
)
|
||||
}
|
||||
|
||||
// A newline is added before for visibility in terminal output.
|
||||
cliui.Infof(inv.Stdout, "\nView the Web UI: %s", cfg.AccessURL.String())
|
||||
cliui.Infof(inv.Stdout, "\nView the Web UI: %s", vals.AccessURL.String())
|
||||
|
||||
// Used for zero-trust instance identity with Google Cloud.
|
||||
googleTokenValidator, err := idtoken.NewValidator(ctx, option.WithoutAuthentication())
|
||||
@@ -390,51 +519,39 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
return err
|
||||
}
|
||||
|
||||
sshKeygenAlgorithm, err := gitsshkey.ParseAlgorithm(cfg.SSHKeygenAlgorithm.String())
|
||||
sshKeygenAlgorithm, err := gitsshkey.ParseAlgorithm(vals.SSHKeygenAlgorithm.String())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse ssh keygen algorithm %s: %w", cfg.SSHKeygenAlgorithm, err)
|
||||
return xerrors.Errorf("parse ssh keygen algorithm %s: %w", vals.SSHKeygenAlgorithm, err)
|
||||
}
|
||||
|
||||
defaultRegion := &tailcfg.DERPRegion{
|
||||
EmbeddedRelay: true,
|
||||
RegionID: int(cfg.DERP.Server.RegionID.Value()),
|
||||
RegionCode: cfg.DERP.Server.RegionCode.String(),
|
||||
RegionName: cfg.DERP.Server.RegionName.String(),
|
||||
RegionID: int(vals.DERP.Server.RegionID.Value()),
|
||||
RegionCode: vals.DERP.Server.RegionCode.String(),
|
||||
RegionName: vals.DERP.Server.RegionName.String(),
|
||||
Nodes: []*tailcfg.DERPNode{{
|
||||
Name: fmt.Sprintf("%db", cfg.DERP.Server.RegionID),
|
||||
RegionID: int(cfg.DERP.Server.RegionID.Value()),
|
||||
HostName: cfg.AccessURL.Value().Hostname(),
|
||||
Name: fmt.Sprintf("%db", vals.DERP.Server.RegionID),
|
||||
RegionID: int(vals.DERP.Server.RegionID.Value()),
|
||||
HostName: vals.AccessURL.Value().Hostname(),
|
||||
DERPPort: accessURLPort,
|
||||
STUNPort: -1,
|
||||
ForceHTTP: cfg.AccessURL.Scheme == "http",
|
||||
ForceHTTP: vals.AccessURL.Scheme == "http",
|
||||
}},
|
||||
}
|
||||
if !cfg.DERP.Server.Enable {
|
||||
if !vals.DERP.Server.Enable {
|
||||
defaultRegion = nil
|
||||
}
|
||||
|
||||
// HACK: see https://github.com/coder/coder/issues/6791.
|
||||
for _, addr := range cfg.DERP.Server.STUNAddresses {
|
||||
if addr != "disable" {
|
||||
continue
|
||||
}
|
||||
err := cfg.DERP.Server.STUNAddresses.Replace(nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
derpMap, err := tailnet.NewDERPMap(
|
||||
ctx, defaultRegion, cfg.DERP.Server.STUNAddresses,
|
||||
cfg.DERP.Config.URL.String(), cfg.DERP.Config.Path.String(),
|
||||
cfg.DERP.Config.BlockDirect.Value(),
|
||||
ctx, defaultRegion, vals.DERP.Server.STUNAddresses,
|
||||
vals.DERP.Config.URL.String(), vals.DERP.Config.Path.String(),
|
||||
vals.DERP.Config.BlockDirect.Value(),
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create derp map: %w", err)
|
||||
}
|
||||
|
||||
appHostname := cfg.WildcardAccessURL.String()
|
||||
appHostname := vals.WildcardAccessURL.String()
|
||||
var appHostnameRegex *regexp.Regexp
|
||||
if appHostname != "" {
|
||||
appHostnameRegex, err = httpapi.CompileHostnamePattern(appHostname)
|
||||
@@ -448,10 +565,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
return xerrors.Errorf("read git auth providers from env: %w", err)
|
||||
}
|
||||
|
||||
cfg.GitAuthProviders.Value = append(cfg.GitAuthProviders.Value, gitAuthEnv...)
|
||||
vals.GitAuthProviders.Value = append(vals.GitAuthProviders.Value, gitAuthEnv...)
|
||||
gitAuthConfigs, err := gitauth.ConvertConfig(
|
||||
cfg.GitAuthProviders.Value,
|
||||
cfg.AccessURL.Value(),
|
||||
vals.GitAuthProviders.Value,
|
||||
vals.AccessURL.Value(),
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("convert git auth config: %w", err)
|
||||
@@ -463,18 +580,18 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
)
|
||||
}
|
||||
|
||||
realIPConfig, err := httpmw.ParseRealIPConfig(cfg.ProxyTrustedHeaders, cfg.ProxyTrustedOrigins)
|
||||
realIPConfig, err := httpmw.ParseRealIPConfig(vals.ProxyTrustedHeaders, vals.ProxyTrustedOrigins)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse real ip config: %w", err)
|
||||
}
|
||||
|
||||
configSSHOptions, err := cfg.SSHConfig.ParseOptions()
|
||||
configSSHOptions, err := vals.SSHConfig.ParseOptions()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse ssh config options %q: %w", cfg.SSHConfig.SSHConfigOptions.String(), err)
|
||||
return xerrors.Errorf("parse ssh config options %q: %w", vals.SSHConfig.SSHConfigOptions.String(), err)
|
||||
}
|
||||
|
||||
options := &coderd.Options{
|
||||
AccessURL: cfg.AccessURL.Value(),
|
||||
AccessURL: vals.AccessURL.Value(),
|
||||
AppHostname: appHostname,
|
||||
AppHostnameRegex: appHostnameRegex,
|
||||
Logger: logger.Named("coderd"),
|
||||
@@ -485,22 +602,22 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
GoogleTokenValidator: googleTokenValidator,
|
||||
GitAuthConfigs: gitAuthConfigs,
|
||||
RealIPConfig: realIPConfig,
|
||||
SecureAuthCookie: cfg.SecureAuthCookie.Value(),
|
||||
SecureAuthCookie: vals.SecureAuthCookie.Value(),
|
||||
SSHKeygenAlgorithm: sshKeygenAlgorithm,
|
||||
TracerProvider: tracerProvider,
|
||||
Telemetry: telemetry.NewNoop(),
|
||||
MetricsCacheRefreshInterval: cfg.MetricsCacheRefreshInterval.Value(),
|
||||
AgentStatsRefreshInterval: cfg.AgentStatRefreshInterval.Value(),
|
||||
DeploymentValues: cfg,
|
||||
MetricsCacheRefreshInterval: vals.MetricsCacheRefreshInterval.Value(),
|
||||
AgentStatsRefreshInterval: vals.AgentStatRefreshInterval.Value(),
|
||||
DeploymentValues: vals,
|
||||
PrometheusRegistry: prometheus.NewRegistry(),
|
||||
APIRateLimit: int(cfg.RateLimit.API.Value()),
|
||||
APIRateLimit: int(vals.RateLimit.API.Value()),
|
||||
LoginRateLimit: loginRateLimit,
|
||||
FilesRateLimit: filesRateLimit,
|
||||
HTTPClient: httpClient,
|
||||
TemplateScheduleStore: &atomic.Pointer[schedule.TemplateScheduleStore]{},
|
||||
UserQuietHoursScheduleStore: &atomic.Pointer[schedule.UserQuietHoursScheduleStore]{},
|
||||
SSHConfig: codersdk.SSHConfigResponse{
|
||||
HostnamePrefix: cfg.SSHConfig.DeploymentName.String(),
|
||||
HostnamePrefix: vals.SSHConfig.DeploymentName.String(),
|
||||
SSHConfigOptions: configSSHOptions,
|
||||
},
|
||||
}
|
||||
@@ -508,16 +625,16 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
options.TLSCertificates = httpServers.TLSConfig.Certificates
|
||||
}
|
||||
|
||||
if cfg.StrictTransportSecurity > 0 {
|
||||
if vals.StrictTransportSecurity > 0 {
|
||||
options.StrictTransportSecurityCfg, err = httpmw.HSTSConfigOptions(
|
||||
int(cfg.StrictTransportSecurity.Value()), cfg.StrictTransportSecurityOptions,
|
||||
int(vals.StrictTransportSecurity.Value()), vals.StrictTransportSecurityOptions,
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("coderd: setting hsts header failed (options: %v): %w", cfg.StrictTransportSecurityOptions, err)
|
||||
return xerrors.Errorf("coderd: setting hsts header failed (options: %v): %w", vals.StrictTransportSecurityOptions, err)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.UpdateCheck {
|
||||
if vals.UpdateCheck {
|
||||
options.UpdateCheckOptions = &updatecheck.Options{
|
||||
// Avoid spamming GitHub API checking for updates.
|
||||
Interval: 24 * time.Hour,
|
||||
@@ -536,103 +653,39 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.OAuth2.Github.ClientSecret != "" {
|
||||
options.GithubOAuth2Config, err = configureGithubOAuth2(cfg.AccessURL.Value(),
|
||||
cfg.OAuth2.Github.ClientID.String(),
|
||||
cfg.OAuth2.Github.ClientSecret.String(),
|
||||
cfg.OAuth2.Github.AllowSignups.Value(),
|
||||
cfg.OAuth2.Github.AllowEveryone.Value(),
|
||||
cfg.OAuth2.Github.AllowedOrgs,
|
||||
cfg.OAuth2.Github.AllowedTeams,
|
||||
cfg.OAuth2.Github.EnterpriseBaseURL.String(),
|
||||
if vals.OAuth2.Github.ClientSecret != "" {
|
||||
options.GithubOAuth2Config, err = configureGithubOAuth2(vals.AccessURL.Value(),
|
||||
vals.OAuth2.Github.ClientID.String(),
|
||||
vals.OAuth2.Github.ClientSecret.String(),
|
||||
vals.OAuth2.Github.AllowSignups.Value(),
|
||||
vals.OAuth2.Github.AllowEveryone.Value(),
|
||||
vals.OAuth2.Github.AllowedOrgs,
|
||||
vals.OAuth2.Github.AllowedTeams,
|
||||
vals.OAuth2.Github.EnterpriseBaseURL.String(),
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("configure github oauth2: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.OIDC.ClientKeyFile != "" || cfg.OIDC.ClientSecret != "" {
|
||||
if cfg.OIDC.ClientID == "" {
|
||||
return xerrors.Errorf("OIDC client ID must be set!")
|
||||
}
|
||||
if cfg.OIDC.IssuerURL == "" {
|
||||
return xerrors.Errorf("OIDC issuer URL must be set!")
|
||||
}
|
||||
|
||||
if cfg.OIDC.IgnoreEmailVerified {
|
||||
if vals.OIDC.ClientKeyFile != "" || vals.OIDC.ClientSecret != "" {
|
||||
if vals.OIDC.IgnoreEmailVerified {
|
||||
logger.Warn(ctx, "coder will not check email_verified for OIDC logins")
|
||||
}
|
||||
|
||||
oidcProvider, err := oidc.NewProvider(
|
||||
ctx, cfg.OIDC.IssuerURL.String(),
|
||||
)
|
||||
oc, err := createOIDCConfig(ctx, vals)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("configure oidc provider: %w", err)
|
||||
}
|
||||
redirectURL, err := cfg.AccessURL.Value().Parse("/api/v2/users/oidc/callback")
|
||||
if err != nil {
|
||||
return xerrors.Errorf("parse oidc oauth callback url: %w", err)
|
||||
}
|
||||
// If the scopes contain 'groups', we enable group support.
|
||||
// Do not override any custom value set by the user.
|
||||
if slice.Contains(cfg.OIDC.Scopes, "groups") && cfg.OIDC.GroupField == "" {
|
||||
cfg.OIDC.GroupField = "groups"
|
||||
}
|
||||
oauthCfg := &oauth2.Config{
|
||||
ClientID: cfg.OIDC.ClientID.String(),
|
||||
ClientSecret: cfg.OIDC.ClientSecret.String(),
|
||||
RedirectURL: redirectURL.String(),
|
||||
Endpoint: oidcProvider.Endpoint(),
|
||||
Scopes: cfg.OIDC.Scopes,
|
||||
}
|
||||
|
||||
var useCfg httpmw.OAuth2Config = oauthCfg
|
||||
if cfg.OIDC.ClientKeyFile != "" {
|
||||
// PKI authentication is done in the params. If a
|
||||
// counter example is found, we can add a config option to
|
||||
// change this.
|
||||
oauthCfg.Endpoint.AuthStyle = oauth2.AuthStyleInParams
|
||||
if cfg.OIDC.ClientSecret != "" {
|
||||
return xerrors.Errorf("cannot specify both oidc client secret and oidc client key file")
|
||||
}
|
||||
|
||||
pkiCfg, err := configureOIDCPKI(oauthCfg, cfg.OIDC.ClientKeyFile.Value(), cfg.OIDC.ClientCertFile.Value())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("configure oauth pki authentication: %w", err)
|
||||
}
|
||||
useCfg = pkiCfg
|
||||
}
|
||||
options.OIDCConfig = &coderd.OIDCConfig{
|
||||
OAuth2Config: useCfg,
|
||||
Provider: oidcProvider,
|
||||
Verifier: oidcProvider.Verifier(&oidc.Config{
|
||||
ClientID: cfg.OIDC.ClientID.String(),
|
||||
}),
|
||||
EmailDomain: cfg.OIDC.EmailDomain,
|
||||
AllowSignups: cfg.OIDC.AllowSignups.Value(),
|
||||
UsernameField: cfg.OIDC.UsernameField.String(),
|
||||
EmailField: cfg.OIDC.EmailField.String(),
|
||||
AuthURLParams: cfg.OIDC.AuthURLParams.Value,
|
||||
IgnoreUserInfo: cfg.OIDC.IgnoreUserInfo.Value(),
|
||||
GroupField: cfg.OIDC.GroupField.String(),
|
||||
GroupFilter: cfg.OIDC.GroupRegexFilter.Value(),
|
||||
CreateMissingGroups: cfg.OIDC.GroupAutoCreate.Value(),
|
||||
GroupMapping: cfg.OIDC.GroupMapping.Value,
|
||||
UserRoleField: cfg.OIDC.UserRoleField.String(),
|
||||
UserRoleMapping: cfg.OIDC.UserRoleMapping.Value,
|
||||
UserRolesDefault: cfg.OIDC.UserRolesDefault.GetSlice(),
|
||||
SignInText: cfg.OIDC.SignInText.String(),
|
||||
IconURL: cfg.OIDC.IconURL.String(),
|
||||
IgnoreEmailVerified: cfg.OIDC.IgnoreEmailVerified.Value(),
|
||||
return xerrors.Errorf("create oidc config: %w", err)
|
||||
}
|
||||
options.OIDCConfig = oc
|
||||
}
|
||||
|
||||
if cfg.InMemoryDatabase {
|
||||
if vals.InMemoryDatabase {
|
||||
// This is only used for testing.
|
||||
options.Database = dbfake.New()
|
||||
options.Pubsub = pubsub.NewInMemory()
|
||||
} else {
|
||||
sqlDB, err := connectToPostgres(ctx, logger, sqlDriver, cfg.PostgresURL.String())
|
||||
sqlDB, err := connectToPostgres(ctx, logger, sqlDriver, vals.PostgresURL.String())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("connect to postgres: %w", err)
|
||||
}
|
||||
@@ -641,7 +694,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
}()
|
||||
|
||||
options.Database = database.New(sqlDB)
|
||||
options.Pubsub, err = pubsub.New(ctx, sqlDB, cfg.PostgresURL.String())
|
||||
options.Pubsub, err = pubsub.New(ctx, sqlDB, vals.PostgresURL.String())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create pubsub: %w", err)
|
||||
}
|
||||
@@ -748,7 +801,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.Telemetry.Enable {
|
||||
if vals.Telemetry.Enable {
|
||||
gitAuth := make([]telemetry.GitAuth, 0)
|
||||
// TODO:
|
||||
var gitAuthConfigs []codersdk.GitAuthConfig
|
||||
@@ -763,15 +816,15 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
DeploymentID: deploymentID,
|
||||
Database: options.Database,
|
||||
Logger: logger.Named("telemetry"),
|
||||
URL: cfg.Telemetry.URL.Value(),
|
||||
Wildcard: cfg.WildcardAccessURL.String() != "",
|
||||
DERPServerRelayURL: cfg.DERP.Server.RelayURL.String(),
|
||||
URL: vals.Telemetry.URL.Value(),
|
||||
Wildcard: vals.WildcardAccessURL.String() != "",
|
||||
DERPServerRelayURL: vals.DERP.Server.RelayURL.String(),
|
||||
GitAuth: gitAuth,
|
||||
GitHubOAuth: cfg.OAuth2.Github.ClientID != "",
|
||||
OIDCAuth: cfg.OIDC.ClientID != "",
|
||||
OIDCIssuerURL: cfg.OIDC.IssuerURL.String(),
|
||||
Prometheus: cfg.Prometheus.Enable.Value(),
|
||||
STUN: len(cfg.DERP.Server.STUNAddresses) != 0,
|
||||
GitHubOAuth: vals.OAuth2.Github.ClientID != "",
|
||||
OIDCAuth: vals.OIDC.ClientID != "",
|
||||
OIDCIssuerURL: vals.OIDC.IssuerURL.String(),
|
||||
Prometheus: vals.Prometheus.Enable.Value(),
|
||||
STUN: len(vals.DERP.Server.STUNAddresses) != 0,
|
||||
Tunnel: tunnel != nil,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -782,56 +835,25 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
|
||||
// This prevents the pprof import from being accidentally deleted.
|
||||
_ = pprof.Handler
|
||||
if cfg.Pprof.Enable {
|
||||
if vals.Pprof.Enable {
|
||||
//nolint:revive
|
||||
defer ServeHandler(ctx, logger, nil, cfg.Pprof.Address.String(), "pprof")()
|
||||
defer ServeHandler(ctx, logger, nil, vals.Pprof.Address.String(), "pprof")()
|
||||
}
|
||||
if cfg.Prometheus.Enable {
|
||||
options.PrometheusRegistry.MustRegister(collectors.NewGoCollector())
|
||||
options.PrometheusRegistry.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
|
||||
|
||||
closeUsersFunc, err := prometheusmetrics.ActiveUsers(ctx, options.PrometheusRegistry, options.Database, 0)
|
||||
if vals.Prometheus.Enable {
|
||||
closeFn, err := enablePrometheus(
|
||||
ctx,
|
||||
logger.Named("prometheus"),
|
||||
vals,
|
||||
options,
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("register active users prometheus metric: %w", err)
|
||||
return xerrors.Errorf("enable prometheus: %w", err)
|
||||
}
|
||||
defer closeUsersFunc()
|
||||
|
||||
closeWorkspacesFunc, err := prometheusmetrics.Workspaces(ctx, options.PrometheusRegistry, options.Database, 0)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("register workspaces prometheus metric: %w", err)
|
||||
}
|
||||
defer closeWorkspacesFunc()
|
||||
|
||||
if cfg.Prometheus.CollectAgentStats {
|
||||
closeAgentStatsFunc, err := prometheusmetrics.AgentStats(ctx, logger, options.PrometheusRegistry, options.Database, time.Now(), 0)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("register agent stats prometheus metric: %w", err)
|
||||
}
|
||||
defer closeAgentStatsFunc()
|
||||
|
||||
metricsAggregator, err := prometheusmetrics.NewMetricsAggregator(logger, options.PrometheusRegistry, 0)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("can't initialize metrics aggregator: %w", err)
|
||||
}
|
||||
|
||||
cancelMetricsAggregator := metricsAggregator.Run(ctx)
|
||||
defer cancelMetricsAggregator()
|
||||
|
||||
options.UpdateAgentMetrics = metricsAggregator.Update
|
||||
err = options.PrometheusRegistry.Register(metricsAggregator)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("can't register metrics aggregator as collector: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
defer ServeHandler(ctx, logger, promhttp.InstrumentMetricHandler(
|
||||
options.PrometheusRegistry, promhttp.HandlerFor(options.PrometheusRegistry, promhttp.HandlerOpts{}),
|
||||
), cfg.Prometheus.Address.String(), "prometheus")()
|
||||
defer closeFn()
|
||||
}
|
||||
|
||||
if cfg.Swagger.Enable {
|
||||
options.SwaggerEndpoint = cfg.Swagger.Enable.Value()
|
||||
if vals.Swagger.Enable {
|
||||
options.SwaggerEndpoint = vals.Swagger.Enable.Value()
|
||||
}
|
||||
|
||||
batcher, closeBatcher, err := batchstats.New(ctx,
|
||||
@@ -855,7 +877,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
return xerrors.Errorf("create coder API: %w", err)
|
||||
}
|
||||
|
||||
if cfg.Prometheus.Enable {
|
||||
if vals.Prometheus.Enable {
|
||||
// Agent metrics require reference to the tailnet coordinator, so must be initiated after Coder API.
|
||||
closeAgentsFunc, err := prometheusmetrics.Agents(ctx, logger, options.PrometheusRegistry, coderAPI.Database, &coderAPI.TailnetCoordinator, coderAPI.DERPMap, coderAPI.Options.AgentInactiveDisconnectTimeout, 0)
|
||||
if err != nil {
|
||||
@@ -903,10 +925,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
var provisionerdWaitGroup sync.WaitGroup
|
||||
defer provisionerdWaitGroup.Wait()
|
||||
provisionerdMetrics := provisionerd.NewMetrics(options.PrometheusRegistry)
|
||||
for i := int64(0); i < cfg.Provisioner.Daemons.Value(); i++ {
|
||||
for i := int64(0); i < vals.Provisioner.Daemons.Value(); i++ {
|
||||
daemonCacheDir := filepath.Join(cacheDir, fmt.Sprintf("provisioner-%d", i))
|
||||
daemon, err := newProvisionerDaemon(
|
||||
ctx, coderAPI, provisionerdMetrics, logger, cfg, daemonCacheDir, errCh, &provisionerdWaitGroup,
|
||||
ctx, coderAPI, provisionerdMetrics, logger, vals, daemonCacheDir, errCh, &provisionerdWaitGroup,
|
||||
)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("create provisioner daemon: %w", err)
|
||||
@@ -925,8 +947,8 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
// Wrap the server in middleware that redirects to the access URL if
|
||||
// the request is not to a local IP.
|
||||
var handler http.Handler = coderAPI.RootHandler
|
||||
if cfg.RedirectToAccessURL {
|
||||
handler = redirectToAccessURL(handler, cfg.AccessURL.Value(), tunnel != nil, appHostnameRegex)
|
||||
if vals.RedirectToAccessURL {
|
||||
handler = redirectToAccessURL(handler, vals.AccessURL.Value(), tunnel != nil, appHostnameRegex)
|
||||
}
|
||||
|
||||
// ReadHeaderTimeout is purposefully not enabled. It caused some
|
||||
@@ -983,12 +1005,12 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
return xerrors.Errorf("notify systemd: %w", err)
|
||||
}
|
||||
|
||||
autobuildTicker := time.NewTicker(cfg.AutobuildPollInterval.Value())
|
||||
autobuildTicker := time.NewTicker(vals.AutobuildPollInterval.Value())
|
||||
defer autobuildTicker.Stop()
|
||||
autobuildExecutor := autobuild.NewExecutor(ctx, options.Database, coderAPI.TemplateScheduleStore, logger, autobuildTicker.C)
|
||||
autobuildExecutor.Run()
|
||||
|
||||
hangDetectorTicker := time.NewTicker(cfg.JobHangDetectorInterval.Value())
|
||||
hangDetectorTicker := time.NewTicker(vals.JobHangDetectorInterval.Value())
|
||||
defer hangDetectorTicker.Stop()
|
||||
hangDetector := unhanger.New(ctx, options.Database, options.Pubsub, logger, hangDetectorTicker.C)
|
||||
hangDetector.Start()
|
||||
@@ -1047,9 +1069,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
if ok, _ := inv.ParsedFlags().GetBool(varVerbose); ok {
|
||||
cliui.Infof(inv.Stdout, "Shutting down provisioner daemon %d...", id)
|
||||
}
|
||||
r.Verbosef(inv, "Shutting down provisioner daemon %d...", id)
|
||||
err := shutdownWithTimeout(provisionerDaemon.Shutdown, 5*time.Second)
|
||||
if err != nil {
|
||||
cliui.Errorf(inv.Stderr, "Failed to shutdown provisioner daemon %d: %s\n", id, err)
|
||||
@@ -1060,9 +1080,7 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
cliui.Errorf(inv.Stderr, "Close provisioner daemon %d: %s\n", id, err)
|
||||
return
|
||||
}
|
||||
if ok, _ := inv.ParsedFlags().GetBool(varVerbose); ok {
|
||||
cliui.Infof(inv.Stdout, "Gracefully shut down provisioner daemon %d", id)
|
||||
}
|
||||
r.Verbosef(inv, "Gracefully shut down provisioner daemon %d", id)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
@@ -1496,31 +1496,6 @@ func TestServer(t *testing.T) {
|
||||
w.RequireSuccess()
|
||||
})
|
||||
})
|
||||
t.Run("DisableDERP", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Make sure that $CODER_DERP_SERVER_STUN_ADDRESSES can be set to
|
||||
// disable STUN.
|
||||
|
||||
inv, cfg := clitest.New(t,
|
||||
"server",
|
||||
"--in-memory",
|
||||
"--http-address", ":0",
|
||||
"--access-url", "https://example.com",
|
||||
)
|
||||
inv.Environ.Set("CODER_DERP_SERVER_STUN_ADDRESSES", "disable")
|
||||
ptytest.New(t).Attach(inv)
|
||||
clitest.Start(t, inv)
|
||||
gotURL := waitAccessURL(t, cfg)
|
||||
client := codersdk.New(gotURL)
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
_ = coderdtest.CreateFirstUser(t, client)
|
||||
gotConfig, err := client.DeploymentConfig(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, gotConfig.Values.DERP.Server.STUNAddresses, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_Production(t *testing.T) {
|
||||
|
||||
@@ -40,7 +40,6 @@ var (
|
||||
autostopNotifyCountdown = []time.Duration{30 * time.Minute}
|
||||
)
|
||||
|
||||
//nolint:gocyclo
|
||||
func (r *RootCmd) ssh() *clibase.Cmd {
|
||||
var (
|
||||
stdio bool
|
||||
|
||||
Reference in New Issue
Block a user