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:
Ammar Bandukwala 2023-08-27 14:46:44 -05:00 committed by GitHub
parent 173aac959c
commit 6ba92ef924
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 263 additions and 272 deletions

View File

@ -11,7 +11,7 @@ linters-settings:
# Gradually extend to cover more of the codebase.
- 'httpmw\.\w+'
gocognit:
min-complexity: 46 # Min code complexity (def 30).
min-complexity: 300
goconst:
min-len: 4 # Min length of string consts (def 3).
@ -122,10 +122,6 @@ linters-settings:
goimports:
local-prefixes: coder.com,cdr.dev,go.coder.com,github.com/cdr,github.com/coder
gocyclo:
# goal: 30
min-complexity: 47
importas:
no-unaliased: true
@ -236,7 +232,11 @@ linters:
- exportloopref
- forcetypeassert
- gocritic
- gocyclo
# gocyclo is may be useful in the future when we start caring
# about testing complexity, but for the time being we should
# create a good culture around cognitive complexity.
# - gocyclo
- gocognit
- goimports
- gomodguard
- gosec

View File

@ -190,7 +190,6 @@ func sshPrepareWorkspaceConfigs(ctx context.Context, client *codersdk.Client) (r
}
}
//nolint:gocyclo
func (r *RootCmd) configSSH() *clibase.Cmd {
var (
sshConfigFile string

View File

@ -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

View File

@ -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()

View File

@ -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) {

View File

@ -40,7 +40,6 @@ var (
autostopNotifyCountdown = []time.Duration{30 * time.Minute}
)
//nolint:gocyclo
func (r *RootCmd) ssh() *clibase.Cmd {
var (
stdio bool

View File

@ -184,8 +184,6 @@ type Options struct {
// @in header
// @name Coder-Session-Token
// New constructs a Coder API handler.
//
//nolint:gocyclo
func New(options *Options) *API {
if options == nil {
options = &Options{}

View File

@ -329,7 +329,7 @@ func NewOptions(t testing.TB, options *Options) (func(http.Handler), context.Can
t.Cleanup(stunCleanup)
stunAddresses = []string{stunAddr.String()}
options.DeploymentValues.DERP.Server.STUNAddresses = stunAddresses
} else if dvStunAddresses[0] != "disable" {
} else if dvStunAddresses[0] != tailnet.DisableSTUN {
stunAddresses = options.DeploymentValues.DERP.Server.STUNAddresses.Value()
}

View File

@ -6053,7 +6053,6 @@ func (q *FakeQuerier) GetTemplateUserRoles(_ context.Context, id uuid.UUID) ([]d
return users, nil
}
//nolint:gocyclo
func (q *FakeQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg database.GetWorkspacesParams, prepared rbac.PreparedAuthorized) ([]database.GetWorkspacesRow, error) {
if err := validateDatabaseType(arg); err != nil {
return nil, err

View File

@ -744,8 +744,6 @@ func (server *Server) FailJob(ctx context.Context, failJob *proto.FailedJob) (*p
}
// CompleteJob is triggered by a provision daemon to mark a provisioner job as completed.
//
//nolint:gocyclo
func (server *Server) CompleteJob(ctx context.Context, completed *proto.CompletedJob) (*proto.Empty, error) {
ctx, span := server.startTrace(ctx, tracing.FuncName())
defer span.End()

View File

@ -303,7 +303,6 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ
// @Param request body codersdk.CreateWorkspaceBuildRequest true "Create workspace build request"
// @Success 200 {object} codersdk.WorkspaceBuild
// @Router /workspaces/{workspace}/builds [post]
// nolint:gocyclo
func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
apiKey := httpmw.APIKey(r)

View File

@ -97,7 +97,6 @@ type State struct {
// ConvertState consumes Terraform state and a GraphViz representation
// produced by `terraform graph` to produce resources consumable by Coder.
// nolint:gocyclo
func ConvertState(modules []*tfjson.StateModule, rawGraph string) (*State, error) {
parsedGraph, err := gographviz.ParseString(rawGraph)
if err != nil {

View File

@ -655,8 +655,6 @@ type TypescriptType struct {
// Eg:
//
// []byte returns "string"
//
//nolint:gocyclo
func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
switch ty := ty.(type) {
case *types.Basic:

View File

@ -13,10 +13,12 @@ import (
"tailscale.com/tailcfg"
)
const DisableSTUN = "disable"
func STUNRegions(baseRegionID int, stunAddrs []string) ([]*tailcfg.DERPRegion, error) {
regions := make([]*tailcfg.DERPRegion, 0, len(stunAddrs))
for index, stunAddr := range stunAddrs {
if stunAddr == "disable" {
if stunAddr == DisableSTUN {
return []*tailcfg.DERPRegion{}, nil
}