From b1faaef482fa18b60950244028a80cbc3442cdb9 Mon Sep 17 00:00:00 2001 From: Garrett Delfosse Date: Mon, 10 Oct 2022 15:04:15 -0400 Subject: [PATCH] feat: deployment flags (#4426) --- cli/deployment/flags.go | 455 ++++++++++++++++++++++++++++++++ cli/root.go | 5 +- cli/server.go | 397 ++++++++++------------------ coderd/coderd.go | 5 + coderd/coderdtest/coderdtest.go | 2 + coderd/flags.go | 18 ++ coderd/flags_test.go | 47 ++++ coderd/rbac/object.go | 5 + codersdk/flags.go | 136 ++++++++++ enterprise/cli/server.go | 45 ++-- site/src/api/typesGenerated.ts | 118 +++++++++ 11 files changed, 949 insertions(+), 284 deletions(-) create mode 100644 cli/deployment/flags.go create mode 100644 coderd/flags.go create mode 100644 coderd/flags_test.go create mode 100644 codersdk/flags.go diff --git a/cli/deployment/flags.go b/cli/deployment/flags.go new file mode 100644 index 0000000000..54807fd539 --- /dev/null +++ b/cli/deployment/flags.go @@ -0,0 +1,455 @@ +package deployment + +import ( + "flag" + "os" + "path/filepath" + "reflect" + "time" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/spf13/pflag" + + "github.com/coder/coder/cli/cliflag" + "github.com/coder/coder/codersdk" +) + +const ( + secretValue = "********" +) + +func Flags() codersdk.DeploymentFlags { + return codersdk.DeploymentFlags{ + AccessURL: codersdk.StringFlag{ + Name: "Access URL", + Flag: "access-url", + EnvVar: "CODER_ACCESS_URL", + Description: "External URL to access your deployment. This must be accessible by all provisioned workspaces.", + }, + WildcardAccessURL: codersdk.StringFlag{ + Name: "Wildcard Address URL", + Flag: "wildcard-access-url", + EnvVar: "CODER_WILDCARD_ACCESS_URL", + Description: `Specifies the wildcard hostname to use for workspace applications in the form "*.example.com".`, + }, + Address: codersdk.StringFlag{ + Name: "Bind Address", + Flag: "address", + EnvVar: "CODER_ADDRESS", + Shorthand: "a", + Description: "Bind address of the server.", + Default: "127.0.0.1:3000", + }, + AutobuildPollInterval: codersdk.DurationFlag{ + Name: "Autobuild Poll Interval", + Flag: "autobuild-poll-interval", + EnvVar: "CODER_AUTOBUILD_POLL_INTERVAL", + Description: "Interval to poll for scheduled workspace builds.", + Default: time.Minute, + }, + DerpServerEnable: codersdk.BoolFlag{ + Name: "DERP Server Enabled", + Flag: "derp-server-enable", + EnvVar: "CODER_DERP_SERVER_ENABLE", + Description: "Whether to enable or disable the embedded DERP relay server.", + Default: true, + }, + DerpServerRegionID: codersdk.IntFlag{ + Name: "DERP Server Region ID", + Flag: "derp-server-region-id", + EnvVar: "CODER_DERP_SERVER_REGION_ID", + Description: "Region ID to use for the embedded DERP server.", + Default: 999, + }, + DerpServerRegionCode: codersdk.StringFlag{ + Name: "DERP Server Region Code", + Flag: "derp-server-region-code", + EnvVar: "CODER_DERP_SERVER_REGION_CODE", + Description: "Region code to use for the embedded DERP server.", + Default: "coder", + }, + DerpServerRegionName: codersdk.StringFlag{ + Name: "DERP Server Region Name", + Flag: "derp-server-region-name", + EnvVar: "CODER_DERP_SERVER_REGION_NAME", + Description: "Region name that for the embedded DERP server.", + Default: "Coder Embedded Relay", + }, + DerpServerSTUNAddresses: codersdk.StringArrayFlag{ + Name: "DERP Server STUN Addresses", + Flag: "derp-server-stun-addresses", + EnvVar: "CODER_DERP_SERVER_STUN_ADDRESSES", + Description: "Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections.", + Default: []string{"stun.l.google.com:19302"}, + }, + DerpConfigURL: codersdk.StringFlag{ + Name: "DERP Config URL", + Flag: "derp-config-url", + EnvVar: "CODER_DERP_CONFIG_URL", + Description: "URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/", + }, + DerpConfigPath: codersdk.StringFlag{ + Name: "DERP Config Path", + Flag: "derp-config-path", + EnvVar: "CODER_DERP_CONFIG_PATH", + Description: "Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp-servers/", + }, + PromEnabled: codersdk.BoolFlag{ + Name: "Prometheus Enabled", + Flag: "prometheus-enable", + EnvVar: "CODER_PROMETHEUS_ENABLE", + Description: "Serve prometheus metrics on the address defined by `prometheus-address`.", + }, + PromAddress: codersdk.StringFlag{ + Name: "Prometheus Address", + Flag: "prometheus-address", + EnvVar: "CODER_PROMETHEUS_ADDRESS", + Description: "The bind address to serve prometheus metrics.", + Default: "127.0.0.1:2112", + }, + PprofEnabled: codersdk.BoolFlag{ + Name: "pprof Enabled", + Flag: "pprof-enable", + EnvVar: "CODER_PPROF_ENABLE", + Description: "Serve pprof metrics on the address defined by `pprof-address`.", + }, + PprofAddress: codersdk.StringFlag{ + Name: "pprof Address", + Flag: "pprof-address", + EnvVar: "CODER_PPROF_ADDRESS", + Description: "The bind address to serve pprof.", + Default: "127.0.0.1:6060", + }, + CacheDir: codersdk.StringFlag{ + Name: "Cache Directory", + Flag: "cache-dir", + EnvVar: "CODER_CACHE_DIRECTORY", + Description: "The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd.", + Default: defaultCacheDir(), + }, + InMemoryDatabase: codersdk.BoolFlag{ + Name: "In-Memory Database", + Flag: "in-memory", + EnvVar: "CODER_INMEMORY", + Description: "Controls whether data will be stored in an in-memory database.", + }, + ProvisionerDaemonCount: codersdk.IntFlag{ + Name: "Provisioner Daemons", + Flag: "provisioner-daemons", + EnvVar: "CODER_PROVISIONER_DAEMONS", + Description: "Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this.", + Default: 3, + }, + PostgresURL: codersdk.StringFlag{ + Name: "Postgres URL", + Flag: "postgres-url", + EnvVar: "CODER_PG_CONNECTION_URL", + Description: "URL of a PostgreSQL database. If empty, PostgreSQL binaries will be downloaded from Maven (https://repo1.maven.org/maven2) and store all data in the config root. Access the built-in database with \"coder server postgres-builtin-url\"", + Secret: true, + }, + OAuth2GithubClientID: codersdk.StringFlag{ + Name: "Oauth2 Github Client ID", + Flag: "oauth2-github-client-id", + EnvVar: "CODER_OAUTH2_GITHUB_CLIENT_ID", + Description: "Client ID for Login with GitHub.", + }, + OAuth2GithubClientSecret: codersdk.StringFlag{ + Name: "Oauth2 Github Client Secret", + Flag: "oauth2-github-client-secret", + EnvVar: "CODER_OAUTH2_GITHUB_CLIENT_SECRET", + Description: "Client secret for Login with GitHub.", + Secret: true, + }, + OAuth2GithubAllowedOrganizations: codersdk.StringArrayFlag{ + Name: "Oauth2 Github Allowed Organizations", + Flag: "oauth2-github-allowed-orgs", + EnvVar: "CODER_OAUTH2_GITHUB_ALLOWED_ORGS", + Description: "Organizations the user must be a member of to Login with GitHub.", + }, + OAuth2GithubAllowedTeams: codersdk.StringArrayFlag{ + Name: "Oauth2 Github Allowed Teams", + Flag: "oauth2-github-allowed-teams", + EnvVar: "CODER_OAUTH2_GITHUB_ALLOWED_TEAMS", + Description: "Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /.", + }, + OAuth2GithubAllowSignups: codersdk.BoolFlag{ + Name: "Oauth2 Github Allow Signups", + Flag: "oauth2-github-allow-signups", + EnvVar: "CODER_AUTOBUILD_POLL_INTERVAL", + Description: "Whether new users can sign up with GitHub.", + }, + OAuth2GithubEnterpriseBaseURL: codersdk.StringFlag{ + Name: "Oauth2 Github Enterprise Base URL", + Flag: "oauth2-github-enterprise-base-url", + EnvVar: "CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL", + Description: "Base URL of a GitHub Enterprise deployment to use for Login with GitHub.", + }, + OIDCAllowSignups: codersdk.BoolFlag{ + Name: "OIDC Allow Signups", + Flag: "oidc-allow-signups", + EnvVar: "CODER_OIDC_ALLOW_SIGNUPS", + Description: "Whether new users can sign up with OIDC.", + Default: true, + }, + OIDCClientID: codersdk.StringFlag{ + Name: "OIDC Client ID", + Flag: "oidc-client-id", + EnvVar: "CODER_OIDC_CLIENT_ID", + Description: "Client ID to use for Login with OIDC.", + }, + OIDCClientSecret: codersdk.StringFlag{ + Name: "OIDC Client Secret", + Flag: "oidc-client-secret", + EnvVar: "CODER_OIDC_CLIENT_SECRET", + Description: "Client secret to use for Login with OIDC.", + Secret: true, + }, + OIDCEmailDomain: codersdk.StringFlag{ + Name: "OIDC Email Domain", + Flag: "oidc-email-domain", + EnvVar: "CODER_OIDC_EMAIL_DOMAIN", + Description: "Email domain that clients logging in with OIDC must match.", + }, + OIDCIssuerURL: codersdk.StringFlag{ + Name: "OIDC Issuer URL", + Flag: "oidc-issuer-url", + EnvVar: "CODER_OIDC_ISSUER_URL", + Description: "Issuer URL to use for Login with OIDC.", + }, + OIDCScopes: codersdk.StringArrayFlag{ + Name: "OIDC Scopes", + Flag: "oidc-scopes", + EnvVar: "CODER_OIDC_SCOPES", + Description: "Scopes to grant when authenticating with OIDC.", + Default: []string{oidc.ScopeOpenID, "profile", "email"}, + }, + TelemetryEnable: codersdk.BoolFlag{ + Name: "Telemetry Enabled", + Flag: "telemetry", + EnvVar: "CODER_TELEMETRY", + Description: "Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product.", + Default: flag.Lookup("test.v") == nil, + }, + TelemetryTraceEnable: codersdk.BoolFlag{ + Name: "Trace Telemetry Enabled", + Flag: "telemetry-trace", + EnvVar: "CODER_TELEMETRY_TRACE", + Shorthand: "", + Description: "Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option.", + Default: flag.Lookup("test.v") == nil, + }, + TelemetryURL: codersdk.StringFlag{ + Name: "Telemetry URL", + Flag: "telemetry-url", + EnvVar: "CODER_TELEMETRY_URL", + Description: "URL to send telemetry.", + Default: "https://telemetry.coder.com", + }, + TLSEnable: codersdk.BoolFlag{ + Name: "TLS Enabled", + Flag: "tls-enable", + EnvVar: "CODER_TLS_ENABLE", + Description: "Whether TLS will be enabled.", + }, + TLSCertFiles: codersdk.StringArrayFlag{ + Name: "TLS Cert Files", + Flag: "tls-cert-file", + EnvVar: "CODER_TLS_CERT_FILE", + Description: "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.", + Default: []string{}, + }, + TLSClientCAFile: codersdk.StringFlag{ + Name: "TLS Client CA File", + Flag: "tls-client-ca-file", + EnvVar: "CODER_TLS_CLIENT_CA_FILE", + Description: "PEM-encoded Certificate Authority file used for checking the authenticity of client", + }, + TLSClientAuth: codersdk.StringFlag{ + Name: "TLS Client Auth", + Flag: "tls-client-auth", + EnvVar: "CODER_TLS_KEY_FILE", + Description: `Policy the server will follow for TLS Client Authentication. ` + + `Accepted values are "none", "request", "require-any", "verify-if-given", or "require-and-verify"`, + Default: "request", + }, + TLSKeyFiles: codersdk.StringArrayFlag{ + Name: "TLS Key Files", + Flag: "tls-key-file", + EnvVar: "CODER_TLS_KEY_FILE", + Description: "Paths to the private keys for each of the certificates. It requires a PEM-encoded file", + Default: []string{}, + }, + TLSMinVersion: codersdk.StringFlag{ + Name: "TLS Min Version", + Flag: "tls-min-version", + EnvVar: "CODER_TLS_MIN_VERSION", + Description: `Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13"`, + Default: "tls12", + }, + TraceEnable: codersdk.BoolFlag{ + Name: "Trace Enabled", + Flag: "trace", + EnvVar: "CODER_TRACE", + Description: "Whether application tracing data is collected.", + }, + SecureAuthCookie: codersdk.BoolFlag{ + Name: "Secure Auth Cookie", + Flag: "secure-auth-cookie", + EnvVar: "CODER_SECURE_AUTH_COOKIE", + Description: "Controls if the 'Secure' property is set on browser session cookies", + }, + SSHKeygenAlgorithm: codersdk.StringFlag{ + Name: "SSH Keygen Algorithm", + Flag: "ssh-keygen-algorithm", + EnvVar: "CODER_SSH_KEYGEN_ALGORITHM", + Description: "The algorithm to use for generating ssh keys. " + + `Accepted values are "ed25519", "ecdsa", or "rsa4096"`, + Default: "ed25519", + }, + AutoImportTemplates: codersdk.StringArrayFlag{ + Name: "Auto Import Templates", + Flag: "auto-import-template", + EnvVar: "CODER_TEMPLATE_AUTOIMPORT", + Description: "Templates to auto-import. Available auto-importable templates are: kubernetes", + Default: []string{}, + }, + MetricsCacheRefreshInterval: codersdk.DurationFlag{ + Name: "Metrics Cache Refresh Interval", + Flag: "metrics-cache-refresh-interval", + EnvVar: "CODER_METRICS_CACHE_REFRESH_INTERVAL", + Description: "How frequently metrics are refreshed", + Default: time.Hour, + }, + AgentStatRefreshInterval: codersdk.DurationFlag{ + Name: "Agent Stats Refresh Interval", + Flag: "agent-stats-refresh-interval", + EnvVar: "CODER_AGENT_STATS_REFRESH_INTERVAL", + Description: "How frequently agent stats are recorded", + Default: 10 * time.Minute, + }, + Verbose: codersdk.BoolFlag{ + Name: "Verbose Logging", + Flag: "verbose", + EnvVar: "CODER_VERBOSE", + Shorthand: "v", + Description: "Enables verbose logging.", + }, + AuditLogging: codersdk.BoolFlag{ + Name: "Audit Logging", + Flag: "audit-logging", + EnvVar: "CODER_AUDIT_LOGGING", + Description: "Specifies whether audit logging is enabled.", + Default: true, + Enterprise: true, + }, + BrowserOnly: codersdk.BoolFlag{ + Name: "Browser Only", + Flag: "browser-only", + EnvVar: "CODER_BROWSER_ONLY", + Description: "Whether Coder only allows connections to workspaces via the browser.", + Enterprise: true, + }, + SCIMAuthHeader: codersdk.StringFlag{ + Name: "SCIM Authentication Header", + Flag: "scim-auth-header", + EnvVar: "CODER_SCIM_API_KEY", + Description: "Enables SCIM and sets the authentication header for the built-in SCIM server. New users are automatically created with OIDC authentication.", + Secret: true, + Enterprise: true, + }, + UserWorkspaceQuota: codersdk.IntFlag{ + Name: "User Workspace Quota", + Flag: "user-workspace-quota", + EnvVar: "CODER_USER_WORKSPACE_QUOTA", + Description: "Enables and sets a limit on how many workspaces each user can create.", + Default: 0, + Enterprise: true, + }, + } +} + +func RemoveSensitiveValues(df codersdk.DeploymentFlags) codersdk.DeploymentFlags { + v := reflect.ValueOf(&df).Elem() + t := v.Type() + for i := 0; i < t.NumField(); i++ { + fv := v.Field(i) + if v, ok := fv.Interface().(codersdk.StringFlag); ok { + if v.Secret && v.Value != "" { + v.Value = secretValue + fv.Set(reflect.ValueOf(v)) + } + } + } + + return df +} + +func StringFlag(flagset *pflag.FlagSet, fl *codersdk.StringFlag) { + cliflag.StringVarP(flagset, + &fl.Value, + fl.Flag, + fl.Shorthand, + fl.EnvVar, + fl.Default, + fl.Description, + ) +} + +func BoolFlag(flagset *pflag.FlagSet, fl *codersdk.BoolFlag) { + cliflag.BoolVarP(flagset, + &fl.Value, + fl.Flag, + fl.Shorthand, + fl.EnvVar, + fl.Default, + fl.Description, + ) +} + +func IntFlag(flagset *pflag.FlagSet, fl *codersdk.IntFlag) { + cliflag.IntVarP(flagset, + &fl.Value, + fl.Flag, + fl.Shorthand, + fl.EnvVar, + fl.Default, + fl.Description, + ) +} + +func DurationFlag(flagset *pflag.FlagSet, fl *codersdk.DurationFlag) { + cliflag.DurationVarP(flagset, + &fl.Value, + fl.Flag, + fl.Shorthand, + fl.EnvVar, + fl.Default, + fl.Description, + ) +} + +func StringArrayFlag(flagset *pflag.FlagSet, fl *codersdk.StringArrayFlag) { + cliflag.StringArrayVarP(flagset, + &fl.Value, + fl.Flag, + fl.Shorthand, + fl.EnvVar, + fl.Default, + fl.Description, + ) +} + +func defaultCacheDir() string { + defaultCacheDir, err := os.UserCacheDir() + if err != nil { + defaultCacheDir = os.TempDir() + } + if dir := os.Getenv("CACHE_DIRECTORY"); dir != "" { + // For compatibility with systemd. + defaultCacheDir = dir + } + + return filepath.Join(defaultCacheDir, "coder") +} diff --git a/cli/root.go b/cli/root.go index 0f533a5b1c..44988f0bb9 100644 --- a/cli/root.go +++ b/cli/root.go @@ -22,6 +22,7 @@ import ( "github.com/coder/coder/cli/cliflag" "github.com/coder/coder/cli/cliui" "github.com/coder/coder/cli/config" + "github.com/coder/coder/cli/deployment" "github.com/coder/coder/coderd" "github.com/coder/coder/codersdk" ) @@ -98,7 +99,9 @@ func Core() []*cobra.Command { } func AGPL() []*cobra.Command { - all := append(Core(), Server(func(_ context.Context, o *coderd.Options) (*coderd.API, error) { + df := deployment.Flags() + all := append(Core(), Server(df, func(_ context.Context, o *coderd.Options) (*coderd.API, error) { + o.DeploymentFlags = &df return coderd.New(o), nil })) return all diff --git a/cli/server.go b/cli/server.go index 1d54f8936a..bd5f9a143c 100644 --- a/cli/server.go +++ b/cli/server.go @@ -43,9 +43,9 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/sloghuman" "github.com/coder/coder/buildinfo" - "github.com/coder/coder/cli/cliflag" "github.com/coder/coder/cli/cliui" "github.com/coder/coder/cli/config" + "github.com/coder/coder/cli/deployment" "github.com/coder/coder/coderd" "github.com/coder/coder/coderd/autobuild/executor" "github.com/coder/coder/coderd/database" @@ -67,67 +67,14 @@ import ( ) // nolint:gocyclo -func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) *cobra.Command { - var ( - accessURL string - address string - wildcardAccessURL string - autobuildPollInterval time.Duration - derpServerEnabled bool - derpServerRegionID int - derpServerRegionCode string - derpServerRegionName string - derpServerSTUNAddrs []string - derpConfigURL string - derpConfigPath string - promEnabled bool - promAddress string - pprofEnabled bool - pprofAddress string - cacheDir string - inMemoryDatabase bool - // provisionerDaemonCount is a uint8 to ensure a number > 0. - provisionerDaemonCount uint8 - postgresURL string - oauth2GithubClientID string - oauth2GithubClientSecret string - oauth2GithubAllowedOrganizations []string - oauth2GithubAllowedTeams []string - oauth2GithubAllowSignups bool - oauth2GithubEnterpriseBaseURL string - oidcAllowSignups bool - oidcClientID string - oidcClientSecret string - oidcEmailDomain string - oidcIssuerURL string - oidcScopes []string - tailscaleEnable bool - telemetryEnable bool - telemetryTraceEnable bool - telemetryURL string - tlsCertFiles []string - tlsClientCAFile string - tlsClientAuth string - tlsEnable bool - tlsKeyFiles []string - tlsMinVersion string - traceEnable bool - secureAuthCookie bool - sshKeygenAlgorithmRaw string - autoImportTemplates []string - spooky bool - verbose bool - metricsCacheRefreshInterval time.Duration - agentStatRefreshInterval time.Duration - ) - +func Server(dflags codersdk.DeploymentFlags, newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) *cobra.Command { root := &cobra.Command{ Use: "server", Short: "Start a Coder server", RunE: func(cmd *cobra.Command, args []string) error { - printLogo(cmd, spooky) + printLogo(cmd) logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr())) - if verbose { + if dflags.Verbose.Value { logger = logger.Leveled(slog.LevelDebug) } @@ -162,16 +109,16 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) // Coder tracing should be disabled if telemetry is disabled unless // --telemetry-trace was explicitly provided. - shouldCoderTrace := telemetryEnable && !isTest() + shouldCoderTrace := dflags.TelemetryEnable.Value && !isTest() // Only override if telemetryTraceEnable was specifically set. // By default we want it to be controlled by telemetryEnable. if cmd.Flags().Changed("telemetry-trace") { - shouldCoderTrace = telemetryTraceEnable + shouldCoderTrace = dflags.TelemetryTraceEnable.Value } - if traceEnable || shouldCoderTrace { + if dflags.TraceEnable.Value || shouldCoderTrace { sdkTracerProvider, closeTracing, err := tracing.TracerProvider(ctx, "coderd", tracing.TracerOpts{ - Default: traceEnable, + Default: dflags.TraceEnable.Value, Coder: shouldCoderTrace, }) if err != nil { @@ -196,10 +143,10 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) config := createConfig(cmd) builtinPostgres := false // Only use built-in if PostgreSQL URL isn't specified! - if !inMemoryDatabase && postgresURL == "" { + if !dflags.InMemoryDatabase.Value && dflags.PostgresURL.Value == "" { var closeFunc func() error cmd.Printf("Using built-in PostgreSQL (%s)\n", config.PostgresPath()) - postgresURL, closeFunc, err = startBuiltinPostgres(ctx, config, logger) + dflags.PostgresURL.Value, closeFunc, err = startBuiltinPostgres(ctx, config, logger) if err != nil { return err } @@ -212,14 +159,20 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) }() } - listener, err := net.Listen("tcp", address) + listener, err := net.Listen("tcp", dflags.Address.Value) if err != nil { - return xerrors.Errorf("listen %q: %w", address, err) + return xerrors.Errorf("listen %q: %w", dflags.Address.Value, err) } defer listener.Close() - if tlsEnable { - listener, err = configureServerTLS(listener, tlsMinVersion, tlsClientAuth, tlsCertFiles, tlsKeyFiles, tlsClientCAFile) + if dflags.TLSEnable.Value { + listener, err = configureServerTLS( + listener, dflags.TLSMinVersion.Value, + dflags.TLSClientAuth.Value, + dflags.TLSCertFiles.Value, + dflags.TLSKeyFiles.Value, + dflags.TLSClientCAFile.Value, + ) if err != nil { return xerrors.Errorf("configure tls: %w", err) } @@ -239,7 +192,7 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) Scheme: "http", Host: tcpAddr.String(), } - if tlsEnable { + if dflags.TLSEnable.Value { localURL.Scheme = "https" } @@ -252,16 +205,16 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) // If the access URL is empty, we attempt to run a reverse-proxy tunnel // to make the initial setup really simple. - if accessURL == "" { + if dflags.AccessURL.Value == "" { cmd.Printf("Opening tunnel so workspaces can connect to your deployment. For production scenarios, specify an external access URL\n") tunnel, tunnelErr, err = devtunnel.New(ctxTunnel, logger.Named("devtunnel")) if err != nil { return xerrors.Errorf("create tunnel: %w", err) } - accessURL = tunnel.URL + dflags.AccessURL.Value = tunnel.URL } - accessURLParsed, err := parseURL(ctx, accessURL) + accessURLParsed, err := parseURL(ctx, dflags.AccessURL.Value) if err != nil { return xerrors.Errorf("parse URL: %w", err) } @@ -296,17 +249,17 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) return err } - sshKeygenAlgorithm, err := gitsshkey.ParseAlgorithm(sshKeygenAlgorithmRaw) + sshKeygenAlgorithm, err := gitsshkey.ParseAlgorithm(dflags.SSHKeygenAlgorithm.Value) if err != nil { - return xerrors.Errorf("parse ssh keygen algorithm %s: %w", sshKeygenAlgorithmRaw, err) + return xerrors.Errorf("parse ssh keygen algorithm %s: %w", dflags.SSHKeygenAlgorithm.Value, err) } // Validate provided auto-import templates. var ( - validatedAutoImportTemplates = make([]coderd.AutoImportTemplate, len(autoImportTemplates)) - seenValidatedAutoImportTemplates = make(map[coderd.AutoImportTemplate]struct{}, len(autoImportTemplates)) + validatedAutoImportTemplates = make([]coderd.AutoImportTemplate, len(dflags.AutoImportTemplates.Value)) + seenValidatedAutoImportTemplates = make(map[coderd.AutoImportTemplate]struct{}, len(dflags.AutoImportTemplates.Value)) ) - for i, autoImportTemplate := range autoImportTemplates { + for i, autoImportTemplate := range dflags.AutoImportTemplates.Value { var v coderd.AutoImportTemplate switch autoImportTemplate { case "kubernetes": @@ -324,27 +277,27 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) defaultRegion := &tailcfg.DERPRegion{ EmbeddedRelay: true, - RegionID: derpServerRegionID, - RegionCode: derpServerRegionCode, - RegionName: derpServerRegionName, + RegionID: dflags.DerpServerRegionID.Value, + RegionCode: dflags.DerpServerRegionCode.Value, + RegionName: dflags.DerpServerRegionName.Value, Nodes: []*tailcfg.DERPNode{{ - Name: fmt.Sprintf("%db", derpServerRegionID), - RegionID: derpServerRegionID, + Name: fmt.Sprintf("%db", dflags.DerpServerRegionID.Value), + RegionID: dflags.DerpServerRegionID.Value, HostName: accessURLParsed.Hostname(), DERPPort: accessURLPort, STUNPort: -1, ForceHTTP: accessURLParsed.Scheme == "http", }}, } - if !derpServerEnabled { + if !dflags.DerpServerEnable.Value { defaultRegion = nil } - derpMap, err := tailnet.NewDERPMap(ctx, defaultRegion, derpServerSTUNAddrs, derpConfigURL, derpConfigPath) + derpMap, err := tailnet.NewDERPMap(ctx, defaultRegion, dflags.DerpServerSTUNAddresses.Value, dflags.DerpConfigURL.Value, dflags.DerpConfigPath.Value) if err != nil { return xerrors.Errorf("create derp map: %w", err) } - appHostname := strings.TrimPrefix(wildcardAccessURL, "http://") + appHostname := strings.TrimPrefix(dflags.WildcardAccessURL.Value, "http://") appHostname = strings.TrimPrefix(appHostname, "https://") appHostname = strings.TrimPrefix(appHostname, "*.") @@ -355,34 +308,42 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) Database: databasefake.New(), DERPMap: derpMap, Pubsub: database.NewPubsubInMemory(), - CacheDir: cacheDir, + CacheDir: dflags.CacheDir.Value, GoogleTokenValidator: googleTokenValidator, - SecureAuthCookie: secureAuthCookie, + SecureAuthCookie: dflags.SecureAuthCookie.Value, SSHKeygenAlgorithm: sshKeygenAlgorithm, TracerProvider: tracerProvider, Telemetry: telemetry.NewNoop(), AutoImportTemplates: validatedAutoImportTemplates, - MetricsCacheRefreshInterval: metricsCacheRefreshInterval, - AgentStatsRefreshInterval: agentStatRefreshInterval, + MetricsCacheRefreshInterval: dflags.MetricsCacheRefreshInterval.Value, + AgentStatsRefreshInterval: dflags.AgentStatRefreshInterval.Value, Experimental: ExperimentalEnabled(cmd), + DeploymentFlags: &dflags, } - if oauth2GithubClientSecret != "" { - options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed, oauth2GithubClientID, oauth2GithubClientSecret, oauth2GithubAllowSignups, oauth2GithubAllowedOrganizations, oauth2GithubAllowedTeams, oauth2GithubEnterpriseBaseURL) + if dflags.OAuth2GithubClientSecret.Value != "" { + options.GithubOAuth2Config, err = configureGithubOAuth2(accessURLParsed, + dflags.OAuth2GithubClientID.Value, + dflags.OAuth2GithubClientSecret.Value, + dflags.OAuth2GithubAllowSignups.Value, + dflags.OAuth2GithubAllowedOrganizations.Value, + dflags.OAuth2GithubAllowedTeams.Value, + dflags.OAuth2GithubEnterpriseBaseURL.Value, + ) if err != nil { return xerrors.Errorf("configure github oauth2: %w", err) } } - if oidcClientSecret != "" { - if oidcClientID == "" { + if dflags.OIDCClientSecret.Value != "" { + if dflags.OIDCClientID.Value == "" { return xerrors.Errorf("OIDC client ID be set!") } - if oidcIssuerURL == "" { + if dflags.OIDCIssuerURL.Value == "" { return xerrors.Errorf("OIDC issuer URL must be set!") } - oidcProvider, err := oidc.NewProvider(ctx, oidcIssuerURL) + oidcProvider, err := oidc.NewProvider(ctx, dflags.OIDCIssuerURL.Value) if err != nil { return xerrors.Errorf("configure oidc provider: %w", err) } @@ -392,25 +353,25 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) } options.OIDCConfig = &coderd.OIDCConfig{ OAuth2Config: &oauth2.Config{ - ClientID: oidcClientID, - ClientSecret: oidcClientSecret, + ClientID: dflags.OIDCClientID.Value, + ClientSecret: dflags.OIDCClientSecret.Value, RedirectURL: redirectURL.String(), Endpoint: oidcProvider.Endpoint(), - Scopes: oidcScopes, + Scopes: dflags.OIDCScopes.Value, }, Verifier: oidcProvider.Verifier(&oidc.Config{ - ClientID: oidcClientID, + ClientID: dflags.OIDCClientID.Value, }), - EmailDomain: oidcEmailDomain, - AllowSignups: oidcAllowSignups, + EmailDomain: dflags.OIDCEmailDomain.Value, + AllowSignups: dflags.OIDCAllowSignups.Value, } } - if inMemoryDatabase { + if dflags.InMemoryDatabase.Value { options.Database = databasefake.New() options.Pubsub = database.NewPubsubInMemory() } else { - sqlDB, err := sql.Open(sqlDriver, postgresURL) + sqlDB, err := sql.Open(sqlDriver, dflags.PostgresURL.Value) if err != nil { return xerrors.Errorf("dial postgres: %w", err) } @@ -425,7 +386,7 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) return xerrors.Errorf("migrate up: %w", err) } options.Database = database.New(sqlDB) - options.Pubsub, err = database.NewPubsub(ctx, sqlDB, postgresURL) + options.Pubsub, err = database.NewPubsub(ctx, sqlDB, dflags.PostgresURL.Value) if err != nil { return xerrors.Errorf("create pubsub: %w", err) } @@ -448,26 +409,26 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) } // Parse the raw telemetry URL! - telemetryURL, err := parseURL(ctx, telemetryURL) + telemetryURL, err := parseURL(ctx, dflags.TelemetryURL.Value) if err != nil { return xerrors.Errorf("parse telemetry url: %w", err) } // Disable telemetry if the in-memory database is used unless explicitly defined! - if inMemoryDatabase && !cmd.Flags().Changed("telemetry") { - telemetryEnable = false + if dflags.InMemoryDatabase.Value && !cmd.Flags().Changed(dflags.TelemetryEnable.Flag) { + dflags.TelemetryEnable.Value = false } - if telemetryEnable { + if dflags.TelemetryEnable.Value { options.Telemetry, err = telemetry.New(telemetry.Options{ BuiltinPostgres: builtinPostgres, DeploymentID: deploymentID, Database: options.Database, Logger: logger.Named("telemetry"), URL: telemetryURL, - GitHubOAuth: oauth2GithubClientID != "", - OIDCAuth: oidcClientID != "", - OIDCIssuerURL: oidcIssuerURL, - Prometheus: promEnabled, - STUN: len(derpServerSTUNAddrs) != 0, + GitHubOAuth: dflags.OAuth2GithubClientID.Value != "", + OIDCAuth: dflags.OIDCClientID.Value != "", + OIDCIssuerURL: dflags.OIDCIssuerURL.Value, + Prometheus: dflags.PromEnabled.Value, + STUN: len(dflags.DerpServerSTUNAddresses.Value) != 0, Tunnel: tunnel != nil, }) if err != nil { @@ -478,11 +439,11 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) // This prevents the pprof import from being accidentally deleted. _ = pprof.Handler - if pprofEnabled { + if dflags.PprofEnabled.Value { //nolint:revive - defer serveHandler(ctx, logger, nil, pprofAddress, "pprof")() + defer serveHandler(ctx, logger, nil, dflags.PprofAddress.Value, "pprof")() } - if promEnabled { + if dflags.PromEnabled.Value { options.PrometheusRegistry = prometheus.NewRegistry() closeUsersFunc, err := prometheusmetrics.ActiveUsers(ctx, options.PrometheusRegistry, options.Database, 0) if err != nil { @@ -499,7 +460,7 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) //nolint:revive defer serveHandler(ctx, logger, promhttp.InstrumentMetricHandler( options.PrometheusRegistry, promhttp.HandlerFor(options.PrometheusRegistry, promhttp.HandlerOpts{}), - ), promAddress, "prometheus")() + ), dflags.PromAddress.Value, "prometheus")() } coderAPI, err := newAPI(ctx, options) @@ -509,7 +470,7 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) defer coderAPI.Close() client := codersdk.New(localURL) - if tlsEnable { + if dflags.TLSEnable.Value { // Secure transport isn't needed for locally communicating! client.HTTPClient.Transport = &http.Transport{ TLSClientConfig: &tls.Config{ @@ -533,8 +494,8 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) _ = daemon.Close() } }() - for i := 0; uint8(i) < provisionerDaemonCount; i++ { - daemon, err := newProvisionerDaemon(ctx, coderAPI, logger, cacheDir, errCh, false) + for i := 0; i < dflags.ProvisionerDaemonCount.Value; i++ { + daemon, err := newProvisionerDaemon(ctx, coderAPI, logger, dflags.CacheDir.Value, errCh, false) if err != nil { return xerrors.Errorf("create provisioner daemon: %w", err) } @@ -600,7 +561,7 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) return xerrors.Errorf("notify systemd: %w", err) } - autobuildPoller := time.NewTicker(autobuildPollInterval) + autobuildPoller := time.NewTicker(dflags.AutobuildPollInterval.Value) defer autobuildPoller.Stop() autobuildExecutor := executor.New(ctx, options.Database, logger, autobuildPoller.C) autobuildExecutor.Run() @@ -665,7 +626,7 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) go func() { defer wg.Done() - if verbose { + if dflags.Verbose.Value { cmd.Printf("Shutting down provisioner daemon %d...\n", id) } err := shutdownWithTimeout(provisionerDaemon.Shutdown, 5*time.Second) @@ -678,7 +639,7 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) cmd.PrintErrf("Close provisioner daemon %d: %s\n", id, err) return } - if verbose { + if dflags.Verbose.Value { cmd.Printf("Gracefully shut down provisioner daemon %d\n", id) } }() @@ -730,7 +691,7 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) RunE: func(cmd *cobra.Command, args []string) error { cfg := createConfig(cmd) logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr())) - if verbose { + if dflags.Verbose.Value { logger = logger.Leveled(slog.LevelDebug) } @@ -751,128 +712,58 @@ func Server(newAPI func(context.Context, *coderd.Options) (*coderd.API, error)) }, }) - cliflag.DurationVarP(root.Flags(), &autobuildPollInterval, "autobuild-poll-interval", "", "CODER_AUTOBUILD_POLL_INTERVAL", time.Minute, - "Interval to poll for scheduled workspace builds.") - _ = root.Flags().MarkHidden("autobuild-poll-interval") - cliflag.StringVarP(root.Flags(), &accessURL, "access-url", "", "CODER_ACCESS_URL", "", - "External URL to access your deployment. This must be accessible by all provisioned workspaces.") - cliflag.StringVarP(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", - "Bind address of the server.") - cliflag.StringVarP(root.Flags(), &wildcardAccessURL, "wildcard-access-url", "", "CODER_WILDCARD_ACCESS_URL", "", `Specifies the wildcard hostname to use for workspace applications in the form "*.example.com".`) - cliflag.StringVarP(root.Flags(), &derpConfigURL, "derp-config-url", "", "CODER_DERP_CONFIG_URL", "", - "URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/") - cliflag.StringVarP(root.Flags(), &derpConfigPath, "derp-config-path", "", "CODER_DERP_CONFIG_PATH", "", - "Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp-servers/") - cliflag.BoolVarP(root.Flags(), &derpServerEnabled, "derp-server-enable", "", "CODER_DERP_SERVER_ENABLE", true, - "Whether to enable or disable the embedded DERP relay server.") - cliflag.IntVarP(root.Flags(), &derpServerRegionID, "derp-server-region-id", "", "CODER_DERP_SERVER_REGION_ID", 999, - "Region ID to use for the embedded DERP server.") - cliflag.StringVarP(root.Flags(), &derpServerRegionCode, "derp-server-region-code", "", "CODER_DERP_SERVER_REGION_CODE", "coder", - "Region code that for the embedded DERP server.") - cliflag.StringVarP(root.Flags(), &derpServerRegionName, "derp-server-region-name", "", "CODER_DERP_SERVER_REGION_NAME", "Coder Embedded Relay", - "Region name that for the embedded DERP server.") - cliflag.StringArrayVarP(root.Flags(), &derpServerSTUNAddrs, "derp-server-stun-addresses", "", "CODER_DERP_SERVER_STUN_ADDRESSES", []string{ - "stun.l.google.com:19302", - }, "Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections.") - cliflag.BoolVarP(root.Flags(), &promEnabled, "prometheus-enable", "", "CODER_PROMETHEUS_ENABLE", false, - "Serve prometheus metrics on the address defined by `prometheus-address`.") - cliflag.StringVarP(root.Flags(), &promAddress, "prometheus-address", "", "CODER_PROMETHEUS_ADDRESS", "127.0.0.1:2112", - "The bind address to serve prometheus metrics.") - cliflag.BoolVarP(root.Flags(), &pprofEnabled, "pprof-enable", "", "CODER_PPROF_ENABLE", false, - "Serve pprof metrics on the address defined by `pprof-address`.") - cliflag.StringVarP(root.Flags(), &pprofAddress, "pprof-address", "", "CODER_PPROF_ADDRESS", "127.0.0.1:6060", - "The bind address to serve pprof.") - - defaultCacheDir, err := os.UserCacheDir() - if err != nil { - defaultCacheDir = os.TempDir() - } - if dir := os.Getenv("CACHE_DIRECTORY"); dir != "" { - // For compatibility with systemd. - defaultCacheDir = dir - } - defaultCacheDir = filepath.Join(defaultCacheDir, "coder") - cliflag.StringVarP(root.Flags(), &cacheDir, "cache-dir", "", "CODER_CACHE_DIRECTORY", defaultCacheDir, - "The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd.") - cliflag.BoolVarP(root.Flags(), &inMemoryDatabase, "in-memory", "", "CODER_INMEMORY", false, - "Controls whether data will be stored in an in-memory database.") - _ = root.Flags().MarkHidden("in-memory") - cliflag.StringVarP(root.Flags(), &postgresURL, "postgres-url", "", "CODER_PG_CONNECTION_URL", "", - "URL of a PostgreSQL database. If empty, PostgreSQL binaries will be downloaded from Maven (https://repo1.maven.org/maven2) and store all data in the config root. Access the built-in database with \"coder server postgres-builtin-url\"") - cliflag.Uint8VarP(root.Flags(), &provisionerDaemonCount, "provisioner-daemons", "", "CODER_PROVISIONER_DAEMONS", 3, - "Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this.") - cliflag.StringVarP(root.Flags(), &oauth2GithubClientID, "oauth2-github-client-id", "", "CODER_OAUTH2_GITHUB_CLIENT_ID", "", - "Client ID for Login with GitHub.") - cliflag.StringVarP(root.Flags(), &oauth2GithubClientSecret, "oauth2-github-client-secret", "", "CODER_OAUTH2_GITHUB_CLIENT_SECRET", "", - "Client secret for Login with GitHub.") - cliflag.StringArrayVarP(root.Flags(), &oauth2GithubAllowedOrganizations, "oauth2-github-allowed-orgs", "", "CODER_OAUTH2_GITHUB_ALLOWED_ORGS", nil, - "Organizations the user must be a member of to Login with GitHub.") - cliflag.StringArrayVarP(root.Flags(), &oauth2GithubAllowedTeams, "oauth2-github-allowed-teams", "", "CODER_OAUTH2_GITHUB_ALLOWED_TEAMS", nil, - "Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /.") - cliflag.BoolVarP(root.Flags(), &oauth2GithubAllowSignups, "oauth2-github-allow-signups", "", "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS", false, - "Whether new users can sign up with GitHub.") - cliflag.StringVarP(root.Flags(), &oauth2GithubEnterpriseBaseURL, "oauth2-github-enterprise-base-url", "", "CODER_OAUTH2_GITHUB_ENTERPRISE_BASE_URL", "", - "Base URL of a GitHub Enterprise deployment to use for Login with GitHub.") - cliflag.BoolVarP(root.Flags(), &oidcAllowSignups, "oidc-allow-signups", "", "CODER_OIDC_ALLOW_SIGNUPS", true, - "Whether new users can sign up with OIDC.") - cliflag.StringVarP(root.Flags(), &oidcClientID, "oidc-client-id", "", "CODER_OIDC_CLIENT_ID", "", - "Client ID to use for Login with OIDC.") - cliflag.StringVarP(root.Flags(), &oidcClientSecret, "oidc-client-secret", "", "CODER_OIDC_CLIENT_SECRET", "", - "Client secret to use for Login with OIDC.") - cliflag.StringVarP(root.Flags(), &oidcEmailDomain, "oidc-email-domain", "", "CODER_OIDC_EMAIL_DOMAIN", "", - "Email domain that clients logging in with OIDC must match.") - cliflag.StringVarP(root.Flags(), &oidcIssuerURL, "oidc-issuer-url", "", "CODER_OIDC_ISSUER_URL", "", - "Issuer URL to use for Login with OIDC.") - cliflag.StringArrayVarP(root.Flags(), &oidcScopes, "oidc-scopes", "", "CODER_OIDC_SCOPES", []string{oidc.ScopeOpenID, "profile", "email"}, - "Scopes to grant when authenticating with OIDC.") - cliflag.BoolVarP(root.Flags(), &tailscaleEnable, "tailscale", "", "CODER_TAILSCALE", true, - "Specifies whether Tailscale networking is used for web applications and terminals.") - _ = root.Flags().MarkHidden("tailscale") - enableTelemetryByDefault := !isTest() - cliflag.BoolVarP(root.Flags(), &telemetryEnable, "telemetry", "", "CODER_TELEMETRY", enableTelemetryByDefault, - "Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product.") - cliflag.BoolVarP(root.Flags(), &telemetryTraceEnable, "telemetry-trace", "", "CODER_TELEMETRY_TRACE", enableTelemetryByDefault, - "Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option.") - cliflag.StringVarP(root.Flags(), &telemetryURL, "telemetry-url", "", "CODER_TELEMETRY_URL", "https://telemetry.coder.com", - "URL to send telemetry.") - _ = root.Flags().MarkHidden("telemetry-url") - cliflag.BoolVarP(root.Flags(), &tlsEnable, "tls-enable", "", "CODER_TLS_ENABLE", false, - "Whether TLS will be enabled.") - cliflag.StringArrayVarP(root.Flags(), &tlsCertFiles, "tls-cert-file", "", "CODER_TLS_CERT_FILE", []string{}, - "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.") - cliflag.StringVarP(root.Flags(), &tlsClientCAFile, "tls-client-ca-file", "", "CODER_TLS_CLIENT_CA_FILE", "", - "PEM-encoded Certificate Authority file used for checking the authenticity of client") - cliflag.StringVarP(root.Flags(), &tlsClientAuth, "tls-client-auth", "", "CODER_TLS_CLIENT_AUTH", "request", - `Policy the server will follow for TLS Client Authentication. `+ - `Accepted values are "none", "request", "require-any", "verify-if-given", or "require-and-verify"`) - cliflag.StringArrayVarP(root.Flags(), &tlsKeyFiles, "tls-key-file", "", "CODER_TLS_KEY_FILE", []string{}, - "Paths to the private keys for each of the certificates. It requires a PEM-encoded file") - cliflag.StringVarP(root.Flags(), &tlsMinVersion, "tls-min-version", "", "CODER_TLS_MIN_VERSION", "tls12", - `Minimum supported version of TLS. Accepted values are "tls10", "tls11", "tls12" or "tls13"`) - cliflag.BoolVarP(root.Flags(), &traceEnable, "trace", "", "CODER_TRACE", false, - "Whether application tracing data is collected.") - cliflag.BoolVarP(root.Flags(), &secureAuthCookie, "secure-auth-cookie", "", "CODER_SECURE_AUTH_COOKIE", false, - "Controls if the 'Secure' property is set on browser session cookies") - cliflag.StringVarP(root.Flags(), &sshKeygenAlgorithmRaw, "ssh-keygen-algorithm", "", "CODER_SSH_KEYGEN_ALGORITHM", "ed25519", - "The algorithm to use for generating ssh keys. "+ - `Accepted values are "ed25519", "ecdsa", or "rsa4096"`) - cliflag.StringArrayVarP(root.Flags(), &autoImportTemplates, "auto-import-template", "", "CODER_TEMPLATE_AUTOIMPORT", []string{}, - "Templates to auto-import. Available auto-importable templates are: kubernetes") - _ = root.Flags().MarkHidden("auto-import-template") - cliflag.BoolVarP(root.Flags(), &spooky, "spooky", "", "", false, "Specifies spookiness level...") - _ = root.Flags().MarkHidden("spooky") - cliflag.BoolVarP(root.Flags(), &verbose, "verbose", "v", "CODER_VERBOSE", false, - "Enables verbose logging.") - - // These metrics flags are for manually testing the metric system. - // The defaults should be acceptable for any Coder deployment of any - // reasonable size. - cliflag.DurationVarP(root.Flags(), &metricsCacheRefreshInterval, "metrics-cache-refresh-interval", "", "CODER_METRICS_CACHE_REFRESH_INTERVAL", time.Hour, "How frequently metrics are refreshed") - _ = root.Flags().MarkHidden("metrics-cache-refresh-interval") - cliflag.DurationVarP(root.Flags(), &agentStatRefreshInterval, "agent-stats-refresh-interval", "", "CODER_AGENT_STATS_REFRESH_INTERVAL", time.Minute*10, "How frequently agent stats are recorded") - _ = root.Flags().MarkHidden("agent-stats-refresh-interval") + deployment.StringFlag(root.Flags(), &dflags.AccessURL) + deployment.StringFlag(root.Flags(), &dflags.WildcardAccessURL) + deployment.StringFlag(root.Flags(), &dflags.Address) + deployment.DurationFlag(root.Flags(), &dflags.AutobuildPollInterval) + _ = root.Flags().MarkHidden(dflags.AutobuildPollInterval.Flag) + deployment.BoolFlag(root.Flags(), &dflags.DerpServerEnable) + deployment.IntFlag(root.Flags(), &dflags.DerpServerRegionID) + deployment.StringFlag(root.Flags(), &dflags.DerpServerRegionCode) + deployment.StringFlag(root.Flags(), &dflags.DerpServerRegionName) + deployment.StringArrayFlag(root.Flags(), &dflags.DerpServerSTUNAddresses) + deployment.StringFlag(root.Flags(), &dflags.DerpConfigURL) + deployment.StringFlag(root.Flags(), &dflags.DerpConfigPath) + deployment.BoolFlag(root.Flags(), &dflags.PromEnabled) + deployment.StringFlag(root.Flags(), &dflags.PromAddress) + deployment.BoolFlag(root.Flags(), &dflags.PprofEnabled) + deployment.StringFlag(root.Flags(), &dflags.CacheDir) + deployment.BoolFlag(root.Flags(), &dflags.InMemoryDatabase) + _ = root.Flags().MarkHidden(dflags.InMemoryDatabase.Flag) + deployment.IntFlag(root.Flags(), &dflags.ProvisionerDaemonCount) + deployment.StringFlag(root.Flags(), &dflags.PostgresURL) + deployment.StringFlag(root.Flags(), &dflags.OAuth2GithubClientID) + deployment.StringFlag(root.Flags(), &dflags.OAuth2GithubClientSecret) + deployment.StringArrayFlag(root.Flags(), &dflags.OAuth2GithubAllowedOrganizations) + deployment.StringArrayFlag(root.Flags(), &dflags.OAuth2GithubAllowedTeams) + deployment.BoolFlag(root.Flags(), &dflags.OAuth2GithubAllowSignups) + deployment.StringFlag(root.Flags(), &dflags.OAuth2GithubEnterpriseBaseURL) + deployment.BoolFlag(root.Flags(), &dflags.OIDCAllowSignups) + deployment.StringFlag(root.Flags(), &dflags.OIDCClientID) + deployment.StringFlag(root.Flags(), &dflags.OIDCClientSecret) + deployment.StringFlag(root.Flags(), &dflags.OIDCEmailDomain) + deployment.StringFlag(root.Flags(), &dflags.OIDCIssuerURL) + deployment.StringArrayFlag(root.Flags(), &dflags.OIDCScopes) + deployment.BoolFlag(root.Flags(), &dflags.TelemetryEnable) + deployment.BoolFlag(root.Flags(), &dflags.TelemetryTraceEnable) + deployment.StringFlag(root.Flags(), &dflags.TelemetryURL) + _ = root.Flags().MarkHidden(dflags.TelemetryURL.Flag) + deployment.BoolFlag(root.Flags(), &dflags.TLSEnable) + deployment.StringArrayFlag(root.Flags(), &dflags.TLSCertFiles) + deployment.StringFlag(root.Flags(), &dflags.TLSClientCAFile) + deployment.StringFlag(root.Flags(), &dflags.TLSClientAuth) + deployment.StringArrayFlag(root.Flags(), &dflags.TLSKeyFiles) + deployment.StringFlag(root.Flags(), &dflags.TLSMinVersion) + deployment.BoolFlag(root.Flags(), &dflags.TraceEnable) + deployment.BoolFlag(root.Flags(), &dflags.SecureAuthCookie) + deployment.StringFlag(root.Flags(), &dflags.SSHKeygenAlgorithm) + deployment.StringArrayFlag(root.Flags(), &dflags.AutoImportTemplates) + _ = root.Flags().MarkHidden(dflags.AutoImportTemplates.Flag) + deployment.DurationFlag(root.Flags(), &dflags.MetricsCacheRefreshInterval) + _ = root.Flags().MarkHidden(dflags.MetricsCacheRefreshInterval.Flag) + deployment.DurationFlag(root.Flags(), &dflags.AgentStatRefreshInterval) + _ = root.Flags().MarkHidden(dflags.AgentStatRefreshInterval.Flag) + deployment.BoolFlag(root.Flags(), &dflags.Verbose) return root } @@ -1016,21 +907,7 @@ func newProvisionerDaemon( } // nolint: revive -func printLogo(cmd *cobra.Command, spooky bool) { - if spooky { - _, _ = fmt.Fprintf(cmd.OutOrStdout(), `▄████▄ ▒█████ ▓█████▄ ▓█████ ██▀███ -▒██▀ ▀█ ▒██▒ ██▒▒██▀ ██▌▓█ ▀ ▓██ ▒ ██▒ -▒▓█ ▄ ▒██░ ██▒░██ █▌▒███ ▓██ ░▄█ ▒ -▒▓▓▄ ▄██▒▒██ ██░░▓█▄ ▌▒▓█ ▄ ▒██▀▀█▄ -▒ ▓███▀ ░░ ████▓▒░░▒████▓ ░▒████▒░██▓ ▒██▒ -░ ░▒ ▒ ░░ ▒░▒░▒░ ▒▒▓ ▒ ░░ ▒░ ░░ ▒▓ ░▒▓░ - ░ ▒ ░ ▒ ▒░ ░ ▒ ▒ ░ ░ ░ ░▒ ░ ▒░ -░ ░ ░ ░ ▒ ░ ░ ░ ░ ░░ ░ -░ ░ ░ ░ ░ ░ ░ ░ -░ ░ -`) - return - } +func printLogo(cmd *cobra.Command) { _, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s - Remote development on your infrastucture\n", cliui.Styles.Bold.Render("Coder "+buildinfo.Version())) } diff --git a/coderd/coderd.go b/coderd/coderd.go index 35bcfdf9e6..a27b25b043 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -82,6 +82,7 @@ type Options struct { MetricsCacheRefreshInterval time.Duration AgentStatsRefreshInterval time.Duration Experimental bool + DeploymentFlags *codersdk.DeploymentFlags } // New constructs a Coder API handler. @@ -259,6 +260,10 @@ func New(options *Options) *API { }) }) }) + r.Route("/flags", func(r chi.Router) { + r.Use(apiKeyMiddleware) + r.Get("/deployment", api.deploymentFlags) + }) r.Route("/audit", func(r chi.Router) { r.Use( apiKeyMiddleware, diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 16d9c099fc..4f7fd2692a 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -83,6 +83,7 @@ type Options struct { IncludeProvisionerDaemon bool MetricsCacheRefreshInterval time.Duration AgentStatsRefreshInterval time.Duration + DeploymentFlags *codersdk.DeploymentFlags } // New constructs a codersdk client connected to an in-memory API instance. @@ -237,6 +238,7 @@ func NewOptions(t *testing.T, options *Options) (*httptest.Server, context.Cance AutoImportTemplates: options.AutoImportTemplates, MetricsCacheRefreshInterval: options.MetricsCacheRefreshInterval, AgentStatsRefreshInterval: options.AgentStatsRefreshInterval, + DeploymentFlags: options.DeploymentFlags, } } diff --git a/coderd/flags.go b/coderd/flags.go new file mode 100644 index 0000000000..7e2f1376df --- /dev/null +++ b/coderd/flags.go @@ -0,0 +1,18 @@ +package coderd + +import ( + "net/http" + + "github.com/coder/coder/cli/deployment" + "github.com/coder/coder/coderd/httpapi" + "github.com/coder/coder/coderd/rbac" +) + +func (api *API) deploymentFlags(rw http.ResponseWriter, r *http.Request) { + if !api.Authorize(r, rbac.ActionRead, rbac.ResourceDeploymentFlags) { + httpapi.Forbidden(rw) + return + } + + httpapi.Write(r.Context(), rw, http.StatusOK, deployment.RemoveSensitiveValues(*api.DeploymentFlags)) +} diff --git a/coderd/flags_test.go b/coderd/flags_test.go new file mode 100644 index 0000000000..b653168316 --- /dev/null +++ b/coderd/flags_test.go @@ -0,0 +1,47 @@ +package coderd_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/coder/coder/cli/deployment" + "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/testutil" +) + +const ( + secretValue = "********" +) + +func TestDeploymentFlagSecrets(t *testing.T) { + t.Parallel() + hi := "hi" + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + df := deployment.Flags() + // check if copy works for non-secret values + df.AccessURL.Value = hi + // check if secrets are removed + df.OAuth2GithubClientSecret.Value = hi + df.OIDCClientSecret.Value = hi + df.PostgresURL.Value = hi + df.SCIMAuthHeader.Value = hi + + client := coderdtest.New(t, &coderdtest.Options{ + DeploymentFlags: &df, + }) + _ = coderdtest.CreateFirstUser(t, client) + scrubbed, err := client.DeploymentFlags(ctx) + require.NoError(t, err) + // ensure df is unchanged + require.EqualValues(t, hi, df.OAuth2GithubClientSecret.Value) + // ensure normal values pass through + require.EqualValues(t, hi, scrubbed.AccessURL.Value) + // ensure secrets are removed + require.EqualValues(t, secretValue, scrubbed.OAuth2GithubClientSecret.Value) + require.EqualValues(t, secretValue, scrubbed.OIDCClientSecret.Value) + require.EqualValues(t, secretValue, scrubbed.PostgresURL.Value) + require.EqualValues(t, secretValue, scrubbed.SCIMAuthHeader.Value) +} diff --git a/coderd/rbac/object.go b/coderd/rbac/object.go index f56804b774..1a1e86738c 100644 --- a/coderd/rbac/object.go +++ b/coderd/rbac/object.go @@ -133,6 +133,11 @@ var ( ResourceLicense = Object{ Type: "license", } + + // ResourceDeploymentFlags + ResourceDeploymentFlags = Object{ + Type: "deployment_flags", + } ) // Object is used to create objects for authz checks when you have none in diff --git a/codersdk/flags.go b/codersdk/flags.go new file mode 100644 index 0000000000..e603907242 --- /dev/null +++ b/codersdk/flags.go @@ -0,0 +1,136 @@ +package codersdk + +import ( + "context" + "encoding/json" + "net/http" + "time" + + "golang.org/x/xerrors" +) + +type DeploymentFlags struct { + AccessURL StringFlag `json:"access_url"` + WildcardAccessURL StringFlag `json:"wildcard_access_url"` + Address StringFlag `json:"address"` + AutobuildPollInterval DurationFlag `json:"autobuild_poll_interval"` + DerpServerEnable BoolFlag `json:"derp_server_enabled"` + DerpServerRegionID IntFlag `json:"derp_server_region_id"` + DerpServerRegionCode StringFlag `json:"derp_server_region_code"` + DerpServerRegionName StringFlag `json:"derp_server_region_name"` + DerpServerSTUNAddresses StringArrayFlag `json:"derp_server_stun_address"` + DerpConfigURL StringFlag `json:"derp_config_url"` + DerpConfigPath StringFlag `json:"derp_config_path"` + PromEnabled BoolFlag `json:"prom_enabled"` + PromAddress StringFlag `json:"prom_address"` + PprofEnabled BoolFlag `json:"pprof_enabled"` + PprofAddress StringFlag `json:"pprof_address"` + CacheDir StringFlag `json:"cache_dir"` + InMemoryDatabase BoolFlag `json:"in_memory_database"` + ProvisionerDaemonCount IntFlag `json:"provisioner_daemon_count"` + PostgresURL StringFlag `json:"postgres_url"` + OAuth2GithubClientID StringFlag `json:"oauth2_github_client_id"` + OAuth2GithubClientSecret StringFlag `json:"oauth2_github_client_secret"` + OAuth2GithubAllowedOrganizations StringArrayFlag `json:"oauth2_github_allowed_organizations"` + OAuth2GithubAllowedTeams StringArrayFlag `json:"oauth2_github_allowed_teams"` + OAuth2GithubAllowSignups BoolFlag `json:"oauth2_github_allow_signups"` + OAuth2GithubEnterpriseBaseURL StringFlag `json:"oauth2_github_enterprise_base_url"` + OIDCAllowSignups BoolFlag `json:"oidc_allow_signups"` + OIDCClientID StringFlag `json:"oidc_client_id"` + OIDCClientSecret StringFlag `json:"oidc_cliet_secret"` + OIDCEmailDomain StringFlag `json:"oidc_email_domain"` + OIDCIssuerURL StringFlag `json:"oidc_issuer_url"` + OIDCScopes StringArrayFlag `json:"oidc_scopes"` + TelemetryEnable BoolFlag `json:"telemetry_enable"` + TelemetryTraceEnable BoolFlag `json:"telemetry_trace_enable"` + TelemetryURL StringFlag `json:"telemetry_url"` + TLSEnable BoolFlag `json:"tls_enable"` + TLSCertFiles StringArrayFlag `json:"tls_cert_files"` + TLSClientCAFile StringFlag `json:"tls_client_ca_file"` + TLSClientAuth StringFlag `json:"tls_client_auth"` + TLSKeyFiles StringArrayFlag `json:"tls_key_tiles"` + TLSMinVersion StringFlag `json:"tls_min_version"` + TraceEnable BoolFlag `json:"trace_enable"` + SecureAuthCookie BoolFlag `json:"secure_auth_cookie"` + SSHKeygenAlgorithm StringFlag `json:"ssh_keygen_algorithm"` + AutoImportTemplates StringArrayFlag `json:"auto_import_templates"` + MetricsCacheRefreshInterval DurationFlag `json:"metrics_cache_refresh_interval"` + AgentStatRefreshInterval DurationFlag `json:"agent_stat_refresh_interval"` + Verbose BoolFlag `json:"verbose"` + AuditLogging BoolFlag `json:"audit_logging"` + BrowserOnly BoolFlag `json:"browser_only"` + SCIMAuthHeader StringFlag `json:"scim_auth_header"` + UserWorkspaceQuota IntFlag `json:"user_workspace_quota"` +} + +type StringFlag struct { + Name string `json:"name"` + Flag string `json:"flag"` + EnvVar string `json:"env_var"` + Shorthand string `json:"shorthand"` + Description string `json:"description"` + Enterprise bool `json:"enterprise"` + Secret bool `json:"secret"` + Default string `json:"default"` + Value string `json:"value"` +} + +type BoolFlag struct { + Name string `json:"name"` + Flag string `json:"flag"` + EnvVar string `json:"env_var"` + Shorthand string `json:"shorthand"` + Description string `json:"description"` + Enterprise bool `json:"enterprise"` + Default bool `json:"default"` + Value bool `json:"value"` +} + +type IntFlag struct { + Name string `json:"name"` + Flag string `json:"flag"` + EnvVar string `json:"env_var"` + Shorthand string `json:"shorthand"` + Description string `json:"description"` + Enterprise bool `json:"enterprise"` + Default int `json:"default"` + Value int `json:"value"` +} + +type DurationFlag struct { + Name string `json:"name"` + Flag string `json:"flag"` + EnvVar string `json:"env_var"` + Shorthand string `json:"shorthand"` + Description string `json:"description"` + Enterprise bool `json:"enterprise"` + Default time.Duration `json:"default"` + Value time.Duration `json:"value"` +} + +type StringArrayFlag struct { + Name string `json:"name"` + Flag string `json:"flag"` + EnvVar string `json:"env_var"` + Shorthand string `json:"shorthand"` + Description string `json:"description"` + Enterprise bool `json:"enterprise"` + Default []string `json:"default"` + Value []string `json:"value"` +} + +// DeploymentFlags returns the deployment level flags for the coder server. +func (c *Client) DeploymentFlags(ctx context.Context) (DeploymentFlags, error) { + res, err := c.Request(ctx, http.MethodGet, "/api/v2/flags/deployment", nil) + if err != nil { + return DeploymentFlags{}, xerrors.Errorf("execute request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return DeploymentFlags{}, readBodyAsError(res) + } + + var df DeploymentFlags + return df, json.NewDecoder(res.Body).Decode(&df) +} diff --git a/enterprise/cli/server.go b/enterprise/cli/server.go index 6dde1c31cd..3c2c373e09 100644 --- a/enterprise/cli/server.go +++ b/enterprise/cli/server.go @@ -5,8 +5,8 @@ import ( "github.com/spf13/cobra" - "github.com/coder/coder/cli/cliflag" "github.com/coder/coder/cli/cliui" + "github.com/coder/coder/cli/deployment" "github.com/coder/coder/enterprise/coderd" agpl "github.com/coder/coder/cli" @@ -14,35 +14,34 @@ import ( ) func server() *cobra.Command { - var ( - auditLogging bool - browserOnly bool - scimAuthHeader string - userWorkspaceQuota int - ) - cmd := agpl.Server(func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, error) { - api, err := coderd.New(ctx, &coderd.Options{ - AuditLogging: auditLogging, - BrowserOnly: browserOnly, - SCIMAPIKey: []byte(scimAuthHeader), - UserWorkspaceQuota: userWorkspaceQuota, + dflags := deployment.Flags() + cmd := agpl.Server(dflags, func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, error) { + options.DeploymentFlags = &dflags + o := &coderd.Options{ + AuditLogging: dflags.AuditLogging.Value, + BrowserOnly: dflags.BrowserOnly.Value, + SCIMAPIKey: []byte(dflags.SCIMAuthHeader.Value), + UserWorkspaceQuota: dflags.UserWorkspaceQuota.Value, Options: options, - }) + } + api, err := coderd.New(ctx, o) if err != nil { return nil, err } return api.AGPL, nil }) - enterpriseOnly := cliui.Styles.Keyword.Render("This is an Enterprise feature. Contact sales@coder.com for licensing") - cliflag.BoolVarP(cmd.Flags(), &auditLogging, "audit-logging", "", "CODER_AUDIT_LOGGING", true, - "Specifies whether audit logging is enabled. "+enterpriseOnly) - cliflag.BoolVarP(cmd.Flags(), &browserOnly, "browser-only", "", "CODER_BROWSER_ONLY", false, - "Whether Coder only allows connections to workspaces via the browser. "+enterpriseOnly) - cliflag.StringVarP(cmd.Flags(), &scimAuthHeader, "scim-auth-header", "", "CODER_SCIM_API_KEY", "", - "Enables SCIM and sets the authentication header for the built-in SCIM server. New users are automatically created with OIDC authentication. "+enterpriseOnly) - cliflag.IntVarP(cmd.Flags(), &userWorkspaceQuota, "user-workspace-quota", "", "CODER_USER_WORKSPACE_QUOTA", 0, - "A positive number applies a limit on how many workspaces each user can create. "+enterpriseOnly) + // append enterprise description to flags + enterpriseOnly := cliui.Styles.Keyword.Render(" This is an Enterprise feature. Contact sales@coder.com for licensing") + dflags.AuditLogging.Description += enterpriseOnly + dflags.BrowserOnly.Description += enterpriseOnly + dflags.SCIMAuthHeader.Description += enterpriseOnly + dflags.UserWorkspaceQuota.Description += enterpriseOnly + + deployment.BoolFlag(cmd.Flags(), &dflags.AuditLogging) + deployment.BoolFlag(cmd.Flags(), &dflags.BrowserOnly) + deployment.StringFlag(cmd.Flags(), &dflags.SCIMAuthHeader) + deployment.IntFlag(cmd.Flags(), &dflags.UserWorkspaceQuota) return cmd } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 198acebcf3..c5188be536 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -125,6 +125,18 @@ export interface AzureInstanceIdentityToken { readonly encoding: string } +// From codersdk/flags.go +export interface BoolFlag { + readonly name: string + readonly flag: string + readonly env_var: string + readonly shorthand: string + readonly description: string + readonly enterprise: boolean + readonly default: boolean + readonly value: boolean +} + // From codersdk/buildinfo.go export interface BuildInfoResponse { readonly external_url: string @@ -239,6 +251,75 @@ export interface DERPRegion { readonly latency_ms: number } +// From codersdk/flags.go +export interface DeploymentFlags { + readonly access_url: StringFlag + readonly wildcard_access_url: StringFlag + readonly address: StringFlag + readonly autobuild_poll_interval: DurationFlag + readonly derp_server_enabled: BoolFlag + readonly derp_server_region_id: IntFlag + readonly derp_server_region_code: StringFlag + readonly derp_server_region_name: StringFlag + readonly derp_server_stun_address: StringArrayFlag + readonly derp_config_url: StringFlag + readonly derp_config_path: StringFlag + readonly prom_enabled: BoolFlag + readonly prom_address: StringFlag + readonly pprof_enabled: BoolFlag + readonly pprof_address: StringFlag + readonly cache_dir: StringFlag + readonly in_memory_database: BoolFlag + readonly provisioner_daemon_count: IntFlag + readonly postgres_url: StringFlag + readonly oauth2_github_client_id: StringFlag + readonly oauth2_github_client_secret: StringFlag + readonly oauth2_github_allowed_organizations: StringArrayFlag + readonly oauth2_github_allowed_teams: StringArrayFlag + readonly oauth2_github_allow_signups: BoolFlag + readonly oauth2_github_enterprise_base_url: StringFlag + readonly oidc_allow_signups: BoolFlag + readonly oidc_client_id: StringFlag + readonly oidc_cliet_secret: StringFlag + readonly oidc_email_domain: StringFlag + readonly oidc_issuer_url: StringFlag + readonly oidc_scopes: StringArrayFlag + readonly telemetry_enable: BoolFlag + readonly telemetry_trace_enable: BoolFlag + readonly telemetry_url: StringFlag + readonly tls_enable: BoolFlag + readonly tls_cert_files: StringArrayFlag + readonly tls_client_ca_file: StringFlag + readonly tls_client_auth: StringFlag + readonly tls_key_tiles: StringArrayFlag + readonly tls_min_version: StringFlag + readonly trace_enable: BoolFlag + readonly secure_auth_cookie: BoolFlag + readonly ssh_keygen_algorithm: StringFlag + readonly auto_import_templates: StringArrayFlag + readonly metrics_cache_refresh_interval: DurationFlag + readonly agent_stat_refresh_interval: DurationFlag + readonly verbose: BoolFlag + readonly audit_logging: BoolFlag + readonly browser_only: BoolFlag + readonly scim_auth_header: StringFlag + readonly user_workspace_quota: IntFlag +} + +// From codersdk/flags.go +export interface DurationFlag { + readonly name: string + readonly flag: string + readonly env_var: string + readonly shorthand: string + readonly description: string + readonly enterprise: boolean + // This is likely an enum in an external package ("time.Duration") + readonly default: number + // This is likely an enum in an external package ("time.Duration") + readonly value: number +} + // From codersdk/features.go export interface Entitlements { readonly features: Record @@ -281,6 +362,18 @@ export interface Healthcheck { readonly threshold: number } +// From codersdk/flags.go +export interface IntFlag { + readonly name: string + readonly flag: string + readonly env_var: string + readonly shorthand: string + readonly description: string + readonly enterprise: boolean + readonly default: number + readonly value: number +} + // From codersdk/licenses.go export interface License { readonly id: number @@ -425,6 +518,31 @@ export interface ServerSentEvent { readonly data: any } +// From codersdk/flags.go +export interface StringArrayFlag { + readonly name: string + readonly flag: string + readonly env_var: string + readonly shorthand: string + readonly description: string + readonly enterprise: boolean + readonly default: string[] + readonly value: string[] +} + +// From codersdk/flags.go +export interface StringFlag { + readonly name: string + readonly flag: string + readonly env_var: string + readonly shorthand: string + readonly description: string + readonly enterprise: boolean + readonly secret: boolean + readonly default: string + readonly value: string +} + // From codersdk/templates.go export interface Template { readonly id: string