diff --git a/cli/config/file.go b/cli/config/file.go index 388ce0881f304..26af6896c14b3 100644 --- a/cli/config/file.go +++ b/cli/config/file.go @@ -6,6 +6,10 @@ import ( "path/filepath" ) +const ( + FlagName = "global-config" +) + // Root represents the configuration directory. type Root string @@ -42,6 +46,10 @@ func (r Root) PostgresPort() File { return File(filepath.Join(r.PostgresPath(), "port")) } +func (r Root) DeploymentConfigPath() string { + return filepath.Join(string(r), "server.yaml") +} + // File provides convenience methods for interacting with *os.File. type File string diff --git a/cli/deployment/config.go b/cli/deployment/config.go new file mode 100644 index 0000000000000..42a875496b090 --- /dev/null +++ b/cli/deployment/config.go @@ -0,0 +1,437 @@ +package deployment + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "reflect" + "strings" + "time" + + "github.com/coreos/go-oidc/v3/oidc" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "golang.org/x/xerrors" + + "github.com/coder/coder/cli/cliui" + "github.com/coder/coder/cli/config" + "github.com/coder/coder/codersdk" +) + +func newConfig() codersdk.DeploymentConfig { + return codersdk.DeploymentConfig{ + AccessURL: codersdk.DeploymentConfigField[string]{ + Key: "access_url", + Usage: "External URL to access your deployment. This must be accessible by all provisioned workspaces.", + Flag: "access-url", + }, + WildcardAccessURL: codersdk.DeploymentConfigField[string]{ + Key: "wildcard_access_url", + Usage: "Specifies the wildcard hostname to use for workspace applications in the form \"*.example.com\".", + Flag: "wildcard-access-url", + }, + Address: codersdk.DeploymentConfigField[string]{ + Key: "address", + Usage: "Bind address of the server.", + Flag: "address", + Shorthand: "a", + Value: "127.0.0.1:3000", + }, + AutobuildPollInterval: codersdk.DeploymentConfigField[time.Duration]{ + Key: "autobuild_poll_interval", + Usage: "Interval to poll for scheduled workspace builds.", + Flag: "autobuild-poll-interval", + Hidden: true, + Value: time.Minute, + }, + DERPServerEnable: codersdk.DeploymentConfigField[bool]{ + Key: "derp.server.enable", + Usage: "Whether to enable or disable the embedded DERP relay server.", + Flag: "derp-server-enable", + Value: true, + }, + DERPServerRegionID: codersdk.DeploymentConfigField[int]{ + Key: "derp.server.region_id", + Usage: "Region ID to use for the embedded DERP server.", + Flag: "derp-server-region-id", + Value: 999, + }, + DERPServerRegionCode: codersdk.DeploymentConfigField[string]{ + Key: "derp.server.region_code", + Usage: "Region code to use for the embedded DERP server.", + Flag: "derp-server-region-code", + Value: "coder", + }, + DERPServerRegionName: codersdk.DeploymentConfigField[string]{ + Key: "derp.server.region_name", + Usage: "Region name that for the embedded DERP server.", + Flag: "derp-server-region-name", + Value: "Coder Embedded Relay", + }, + DERPServerSTUNAddresses: codersdk.DeploymentConfigField[[]string]{ + Key: "derp.server.stun_addresses", + Usage: "Addresses for STUN servers to establish P2P connections. Set empty to disable P2P connections.", + Flag: "derp-server-stun-addresses", + Value: []string{"stun.l.google.com:19302"}, + }, + DERPServerRelayAddress: codersdk.DeploymentConfigField[string]{ + Key: "derp.server.relay_address", + Usage: "An HTTP address that is accessible by other replicas to relay DERP traffic. Required for high availability.", + Flag: "derp-server-relay-address", + Enterprise: true, + }, + DERPConfigURL: codersdk.DeploymentConfigField[string]{ + Key: "derp.config.url", + Usage: "URL to fetch a DERP mapping on startup. See: https://tailscale.com/kb/1118/custom-derp-servers/", + Flag: "derp-config-url", + }, + DERPConfigPath: codersdk.DeploymentConfigField[string]{ + Key: "derp.config.path", + Usage: "Path to read a DERP mapping from. See: https://tailscale.com/kb/1118/custom-derp-servers/", + Flag: "derp-config-path", + }, + PrometheusEnable: codersdk.DeploymentConfigField[bool]{ + Key: "prometheus.enable", + Usage: "Serve prometheus metrics on the address defined by prometheus address.", + Flag: "prometheus-enable", + }, + PrometheusAddress: codersdk.DeploymentConfigField[string]{ + Key: "prometheus.address", + Usage: "The bind address to serve prometheus metrics.", + Flag: "prometheus-address", + Value: "127.0.0.1:2112", + }, + PprofEnable: codersdk.DeploymentConfigField[bool]{ + Key: "pprof.enable", + Usage: "Serve pprof metrics on the address defined by pprof address.", + Flag: "pprof-enable", + }, + PprofAddress: codersdk.DeploymentConfigField[string]{ + Key: "pprof.address", + Usage: "The bind address to serve pprof.", + Flag: "pprof-address", + Value: "127.0.0.1:6060", + }, + CacheDirectory: codersdk.DeploymentConfigField[string]{ + Key: "cache_directory", + Usage: "The directory to cache temporary files. If unspecified and $CACHE_DIRECTORY is set, it will be used for compatibility with systemd.", + Flag: "cache-dir", + Value: defaultCacheDir(), + }, + InMemoryDatabase: codersdk.DeploymentConfigField[bool]{ + Key: "in_memory_database", + Usage: "Controls whether data will be stored in an in-memory database.", + Flag: "in-memory", + Hidden: true, + }, + ProvisionerDaemons: codersdk.DeploymentConfigField[int]{ + Key: "provisioner.daemons", + Usage: "Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this.", + Flag: "provisioner-daemons", + Value: 3, + }, + PostgresURL: codersdk.DeploymentConfigField[string]{ + Key: "pg_connection_url", + Usage: "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\".", + Flag: "postgres-url", + }, + OAuth2GithubClientID: codersdk.DeploymentConfigField[string]{ + Key: "oauth2.github.client_id", + Usage: "Client ID for Login with GitHub.", + Flag: "oauth2-github-client-id", + }, + OAuth2GithubClientSecret: codersdk.DeploymentConfigField[string]{ + Key: "oauth2.github.client_secret", + Usage: "Client secret for Login with GitHub.", + Flag: "oauth2-github-client-secret", + }, + OAuth2GithubAllowedOrganizations: codersdk.DeploymentConfigField[[]string]{ + Key: "oauth2.github.allowed_organizations", + Usage: "Organizations the user must be a member of to Login with GitHub.", + Flag: "oauth2-github-allowed-orgs", + }, + OAuth2GithubAllowedTeams: codersdk.DeploymentConfigField[[]string]{ + Key: "oauth2.github.allowed_teams", + Usage: "Teams inside organizations the user must be a member of to Login with GitHub. Structured as: /.", + Flag: "oauth2-github-allowed-teams", + }, + OAuth2GithubAllowSignups: codersdk.DeploymentConfigField[bool]{ + Key: "oauth2.github.allow_signups", + Usage: "Whether new users can sign up with GitHub.", + Flag: "oauth2-github-allow-signups", + }, + OAuth2GithubEnterpriseBaseURL: codersdk.DeploymentConfigField[string]{ + Key: "oauth2.github.enterprise_base_url", + Usage: "Base URL of a GitHub Enterprise deployment to use for Login with GitHub.", + Flag: "oauth2-github-enterprise-base-url", + }, + OIDCAllowSignups: codersdk.DeploymentConfigField[bool]{ + Key: "oidc.allow_signups", + Usage: "Whether new users can sign up with OIDC.", + Flag: "oidc-allow-signups", + Value: true, + }, + OIDCClientID: codersdk.DeploymentConfigField[string]{ + Key: "oidc.client_id", + Usage: "Client ID to use for Login with OIDC.", + Flag: "oidc-client-id", + }, + OIDCClientSecret: codersdk.DeploymentConfigField[string]{ + Key: "oidc.client_secret", + Usage: "Client secret to use for Login with OIDC.", + Flag: "oidc-client-secret", + }, + OIDCEmailDomain: codersdk.DeploymentConfigField[string]{ + Key: "oidc.email_domain", + Usage: "Email domain that clients logging in with OIDC must match.", + Flag: "oidc-email-domain", + }, + OIDCIssuerURL: codersdk.DeploymentConfigField[string]{ + Key: "oidc.issuer_url", + Usage: "Issuer URL to use for Login with OIDC.", + Flag: "oidc-issuer-url", + }, + OIDCScopes: codersdk.DeploymentConfigField[[]string]{ + Key: "oidc.scopes", + Usage: "Scopes to grant when authenticating with OIDC.", + Flag: "oidc-scopes", + Value: []string{oidc.ScopeOpenID, "profile", "email"}, + }, + TelemetryEnable: codersdk.DeploymentConfigField[bool]{ + Key: "telemetry.enable", + Usage: "Whether telemetry is enabled or not. Coder collects anonymized usage data to help improve our product.", + Flag: "telemetry", + Value: flag.Lookup("test.v") == nil, + }, + TelemetryTrace: codersdk.DeploymentConfigField[bool]{ + Key: "telemetry.trace", + Usage: "Whether Opentelemetry traces are sent to Coder. Coder collects anonymized application tracing to help improve our product. Disabling telemetry also disables this option.", + Flag: "telemetry-trace", + Value: flag.Lookup("test.v") == nil, + }, + TelemetryURL: codersdk.DeploymentConfigField[string]{ + Key: "telemetry.url", + Usage: "URL to send telemetry.", + Flag: "telemetry-url", + Hidden: true, + Value: "https://telemetry.coder.com", + }, + TLSEnable: codersdk.DeploymentConfigField[bool]{ + Key: "tls.enable", + Usage: "Whether TLS will be enabled.", + Flag: "tls-enable", + }, + TLSCertFiles: codersdk.DeploymentConfigField[[]string]{ + Key: "tls.cert_file", + Usage: "Path to each certificate for TLS. It requires a PEM-encoded file. To configure the listener to use a CA certificate, concatenate the primary certificate and the CA certificate together. The primary certificate should appear first in the combined file.", + Flag: "tls-cert-file", + }, + TLSClientCAFile: codersdk.DeploymentConfigField[string]{ + Key: "tls.client_ca_file", + Usage: "PEM-encoded Certificate Authority file used for checking the authenticity of client", + Flag: "tls-client-ca-file", + }, + TLSClientAuth: codersdk.DeploymentConfigField[string]{ + Key: "tls.client_auth", + Usage: "Policy the server will follow for TLS Client Authentication. Accepted values are \"none\", \"request\", \"require-any\", \"verify-if-given\", or \"require-and-verify\".", + Flag: "tls-client-auth", + Value: "request", + }, + TLSKeyFiles: codersdk.DeploymentConfigField[[]string]{ + Key: "tls.key_file", + Usage: "Paths to the private keys for each of the certificates. It requires a PEM-encoded file.", + Flag: "tls-key-file", + }, + TLSMinVersion: codersdk.DeploymentConfigField[string]{ + Key: "tls.min_version", + Usage: "Minimum supported version of TLS. Accepted values are \"tls10\", \"tls11\", \"tls12\" or \"tls13\"", + Flag: "tls-min-version", + Value: "tls12", + }, + TraceEnable: codersdk.DeploymentConfigField[bool]{ + Key: "trace", + Usage: "Whether application tracing data is collected.", + Flag: "trace", + }, + SecureAuthCookie: codersdk.DeploymentConfigField[bool]{ + Key: "secure_auth_cookie", + Usage: "Controls if the 'Secure' property is set on browser session cookies.", + Flag: "secure-auth-cookie", + }, + SSHKeygenAlgorithm: codersdk.DeploymentConfigField[string]{ + Key: "ssh_keygen_algorithm", + Usage: "The algorithm to use for generating ssh keys. Accepted values are \"ed25519\", \"ecdsa\", or \"rsa4096\".", + Flag: "ssh-keygen-algorithm", + Value: "ed25519", + }, + AutoImportTemplates: codersdk.DeploymentConfigField[[]string]{ + Key: "auto_import_templates", + Usage: "Templates to auto-import. Available auto-importable templates are: kubernetes", + Flag: "auto-import-template", + Hidden: true, + }, + MetricsCacheRefreshInterval: codersdk.DeploymentConfigField[time.Duration]{ + Key: "metrics_cache_refresh_interval", + Usage: "How frequently metrics are refreshed", + Flag: "metrics-cache-refresh-interval", + Hidden: true, + Value: time.Hour, + }, + AgentStatRefreshInterval: codersdk.DeploymentConfigField[time.Duration]{ + Key: "agent_stat_refresh_interval", + Usage: "How frequently agent stats are recorded", + Flag: "agent-stats-refresh-interval", + Hidden: true, + Value: 10 * time.Minute, + }, + AuditLogging: codersdk.DeploymentConfigField[bool]{ + Key: "audit_logging", + Usage: "Specifies whether audit logging is enabled.", + Flag: "audit-logging", + Value: true, + Enterprise: true, + }, + BrowserOnly: codersdk.DeploymentConfigField[bool]{ + Key: "browser_only", + Usage: "Whether Coder only allows connections to workspaces via the browser.", + Flag: "browser-only", + Enterprise: true, + }, + SCIMAPIKey: codersdk.DeploymentConfigField[string]{ + Key: "scim_api_key", + Usage: "Enables SCIM and sets the authentication header for the built-in SCIM server. New users are automatically created with OIDC authentication.", + Flag: "scim-auth-header", + Enterprise: true, + }, + UserWorkspaceQuota: codersdk.DeploymentConfigField[int]{ + Key: "user_workspace_quota", + Usage: "Enables and sets a limit on how many workspaces each user can create.", + Flag: "user-workspace-quota", + Enterprise: true, + }, + } +} + +//nolint:revive +func Config(flagset *pflag.FlagSet, vip *viper.Viper) (codersdk.DeploymentConfig, error) { + dc := newConfig() + flg, err := flagset.GetString(config.FlagName) + if err != nil { + return dc, xerrors.Errorf("get global config from flag: %w", err) + } + vip.SetEnvPrefix("coder") + vip.AutomaticEnv() + + if flg != "" { + vip.SetConfigFile(flg + "/server.yaml") + err = vip.ReadInConfig() + if err != nil && !xerrors.Is(err, os.ErrNotExist) { + return dc, xerrors.Errorf("reading deployment config: %w", err) + } + } + + dcv := reflect.ValueOf(&dc).Elem() + t := dcv.Type() + for i := 0; i < t.NumField(); i++ { + fve := dcv.Field(i) + key := fve.FieldByName("Key").String() + value := fve.FieldByName("Value").Interface() + + switch value.(type) { + case string: + fve.FieldByName("Value").SetString(vip.GetString(key)) + case bool: + fve.FieldByName("Value").SetBool(vip.GetBool(key)) + case int: + fve.FieldByName("Value").SetInt(int64(vip.GetInt(key))) + case time.Duration: + fve.FieldByName("Value").SetInt(int64(vip.GetDuration(key))) + case []string: + fve.FieldByName("Value").Set(reflect.ValueOf(vip.GetStringSlice(key))) + default: + return dc, xerrors.Errorf("unsupported type %T", value) + } + } + + return dc, nil +} + +func NewViper() *viper.Viper { + dc := newConfig() + v := viper.New() + v.SetEnvPrefix("coder") + v.AutomaticEnv() + + dcv := reflect.ValueOf(dc) + t := dcv.Type() + for i := 0; i < t.NumField(); i++ { + fv := dcv.Field(i) + key := fv.FieldByName("Key").String() + value := fv.FieldByName("Value").Interface() + v.SetDefault(key, value) + } + + return v +} + +//nolint:revive +func AttachFlags(flagset *pflag.FlagSet, vip *viper.Viper, enterprise bool) { + dc := newConfig() + dcv := reflect.ValueOf(dc) + t := dcv.Type() + for i := 0; i < t.NumField(); i++ { + fv := dcv.Field(i) + isEnt := fv.FieldByName("Enterprise").Bool() + if enterprise != isEnt { + continue + } + key := fv.FieldByName("Key").String() + flg := fv.FieldByName("Flag").String() + if flg == "" { + continue + } + usage := fv.FieldByName("Usage").String() + usage = fmt.Sprintf("%s\n%s", usage, cliui.Styles.Placeholder.Render("Consumes $"+formatEnv(key))) + shorthand := fv.FieldByName("Shorthand").String() + hidden := fv.FieldByName("Hidden").Bool() + value := fv.FieldByName("Value").Interface() + + switch value.(type) { + case string: + _ = flagset.StringP(flg, shorthand, vip.GetString(key), usage) + case bool: + _ = flagset.BoolP(flg, shorthand, vip.GetBool(key), usage) + case int: + _ = flagset.IntP(flg, shorthand, vip.GetInt(key), usage) + case time.Duration: + _ = flagset.DurationP(flg, shorthand, vip.GetDuration(key), usage) + case []string: + _ = flagset.StringSliceP(flg, shorthand, vip.GetStringSlice(key), usage) + default: + continue + } + + _ = vip.BindPFlag(key, flagset.Lookup(flg)) + if hidden { + _ = flagset.MarkHidden(flg) + } + } +} + +func formatEnv(key string) string { + return "CODER_" + strings.ToUpper(strings.NewReplacer("-", "_", ".", "_").Replace(key)) +} + +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/deployment/flags.go b/cli/deployment/flags.go deleted file mode 100644 index 792051f805f70..0000000000000 --- a/cli/deployment/flags.go +++ /dev/null @@ -1,511 +0,0 @@ -package deployment - -import ( - "flag" - "fmt" - "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/cli/cliui" - "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" or "*-suffix.example.com". Ports or schemes should not be included. The scheme will be copied from the access URL.`, - }, - 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.", - Hidden: true, - 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"}, - }, - DerpServerRelayAddress: &codersdk.StringFlag{ - Name: "DERP Server Relay Address", - Flag: "derp-server-relay-address", - EnvVar: "CODER_DERP_SERVER_RELAY_URL", - Description: "An HTTP address that is accessible by other replicas to relay DERP traffic. Required for high availability.", - Enterprise: true, - }, - 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.", - Hidden: true, - }, - 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.", - Default: []string{}, - }, - 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: /.", - Default: []string{}, - }, - OAuth2GithubAllowSignups: &codersdk.BoolFlag{ - Name: "Oauth2 Github Allow Signups", - Flag: "oauth2-github-allow-signups", - EnvVar: "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS", - 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.", - Hidden: true, - 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_CLIENT_AUTH", - 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", - Hidden: true, - 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", - Hidden: true, - 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", - Hidden: true, - 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 vp, ok := fv.Interface().(*codersdk.StringFlag); ok { - if vp.Secret && vp.Value != "" { - // Make a copy and remove the value. - v := *vp - v.Value = secretValue - fv.Set(reflect.ValueOf(&v)) - } - } - } - - return df -} - -//nolint:revive -func AttachFlags(flagset *pflag.FlagSet, df *codersdk.DeploymentFlags, enterprise bool) { - v := reflect.ValueOf(df).Elem() - t := v.Type() - for i := 0; i < t.NumField(); i++ { - fv := v.Field(i) - fve := fv.Elem() - e := fve.FieldByName("Enterprise").Bool() - if e != enterprise { - continue - } - if e { - d := fve.FieldByName("Description").String() - d += cliui.Styles.Keyword.Render(" This is an Enterprise feature. Contact sales@coder.com for licensing") - fve.FieldByName("Description").SetString(d) - } - - switch v := fv.Interface().(type) { - case *codersdk.StringFlag: - StringFlag(flagset, v) - case *codersdk.StringArrayFlag: - StringArrayFlag(flagset, v) - case *codersdk.IntFlag: - IntFlag(flagset, v) - case *codersdk.BoolFlag: - BoolFlag(flagset, v) - case *codersdk.DurationFlag: - DurationFlag(flagset, v) - default: - panic(fmt.Sprintf("unknown flag type: %T", v)) - } - if fve.FieldByName("Hidden").Bool() { - _ = flagset.MarkHidden(fve.FieldByName("Flag").String()) - } - } -} - -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/deployment/flags_test.go b/cli/deployment/flags_test.go deleted file mode 100644 index 8b411068dba28..0000000000000 --- a/cli/deployment/flags_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package deployment_test - -import ( - "testing" - - "github.com/spf13/pflag" - "github.com/stretchr/testify/require" - - "github.com/coder/coder/cli/deployment" -) - -func TestFlags(t *testing.T) { - t.Parallel() - - df := deployment.Flags() - fs := pflag.NewFlagSet("test", pflag.ContinueOnError) - deployment.AttachFlags(fs, df, false) - - require.NotNil(t, fs.Lookup("access-url")) - require.False(t, fs.Lookup("access-url").Hidden) - require.True(t, fs.Lookup("telemetry-url").Hidden) - require.NotEmpty(t, fs.Lookup("telemetry-url").DefValue) - require.Nil(t, fs.Lookup("audit-logging")) - - df = deployment.Flags() - fs = pflag.NewFlagSet("test-enterprise", pflag.ContinueOnError) - deployment.AttachFlags(fs, df, true) - - require.Nil(t, fs.Lookup("access-url")) - require.NotNil(t, fs.Lookup("audit-logging")) - require.Contains(t, fs.Lookup("audit-logging").Usage, "This is an Enterprise feature") -} diff --git a/cli/root.go b/cli/root.go index ea803cdfa5d81..c40bd927e2b90 100644 --- a/cli/root.go +++ b/cli/root.go @@ -43,7 +43,6 @@ const ( varToken = "token" varAgentToken = "agent-token" varAgentURL = "agent-url" - varGlobalConfig = "global-config" varHeader = "header" varNoOpen = "no-open" varNoVersionCheck = "no-version-warning" @@ -101,7 +100,7 @@ func Core() []*cobra.Command { } func AGPL() []*cobra.Command { - all := append(Core(), Server(deployment.Flags(), func(_ context.Context, o *coderd.Options) (*coderd.API, io.Closer, error) { + all := append(Core(), Server(deployment.NewViper(), func(_ context.Context, o *coderd.Options) (*coderd.API, io.Closer, error) { api := coderd.New(o) return api, api, nil })) @@ -184,7 +183,7 @@ func Root(subcommands []*cobra.Command) *cobra.Command { _ = cmd.PersistentFlags().MarkHidden(varAgentToken) cliflag.String(cmd.PersistentFlags(), varAgentURL, "", "CODER_AGENT_URL", "", "URL for an agent to access your deployment.") _ = cmd.PersistentFlags().MarkHidden(varAgentURL) - cliflag.String(cmd.PersistentFlags(), varGlobalConfig, "", "CODER_CONFIG_DIR", configdir.LocalConfig("coderv2"), "Path to the global `coder` config directory.") + cliflag.String(cmd.PersistentFlags(), config.FlagName, "", "CODER_CONFIG_DIR", configdir.LocalConfig("coderv2"), "Path to the global `coder` config directory.") cliflag.StringArray(cmd.PersistentFlags(), varHeader, "", "CODER_HEADER", []string{}, "HTTP headers added to all requests. Provide as \"Key=Value\"") cmd.PersistentFlags().Bool(varForceTty, false, "Force the `coder` command to run as if connected to a TTY.") _ = cmd.PersistentFlags().MarkHidden(varForceTty) @@ -362,7 +361,7 @@ func namedWorkspace(cmd *cobra.Command, client *codersdk.Client, identifier stri // createConfig consumes the global configuration flag to produce a config root. func createConfig(cmd *cobra.Command) config.Root { - globalRoot, err := cmd.Flags().GetString(varGlobalConfig) + globalRoot, err := cmd.Flags().GetString(config.FlagName) if err != nil { panic(err) } diff --git a/cli/server.go b/cli/server.go index 99f23e9278315..8f3ce62921c80 100644 --- a/cli/server.go +++ b/cli/server.go @@ -32,6 +32,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/spf13/afero" "github.com/spf13/cobra" + "github.com/spf13/viper" "go.opentelemetry.io/otel/trace" "golang.org/x/mod/semver" "golang.org/x/oauth2" @@ -70,14 +71,18 @@ import ( ) // nolint:gocyclo -func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *coderd.Options) (*coderd.API, io.Closer, error)) *cobra.Command { +func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*coderd.API, io.Closer, error)) *cobra.Command { root := &cobra.Command{ Use: "server", Short: "Start a Coder server", RunE: func(cmd *cobra.Command, args []string) error { + cfg, err := deployment.Config(cmd.Flags(), vip) + if err != nil { + return xerrors.Errorf("getting deployment config: %w", err) + } printLogo(cmd) logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr())) - if dflags.Verbose.Value { + if ok, _ := cmd.Flags().GetBool(varVerbose); ok { logger = logger.Leveled(slog.LevelDebug) } @@ -106,22 +111,21 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code var ( tracerProvider trace.TracerProvider - err error sqlDriver = "postgres" ) // Coder tracing should be disabled if telemetry is disabled unless // --telemetry-trace was explicitly provided. - shouldCoderTrace := dflags.TelemetryEnable.Value && !isTest() + shouldCoderTrace := cfg.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 = dflags.TelemetryTraceEnable.Value + shouldCoderTrace = cfg.TelemetryTrace.Value } - if dflags.TraceEnable.Value || shouldCoderTrace { + if cfg.TraceEnable.Value || shouldCoderTrace { sdkTracerProvider, closeTracing, err := tracing.TracerProvider(ctx, "coderd", tracing.TracerOpts{ - Default: dflags.TraceEnable.Value, + Default: cfg.TraceEnable.Value, Coder: shouldCoderTrace, }) if err != nil { @@ -146,10 +150,10 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code config := createConfig(cmd) builtinPostgres := false // Only use built-in if PostgreSQL URL isn't specified! - if !dflags.InMemoryDatabase.Value && dflags.PostgresURL.Value == "" { + if !cfg.InMemoryDatabase.Value && cfg.PostgresURL.Value == "" { var closeFunc func() error cmd.Printf("Using built-in PostgreSQL (%s)\n", config.PostgresPath()) - dflags.PostgresURL.Value, closeFunc, err = startBuiltinPostgres(ctx, config, logger) + cfg.PostgresURL.Value, closeFunc, err = startBuiltinPostgres(ctx, config, logger) if err != nil { return err } @@ -162,20 +166,20 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code }() } - listener, err := net.Listen("tcp", dflags.Address.Value) + listener, err := net.Listen("tcp", cfg.Address.Value) if err != nil { - return xerrors.Errorf("listen %q: %w", dflags.Address.Value, err) + return xerrors.Errorf("listen %q: %w", cfg.Address.Value, err) } defer listener.Close() var tlsConfig *tls.Config - if dflags.TLSEnable.Value { + if cfg.TLSEnable.Value { tlsConfig, err = configureTLS( - dflags.TLSMinVersion.Value, - dflags.TLSClientAuth.Value, - dflags.TLSCertFiles.Value, - dflags.TLSKeyFiles.Value, - dflags.TLSClientCAFile.Value, + cfg.TLSMinVersion.Value, + cfg.TLSClientAuth.Value, + cfg.TLSCertFiles.Value, + cfg.TLSKeyFiles.Value, + cfg.TLSClientCAFile.Value, ) if err != nil { return xerrors.Errorf("configure tls: %w", err) @@ -197,7 +201,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code Scheme: "http", Host: tcpAddr.String(), } - if dflags.TLSEnable.Value { + if cfg.TLSEnable.Value { localURL.Scheme = "https" } @@ -210,26 +214,26 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code // If the access URL is empty, we attempt to run a reverse-proxy // tunnel to make the initial setup really simple. - if dflags.AccessURL.Value == "" { + if cfg.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) } - dflags.AccessURL.Value = tunnel.URL + cfg.AccessURL.Value = tunnel.URL - if dflags.WildcardAccessURL.Value == "" { + if cfg.WildcardAccessURL.Value == "" { u, err := parseURL(ctx, tunnel.URL) if err != nil { return xerrors.Errorf("parse tunnel url: %w", err) } // Suffixed wildcard access URL. - dflags.WildcardAccessURL.Value = fmt.Sprintf("*--%s", u.Hostname()) + cfg.WildcardAccessURL.Value = fmt.Sprintf("*--%s", u.Hostname()) } } - accessURLParsed, err := parseURL(ctx, dflags.AccessURL.Value) + accessURLParsed, err := parseURL(ctx, cfg.AccessURL.Value) if err != nil { return xerrors.Errorf("parse URL: %w", err) } @@ -264,17 +268,17 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code return err } - sshKeygenAlgorithm, err := gitsshkey.ParseAlgorithm(dflags.SSHKeygenAlgorithm.Value) + sshKeygenAlgorithm, err := gitsshkey.ParseAlgorithm(cfg.SSHKeygenAlgorithm.Value) if err != nil { - return xerrors.Errorf("parse ssh keygen algorithm %s: %w", dflags.SSHKeygenAlgorithm.Value, err) + return xerrors.Errorf("parse ssh keygen algorithm %s: %w", cfg.SSHKeygenAlgorithm.Value, err) } // Validate provided auto-import templates. var ( - validatedAutoImportTemplates = make([]coderd.AutoImportTemplate, len(dflags.AutoImportTemplates.Value)) - seenValidatedAutoImportTemplates = make(map[coderd.AutoImportTemplate]struct{}, len(dflags.AutoImportTemplates.Value)) + validatedAutoImportTemplates = make([]coderd.AutoImportTemplate, len(cfg.AutoImportTemplates.Value)) + seenValidatedAutoImportTemplates = make(map[coderd.AutoImportTemplate]struct{}, len(cfg.AutoImportTemplates.Value)) ) - for i, autoImportTemplate := range dflags.AutoImportTemplates.Value { + for i, autoImportTemplate := range cfg.AutoImportTemplates.Value { var v coderd.AutoImportTemplate switch autoImportTemplate { case "kubernetes": @@ -292,27 +296,27 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code defaultRegion := &tailcfg.DERPRegion{ EmbeddedRelay: true, - RegionID: dflags.DerpServerRegionID.Value, - RegionCode: dflags.DerpServerRegionCode.Value, - RegionName: dflags.DerpServerRegionName.Value, + RegionID: cfg.DERPServerRegionID.Value, + RegionCode: cfg.DERPServerRegionCode.Value, + RegionName: cfg.DERPServerRegionName.Value, Nodes: []*tailcfg.DERPNode{{ - Name: fmt.Sprintf("%db", dflags.DerpServerRegionID.Value), - RegionID: dflags.DerpServerRegionID.Value, + Name: fmt.Sprintf("%db", cfg.DERPServerRegionID.Value), + RegionID: cfg.DERPServerRegionID.Value, HostName: accessURLParsed.Hostname(), DERPPort: accessURLPort, STUNPort: -1, ForceHTTP: accessURLParsed.Scheme == "http", }}, } - if !dflags.DerpServerEnable.Value { + if !cfg.DERPServerEnable.Value { defaultRegion = nil } - derpMap, err := tailnet.NewDERPMap(ctx, defaultRegion, dflags.DerpServerSTUNAddresses.Value, dflags.DerpConfigURL.Value, dflags.DerpConfigPath.Value) + derpMap, err := tailnet.NewDERPMap(ctx, defaultRegion, cfg.DERPServerSTUNAddresses.Value, cfg.DERPConfigURL.Value, cfg.DERPConfigPath.Value) if err != nil { return xerrors.Errorf("create derp map: %w", err) } - appHostname := strings.TrimSpace(dflags.WildcardAccessURL.Value) + appHostname := strings.TrimSpace(cfg.WildcardAccessURL.Value) var appHostnameRegex *regexp.Regexp if appHostname != "" { appHostnameRegex, err = httpapi.CompileHostnamePattern(appHostname) @@ -329,45 +333,45 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code Database: databasefake.New(), DERPMap: derpMap, Pubsub: database.NewPubsubInMemory(), - CacheDir: dflags.CacheDir.Value, + CacheDir: cfg.CacheDirectory.Value, GoogleTokenValidator: googleTokenValidator, - SecureAuthCookie: dflags.SecureAuthCookie.Value, + SecureAuthCookie: cfg.SecureAuthCookie.Value, SSHKeygenAlgorithm: sshKeygenAlgorithm, TracerProvider: tracerProvider, Telemetry: telemetry.NewNoop(), AutoImportTemplates: validatedAutoImportTemplates, - MetricsCacheRefreshInterval: dflags.MetricsCacheRefreshInterval.Value, - AgentStatsRefreshInterval: dflags.AgentStatRefreshInterval.Value, + MetricsCacheRefreshInterval: cfg.MetricsCacheRefreshInterval.Value, + AgentStatsRefreshInterval: cfg.AgentStatRefreshInterval.Value, Experimental: ExperimentalEnabled(cmd), - DeploymentFlags: dflags, + DeploymentConfig: &cfg, } if tlsConfig != nil { options.TLSCertificates = tlsConfig.Certificates } - if dflags.OAuth2GithubClientSecret.Value != "" { + if cfg.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, + cfg.OAuth2GithubClientID.Value, + cfg.OAuth2GithubClientSecret.Value, + cfg.OAuth2GithubAllowSignups.Value, + cfg.OAuth2GithubAllowedOrganizations.Value, + cfg.OAuth2GithubAllowedTeams.Value, + cfg.OAuth2GithubEnterpriseBaseURL.Value, ) if err != nil { return xerrors.Errorf("configure github oauth2: %w", err) } } - if dflags.OIDCClientSecret.Value != "" { - if dflags.OIDCClientID.Value == "" { + if cfg.OIDCClientSecret.Value != "" { + if cfg.OIDCClientID.Value == "" { return xerrors.Errorf("OIDC client ID be set!") } - if dflags.OIDCIssuerURL.Value == "" { + if cfg.OIDCIssuerURL.Value == "" { return xerrors.Errorf("OIDC issuer URL must be set!") } - oidcProvider, err := oidc.NewProvider(ctx, dflags.OIDCIssuerURL.Value) + oidcProvider, err := oidc.NewProvider(ctx, cfg.OIDCIssuerURL.Value) if err != nil { return xerrors.Errorf("configure oidc provider: %w", err) } @@ -377,25 +381,25 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code } options.OIDCConfig = &coderd.OIDCConfig{ OAuth2Config: &oauth2.Config{ - ClientID: dflags.OIDCClientID.Value, - ClientSecret: dflags.OIDCClientSecret.Value, + ClientID: cfg.OIDCClientID.Value, + ClientSecret: cfg.OIDCClientSecret.Value, RedirectURL: redirectURL.String(), Endpoint: oidcProvider.Endpoint(), - Scopes: dflags.OIDCScopes.Value, + Scopes: cfg.OIDCScopes.Value, }, Verifier: oidcProvider.Verifier(&oidc.Config{ - ClientID: dflags.OIDCClientID.Value, + ClientID: cfg.OIDCClientID.Value, }), - EmailDomain: dflags.OIDCEmailDomain.Value, - AllowSignups: dflags.OIDCAllowSignups.Value, + EmailDomain: cfg.OIDCEmailDomain.Value, + AllowSignups: cfg.OIDCAllowSignups.Value, } } - if dflags.InMemoryDatabase.Value { + if cfg.InMemoryDatabase.Value { options.Database = databasefake.New() options.Pubsub = database.NewPubsubInMemory() } else { - sqlDB, err := sql.Open(sqlDriver, dflags.PostgresURL.Value) + sqlDB, err := sql.Open(sqlDriver, cfg.PostgresURL.Value) if err != nil { return xerrors.Errorf("dial postgres: %w", err) } @@ -427,7 +431,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code return xerrors.Errorf("migrate up: %w", err) } options.Database = database.New(sqlDB) - options.Pubsub, err = database.NewPubsub(ctx, sqlDB, dflags.PostgresURL.Value) + options.Pubsub, err = database.NewPubsub(ctx, sqlDB, cfg.PostgresURL.Value) if err != nil { return xerrors.Errorf("create pubsub: %w", err) } @@ -450,26 +454,26 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code } // Parse the raw telemetry URL! - telemetryURL, err := parseURL(ctx, dflags.TelemetryURL.Value) + telemetryURL, err := parseURL(ctx, cfg.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 dflags.InMemoryDatabase.Value && !cmd.Flags().Changed(dflags.TelemetryEnable.Flag) { - dflags.TelemetryEnable.Value = false + if cfg.InMemoryDatabase.Value && !cmd.Flags().Changed(cfg.TelemetryEnable.Flag) { + cfg.TelemetryEnable.Value = false } - if dflags.TelemetryEnable.Value { + if cfg.TelemetryEnable.Value { options.Telemetry, err = telemetry.New(telemetry.Options{ BuiltinPostgres: builtinPostgres, DeploymentID: deploymentID, Database: options.Database, Logger: logger.Named("telemetry"), URL: telemetryURL, - GitHubOAuth: dflags.OAuth2GithubClientID.Value != "", - OIDCAuth: dflags.OIDCClientID.Value != "", - OIDCIssuerURL: dflags.OIDCIssuerURL.Value, - Prometheus: dflags.PromEnabled.Value, - STUN: len(dflags.DerpServerSTUNAddresses.Value) != 0, + GitHubOAuth: cfg.OAuth2GithubClientID.Value != "", + OIDCAuth: cfg.OIDCClientID.Value != "", + OIDCIssuerURL: cfg.OIDCIssuerURL.Value, + Prometheus: cfg.PrometheusEnable.Value, + STUN: len(cfg.DERPServerSTUNAddresses.Value) != 0, Tunnel: tunnel != nil, }) if err != nil { @@ -480,11 +484,11 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code // This prevents the pprof import from being accidentally deleted. _ = pprof.Handler - if dflags.PprofEnabled.Value { + if cfg.PprofEnable.Value { //nolint:revive - defer serveHandler(ctx, logger, nil, dflags.PprofAddress.Value, "pprof")() + defer serveHandler(ctx, logger, nil, cfg.PprofAddress.Value, "pprof")() } - if dflags.PromEnabled.Value { + if cfg.PrometheusEnable.Value { options.PrometheusRegistry = prometheus.NewRegistry() closeUsersFunc, err := prometheusmetrics.ActiveUsers(ctx, options.PrometheusRegistry, options.Database, 0) if err != nil { @@ -501,7 +505,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code //nolint:revive defer serveHandler(ctx, logger, promhttp.InstrumentMetricHandler( options.PrometheusRegistry, promhttp.HandlerFor(options.PrometheusRegistry, promhttp.HandlerOpts{}), - ), dflags.PromAddress.Value, "prometheus")() + ), cfg.PrometheusAddress.Value, "prometheus")() } // We use a separate coderAPICloser so the Enterprise API @@ -513,7 +517,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code } client := codersdk.New(localURL) - if dflags.TLSEnable.Value { + if cfg.TLSEnable.Value { // Secure transport isn't needed for locally communicating! client.HTTPClient.Transport = &http.Transport{ TLSClientConfig: &tls.Config{ @@ -537,8 +541,8 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code _ = daemon.Close() } }() - for i := 0; i < dflags.ProvisionerDaemonCount.Value; i++ { - daemon, err := newProvisionerDaemon(ctx, coderAPI, logger, dflags.CacheDir.Value, errCh, false) + for i := 0; i < cfg.ProvisionerDaemons.Value; i++ { + daemon, err := newProvisionerDaemon(ctx, coderAPI, logger, cfg.CacheDirectory.Value, errCh, false) if err != nil { return xerrors.Errorf("create provisioner daemon: %w", err) } @@ -604,7 +608,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code return xerrors.Errorf("notify systemd: %w", err) } - autobuildPoller := time.NewTicker(dflags.AutobuildPollInterval.Value) + autobuildPoller := time.NewTicker(cfg.AutobuildPollInterval.Value) defer autobuildPoller.Stop() autobuildExecutor := executor.New(ctx, options.Database, logger, autobuildPoller.C) autobuildExecutor.Run() @@ -669,7 +673,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code go func() { defer wg.Done() - if dflags.Verbose.Value { + if ok, _ := cmd.Flags().GetBool(varVerbose); ok { cmd.Printf("Shutting down provisioner daemon %d...\n", id) } err := shutdownWithTimeout(provisionerDaemon.Shutdown, 5*time.Second) @@ -682,7 +686,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code cmd.PrintErrf("Close provisioner daemon %d: %s\n", id, err) return } - if dflags.Verbose.Value { + if ok, _ := cmd.Flags().GetBool(varVerbose); ok { cmd.Printf("Gracefully shut down provisioner daemon %d\n", id) } }() @@ -734,7 +738,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code RunE: func(cmd *cobra.Command, args []string) error { cfg := createConfig(cmd) logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr())) - if dflags.Verbose.Value { + if ok, _ := cmd.Flags().GetBool(varVerbose); ok { logger = logger.Leveled(slog.LevelDebug) } @@ -755,7 +759,7 @@ func Server(dflags *codersdk.DeploymentFlags, newAPI func(context.Context, *code }, }) - deployment.AttachFlags(root.Flags(), dflags, false) + deployment.AttachFlags(root.Flags(), vip, false) return root } diff --git a/coderd/coderd.go b/coderd/coderd.go index 75263926d329c..1aed8417b0ba8 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -92,7 +92,7 @@ type Options struct { MetricsCacheRefreshInterval time.Duration AgentStatsRefreshInterval time.Duration Experimental bool - DeploymentFlags *codersdk.DeploymentFlags + DeploymentConfig *codersdk.DeploymentConfig } // New constructs a Coder API handler. @@ -286,9 +286,9 @@ func New(options *Options) *API { }) }) }) - r.Route("/flags", func(r chi.Router) { + r.Route("/config", func(r chi.Router) { r.Use(apiKeyMiddleware) - r.Get("/deployment", api.deploymentFlags) + r.Get("/deployment", api.deploymentConfig) }) r.Route("/audit", func(r chi.Router) { r.Use( diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 05e3d6a27d2d4..4a4198e43c45b 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -91,7 +91,7 @@ type Options struct { IncludeProvisionerDaemon bool MetricsCacheRefreshInterval time.Duration AgentStatsRefreshInterval time.Duration - DeploymentFlags *codersdk.DeploymentFlags + DeploymentConfig *codersdk.DeploymentConfig // Overriding the database is heavily discouraged. // It should only be used in cases where multiple Coder @@ -268,7 +268,7 @@ func NewOptions(t *testing.T, options *Options) (func(http.Handler), context.Can AutoImportTemplates: options.AutoImportTemplates, MetricsCacheRefreshInterval: options.MetricsCacheRefreshInterval, AgentStatsRefreshInterval: options.AgentStatsRefreshInterval, - DeploymentFlags: options.DeploymentFlags, + DeploymentConfig: options.DeploymentConfig, } } diff --git a/coderd/deploymentconfig.go b/coderd/deploymentconfig.go new file mode 100644 index 0000000000000..d68332c9089f7 --- /dev/null +++ b/coderd/deploymentconfig.go @@ -0,0 +1,17 @@ +package coderd + +import ( + "net/http" + + "github.com/coder/coder/coderd/httpapi" + "github.com/coder/coder/coderd/rbac" +) + +func (api *API) deploymentConfig(rw http.ResponseWriter, r *http.Request) { + if !api.Authorize(r, rbac.ActionRead, rbac.ResourceDeploymentConfig) { + httpapi.Forbidden(rw) + return + } + + httpapi.Write(r.Context(), rw, http.StatusOK, api.DeploymentConfig) +} diff --git a/coderd/deploymentconfig_test.go b/coderd/deploymentconfig_test.go new file mode 100644 index 0000000000000..2f03e655c671d --- /dev/null +++ b/coderd/deploymentconfig_test.go @@ -0,0 +1,47 @@ +package coderd_test + +import ( + "context" + "testing" + + "github.com/spf13/pflag" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/cli/config" + "github.com/coder/coder/cli/deployment" + "github.com/coder/coder/coderd/coderdtest" + "github.com/coder/coder/testutil" +) + +func TestDeploymentConfig(t *testing.T) { + t.Parallel() + hi := "hi" + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + vip := deployment.NewViper() + fs := pflag.NewFlagSet("test", pflag.ContinueOnError) + fs.String(config.FlagName, hi, "usage") + cfg, err := deployment.Config(fs, vip) + require.NoError(t, err) + // values should be returned + cfg.AccessURL.Value = hi + // values should not be returned + cfg.OAuth2GithubClientSecret.Value = hi + cfg.OIDCClientSecret.Value = hi + cfg.PostgresURL.Value = hi + cfg.SCIMAPIKey.Value = hi + + client := coderdtest.New(t, &coderdtest.Options{ + DeploymentConfig: &cfg, + }) + _ = coderdtest.CreateFirstUser(t, client) + scrubbed, err := client.DeploymentConfig(ctx) + require.NoError(t, err) + // ensure normal values pass through + require.EqualValues(t, hi, scrubbed.AccessURL.Value) + // ensure secrets are removed + require.Empty(t, scrubbed.OAuth2GithubClientSecret.Value) + require.Empty(t, scrubbed.OIDCClientSecret.Value) + require.Empty(t, scrubbed.PostgresURL.Value) + require.Empty(t, scrubbed.SCIMAPIKey.Value) +} diff --git a/coderd/flags.go b/coderd/flags.go deleted file mode 100644 index 7e2f1376df116..0000000000000 --- a/coderd/flags.go +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index dbff5992385d4..0000000000000 --- a/coderd/flags_test.go +++ /dev/null @@ -1,47 +0,0 @@ -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 1a8861c984ce9..4852a4c269638 100644 --- a/coderd/rbac/object.go +++ b/coderd/rbac/object.go @@ -142,9 +142,9 @@ var ( Type: "license", } - // ResourceDeploymentFlags - ResourceDeploymentFlags = Object{ - Type: "deployment_flags", + // ResourceDeploymentConfig + ResourceDeploymentConfig = Object{ + Type: "deployment_config", } ResourceReplicas = Object{ diff --git a/codersdk/deploymentconfig.go b/codersdk/deploymentconfig.go new file mode 100644 index 0000000000000..174152e4a29f2 --- /dev/null +++ b/codersdk/deploymentconfig.go @@ -0,0 +1,97 @@ +package codersdk + +import ( + "context" + "encoding/json" + "net/http" + "time" + + "golang.org/x/xerrors" +) + +// DeploymentConfig is the central configuration for the coder server. +// Secret values should specify `json:"-"` to prevent them from being returned by the API. +type DeploymentConfig struct { + AccessURL DeploymentConfigField[string] `json:"access_url"` + WildcardAccessURL DeploymentConfigField[string] `json:"wildcard_access_url"` + Address DeploymentConfigField[string] `json:"address"` + AutobuildPollInterval DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval"` + DERPServerEnable DeploymentConfigField[bool] `json:"derp_server_enabled"` + DERPServerRegionID DeploymentConfigField[int] `json:"derp_server_region_id"` + DERPServerRegionCode DeploymentConfigField[string] `json:"derp_server_region_code"` + DERPServerRegionName DeploymentConfigField[string] `json:"derp_server_region_name"` + DERPServerSTUNAddresses DeploymentConfigField[[]string] `json:"derp_server_stun_address"` + DERPServerRelayAddress DeploymentConfigField[string] `json:"derp_server_relay_address"` + DERPConfigURL DeploymentConfigField[string] `json:"derp_config_url"` + DERPConfigPath DeploymentConfigField[string] `json:"derp_config_path"` + PrometheusEnable DeploymentConfigField[bool] `json:"prometheus_enabled"` + PrometheusAddress DeploymentConfigField[string] `json:"prometheus_address"` + PprofEnable DeploymentConfigField[bool] `json:"pprof_enabled"` + PprofAddress DeploymentConfigField[string] `json:"pprof_address"` + CacheDirectory DeploymentConfigField[string] `json:"cache_directory"` + InMemoryDatabase DeploymentConfigField[bool] `json:"in_memory_database"` + ProvisionerDaemons DeploymentConfigField[int] `json:"provisioner_daemon_count"` + PostgresURL DeploymentConfigField[string] `json:"-"` + OAuth2GithubClientID DeploymentConfigField[string] `json:"oauth2_github_client_id"` + OAuth2GithubClientSecret DeploymentConfigField[string] `json:"-"` + OAuth2GithubAllowedOrganizations DeploymentConfigField[[]string] `json:"oauth2_github_allowed_organizations"` + OAuth2GithubAllowedTeams DeploymentConfigField[[]string] `json:"oauth2_github_allowed_teams"` + OAuth2GithubAllowSignups DeploymentConfigField[bool] `json:"oauth2_github_allow_signups"` + OAuth2GithubEnterpriseBaseURL DeploymentConfigField[string] `json:"oauth2_github_enterprise_base_url"` + OIDCAllowSignups DeploymentConfigField[bool] `json:"oidc_allow_signups"` + OIDCClientID DeploymentConfigField[string] `json:"oidc_client_id"` + OIDCClientSecret DeploymentConfigField[string] `json:"-"` + OIDCEmailDomain DeploymentConfigField[string] `json:"oidc_email_domain"` + OIDCIssuerURL DeploymentConfigField[string] `json:"oidc_issuer_url"` + OIDCScopes DeploymentConfigField[[]string] `json:"oidc_scopes"` + TelemetryEnable DeploymentConfigField[bool] `json:"telemetry_enable"` + TelemetryTrace DeploymentConfigField[bool] `json:"telemetry_trace_enable"` + TelemetryURL DeploymentConfigField[string] `json:"telemetry_url"` + TLSEnable DeploymentConfigField[bool] `json:"tls_enable"` + TLSCertFiles DeploymentConfigField[[]string] `json:"tls_cert_files"` + TLSClientCAFile DeploymentConfigField[string] `json:"tls_client_ca_file"` + TLSClientAuth DeploymentConfigField[string] `json:"tls_client_auth"` + TLSKeyFiles DeploymentConfigField[[]string] `json:"tls_key_files"` + TLSMinVersion DeploymentConfigField[string] `json:"tls_min_version"` + TraceEnable DeploymentConfigField[bool] `json:"trace_enable"` + SecureAuthCookie DeploymentConfigField[bool] `json:"secure_auth_cookie"` + SSHKeygenAlgorithm DeploymentConfigField[string] `json:"ssh_keygen_algorithm"` + AutoImportTemplates DeploymentConfigField[[]string] `json:"auto_import_templates"` + MetricsCacheRefreshInterval DeploymentConfigField[time.Duration] `json:"metrics_cache_refresh_interval"` + AgentStatRefreshInterval DeploymentConfigField[time.Duration] `json:"agent_stat_refresh_interval"` + AuditLogging DeploymentConfigField[bool] `json:"audit_logging"` + BrowserOnly DeploymentConfigField[bool] `json:"browser_only"` + SCIMAPIKey DeploymentConfigField[string] `json:"-"` + UserWorkspaceQuota DeploymentConfigField[int] `json:"user_workspace_quota"` +} + +type Flaggable interface { + string | bool | int | time.Duration | []string +} + +type DeploymentConfigField[T Flaggable] struct { + Key string `json:"key"` + Name string `json:"name"` + Usage string `json:"usage"` + Flag string `json:"flag"` + Shorthand string `json:"shorthand"` + Enterprise bool `json:"enterprise"` + Hidden bool `json:"hidden"` + Value T `json:"value"` +} + +// DeploymentConfig returns the deployment config for the coder server. +func (c *Client) DeploymentConfig(ctx context.Context) (DeploymentConfig, error) { + res, err := c.Request(ctx, http.MethodGet, "/api/v2/config/deployment", nil) + if err != nil { + return DeploymentConfig{}, xerrors.Errorf("execute request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return DeploymentConfig{}, readBodyAsError(res) + } + + var df DeploymentConfig + return df, json.NewDecoder(res.Body).Decode(&df) +} diff --git a/codersdk/flags.go b/codersdk/flags.go deleted file mode 100644 index bf407760bbfb8..0000000000000 --- a/codersdk/flags.go +++ /dev/null @@ -1,142 +0,0 @@ -package codersdk - -import ( - "context" - "encoding/json" - "net/http" - "time" - - "golang.org/x/xerrors" -) - -type DeploymentFlags struct { - AccessURL *StringFlag `json:"access_url" typescript:",notnull"` - WildcardAccessURL *StringFlag `json:"wildcard_access_url" typescript:",notnull"` - Address *StringFlag `json:"address" typescript:",notnull"` - AutobuildPollInterval *DurationFlag `json:"autobuild_poll_interval" typescript:",notnull"` - DerpServerEnable *BoolFlag `json:"derp_server_enabled" typescript:",notnull"` - DerpServerRegionID *IntFlag `json:"derp_server_region_id" typescript:",notnull"` - DerpServerRegionCode *StringFlag `json:"derp_server_region_code" typescript:",notnull"` - DerpServerRegionName *StringFlag `json:"derp_server_region_name" typescript:",notnull"` - DerpServerSTUNAddresses *StringArrayFlag `json:"derp_server_stun_address" typescript:",notnull"` - DerpServerRelayAddress *StringFlag `json:"derp_server_relay_address" typescript:",notnull"` - DerpConfigURL *StringFlag `json:"derp_config_url" typescript:",notnull"` - DerpConfigPath *StringFlag `json:"derp_config_path" typescript:",notnull"` - PromEnabled *BoolFlag `json:"prom_enabled" typescript:",notnull"` - PromAddress *StringFlag `json:"prom_address" typescript:",notnull"` - PprofEnabled *BoolFlag `json:"pprof_enabled" typescript:",notnull"` - PprofAddress *StringFlag `json:"pprof_address" typescript:",notnull"` - CacheDir *StringFlag `json:"cache_dir" typescript:",notnull"` - InMemoryDatabase *BoolFlag `json:"in_memory_database" typescript:",notnull"` - ProvisionerDaemonCount *IntFlag `json:"provisioner_daemon_count" typescript:",notnull"` - PostgresURL *StringFlag `json:"postgres_url" typescript:",notnull"` - OAuth2GithubClientID *StringFlag `json:"oauth2_github_client_id" typescript:",notnull"` - OAuth2GithubClientSecret *StringFlag `json:"oauth2_github_client_secret" typescript:",notnull"` - OAuth2GithubAllowedOrganizations *StringArrayFlag `json:"oauth2_github_allowed_organizations" typescript:",notnull"` - OAuth2GithubAllowedTeams *StringArrayFlag `json:"oauth2_github_allowed_teams" typescript:",notnull"` - OAuth2GithubAllowSignups *BoolFlag `json:"oauth2_github_allow_signups" typescript:",notnull"` - OAuth2GithubEnterpriseBaseURL *StringFlag `json:"oauth2_github_enterprise_base_url" typescript:",notnull"` - OIDCAllowSignups *BoolFlag `json:"oidc_allow_signups" typescript:",notnull"` - OIDCClientID *StringFlag `json:"oidc_client_id" typescript:",notnull"` - OIDCClientSecret *StringFlag `json:"oidc_client_secret" typescript:",notnull"` - OIDCEmailDomain *StringFlag `json:"oidc_email_domain" typescript:",notnull"` - OIDCIssuerURL *StringFlag `json:"oidc_issuer_url" typescript:",notnull"` - OIDCScopes *StringArrayFlag `json:"oidc_scopes" typescript:",notnull"` - TelemetryEnable *BoolFlag `json:"telemetry_enable" typescript:",notnull"` - TelemetryTraceEnable *BoolFlag `json:"telemetry_trace_enable" typescript:",notnull"` - TelemetryURL *StringFlag `json:"telemetry_url" typescript:",notnull"` - TLSEnable *BoolFlag `json:"tls_enable" typescript:",notnull"` - TLSCertFiles *StringArrayFlag `json:"tls_cert_files" typescript:",notnull"` - TLSClientCAFile *StringFlag `json:"tls_client_ca_file" typescript:",notnull"` - TLSClientAuth *StringFlag `json:"tls_client_auth" typescript:",notnull"` - TLSKeyFiles *StringArrayFlag `json:"tls_key_files" typescript:",notnull"` - TLSMinVersion *StringFlag `json:"tls_min_version" typescript:",notnull"` - TraceEnable *BoolFlag `json:"trace_enable" typescript:",notnull"` - SecureAuthCookie *BoolFlag `json:"secure_auth_cookie" typescript:",notnull"` - SSHKeygenAlgorithm *StringFlag `json:"ssh_keygen_algorithm" typescript:",notnull"` - AutoImportTemplates *StringArrayFlag `json:"auto_import_templates" typescript:",notnull"` - MetricsCacheRefreshInterval *DurationFlag `json:"metrics_cache_refresh_interval" typescript:",notnull"` - AgentStatRefreshInterval *DurationFlag `json:"agent_stat_refresh_interval" typescript:",notnull"` - Verbose *BoolFlag `json:"verbose" typescript:",notnull"` - AuditLogging *BoolFlag `json:"audit_logging" typescript:",notnull"` - BrowserOnly *BoolFlag `json:"browser_only" typescript:",notnull"` - SCIMAuthHeader *StringFlag `json:"scim_auth_header" typescript:",notnull"` - UserWorkspaceQuota *IntFlag `json:"user_workspace_quota" typescript:",notnull"` -} - -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"` - Hidden bool `json:"hidden"` - 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"` - Hidden bool `json:"hidden"` - 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"` - Hidden bool `json:"hidden"` - 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"` - Hidden bool `json:"hidden"` - 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"` - Hidden bool `json:"hidden"` - 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/docs/install/kubernetes.md b/docs/install/kubernetes.md index ef5d5fb9e90d2..81958e964878c 100644 --- a/docs/install/kubernetes.md +++ b/docs/install/kubernetes.md @@ -102,7 +102,7 @@ to log in and manage templates. # This env variable controls whether or not to auto-import the # "kubernetes" template on first startup. This will not work unless # coder.serviceAccount.workspacePerms is true. - - name: CODER_TEMPLATE_AUTOIMPORT + - name: CODER_AUTO_IMPORT_TEMPLATES value: "kubernetes" #tls: diff --git a/enterprise/cli/server.go b/enterprise/cli/server.go index 9459cc6906a84..cd04a3be93972 100644 --- a/enterprise/cli/server.go +++ b/enterprise/cli/server.go @@ -24,10 +24,10 @@ import ( ) func server() *cobra.Command { - dflags := deployment.Flags() - cmd := agpl.Server(dflags, func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, io.Closer, error) { - if dflags.DerpServerRelayAddress.Value != "" { - _, err := url.Parse(dflags.DerpServerRelayAddress.Value) + vip := deployment.NewViper() + cmd := agpl.Server(vip, func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, io.Closer, error) { + if options.DeploymentConfig.DERPServerRelayAddress.Value != "" { + _, err := url.Parse(options.DeploymentConfig.DERPServerRelayAddress.Value) if err != nil { return nil, nil, xerrors.Errorf("derp-server-relay-address must be a valid HTTP URL: %w", err) } @@ -50,7 +50,7 @@ func server() *cobra.Command { } options.DERPServer.SetMeshKey(meshKey) - if dflags.AuditLogging.Value { + if options.DeploymentConfig.AuditLogging.Value { options.Auditor = audit.NewAuditor(audit.DefaultFilter, backends.NewPostgres(options.Database, true), backends.NewSlog(options.Logger), @@ -58,13 +58,13 @@ func server() *cobra.Command { } o := &coderd.Options{ - AuditLogging: dflags.AuditLogging.Value, - BrowserOnly: dflags.BrowserOnly.Value, - SCIMAPIKey: []byte(dflags.SCIMAuthHeader.Value), - UserWorkspaceQuota: dflags.UserWorkspaceQuota.Value, + AuditLogging: options.DeploymentConfig.AuditLogging.Value, + BrowserOnly: options.DeploymentConfig.BrowserOnly.Value, + SCIMAPIKey: []byte(options.DeploymentConfig.SCIMAPIKey.Value), + UserWorkspaceQuota: options.DeploymentConfig.UserWorkspaceQuota.Value, RBAC: true, - DERPServerRelayAddress: dflags.DerpServerRelayAddress.Value, - DERPServerRegionID: dflags.DerpServerRegionID.Value, + DERPServerRelayAddress: options.DeploymentConfig.DERPServerRelayAddress.Value, + DERPServerRegionID: options.DeploymentConfig.DERPServerRegionID.Value, Options: options, } @@ -76,6 +76,7 @@ func server() *cobra.Command { return api.AGPL, api, nil }) - deployment.AttachFlags(cmd.Flags(), dflags, true) + deployment.AttachFlags(cmd.Flags(), vip, true) + return cmd } diff --git a/go.mod b/go.mod index 758f1d6a4fff9..c7ec45dd2a9b2 100644 --- a/go.mod +++ b/go.mod @@ -160,6 +160,15 @@ require github.com/jmoiron/sqlx v1.3.5 require github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 +require ( + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/spf13/viper v1.13.0 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect +) + require ( filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect @@ -246,7 +255,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect github.com/opencontainers/runc v1.1.2 // indirect - github.com/pelletier/go-toml/v2 v2.0.4 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pion/transport v0.13.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 099a4d96cbd56..58de16d6865c3 100644 --- a/go.sum +++ b/go.sum @@ -594,6 +594,8 @@ github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsouza/fake-gcs-server v1.17.0/go.mod h1:D1rTE4YCyHFNa99oyJJ5HyclvN/0uQR+pM/VdlL83bw= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa h1:RDBNVkRviHZtvDvId8XSGPu3rmpmSe+wKRcEWNgsfWU= github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= @@ -1230,6 +1232,8 @@ github.com/mafredri/udp v0.1.2-0.20220805105907-b2872e92e98d/go.mod h1:GUd681aT3 github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -1483,8 +1487,12 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.4 h1:MHHO+ZUPwPZQ6BmnnT81iQg5cuurp78CRH7rNsguSMk= github.com/pelletier/go-toml/v2 v2.0.4/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= @@ -1680,6 +1688,8 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4= +github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= @@ -1703,6 +1713,8 @@ github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/swaggest/assertjson v1.7.0 h1:SKw5Rn0LQs6UvmGrIdaKQbMR1R3ncXm5KNon+QJ7jtw= github.com/sylvia7788/contextcheck v1.0.4/go.mod h1:vuPKJMQ7MQ91ZTqfdyreNKwZjyUg6KO+IebVyQDedZQ= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -2708,6 +2720,8 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/helm/README.md b/helm/README.md index 0fa51303c71a4..95b4ecffe3ed8 100644 --- a/helm/README.md +++ b/helm/README.md @@ -47,10 +47,10 @@ coder: # This env variable controls whether or not to auto-import the "kubernetes" # template on first startup. This will not work unless # coder.serviceAccount.workspacePerms is true. - - name: CODER_TEMPLATE_AUTOIMPORT + - name: CODER_AUTO_IMPORT_TEMPLATES value: "kubernetes" tls: - secretNames: + secretNames: - my-tls-secret-name ``` diff --git a/scripts/apitypings/main.go b/scripts/apitypings/main.go index 720a828f100bf..20ff1ba1f8250 100644 --- a/scripts/apitypings/main.go +++ b/scripts/apitypings/main.go @@ -441,6 +441,10 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err jsonOptional bool ) if err == nil { + if jsonTag.Name == "-" { + // Completely ignore this field. + continue + } jsonName = jsonTag.Name if len(jsonTag.Options) > 0 && jsonTag.Options[0] == "omitempty" { jsonOptional = true diff --git a/site/src/AppRouter.tsx b/site/src/AppRouter.tsx index c0759aecafdaf..feecf7dd6ab72 100644 --- a/site/src/AppRouter.tsx +++ b/site/src/AppRouter.tsx @@ -256,7 +256,7 @@ export const AppRouter: FC = () => { element={ @@ -270,7 +270,7 @@ export const AppRouter: FC = () => { element={ @@ -284,7 +284,7 @@ export const AppRouter: FC = () => { element={ @@ -298,7 +298,7 @@ export const AppRouter: FC = () => { element={ diff --git a/site/src/api/api.ts b/site/src/api/api.ts index ded0d3d3c89ba..8bb9303f97fa2 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -667,9 +667,9 @@ export const getAgentListeningPorts = async ( return response.data } -export const getDeploymentFlags = - async (): Promise => { - const response = await axios.get(`/api/v2/flags/deployment`) +export const getDeploymentConfig = + async (): Promise => { + const response = await axios.get(`/api/v2/config/deployment`) return response.data } diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index c8540c31888a9..0729f24e784f8 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -126,19 +126,6 @@ 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 hidden: boolean - readonly default: boolean - readonly value: boolean -} - // From codersdk/buildinfo.go export interface BuildInfoResponse { readonly external_url: string @@ -264,75 +251,67 @@ 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_server_relay_address: StringFlag - 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_client_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_files: 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 { +// From codersdk/deploymentconfig.go +export interface DeploymentConfig { + readonly access_url: DeploymentConfigField + readonly wildcard_access_url: DeploymentConfigField + readonly address: DeploymentConfigField + readonly autobuild_poll_interval: DeploymentConfigField + readonly derp_server_enabled: DeploymentConfigField + readonly derp_server_region_id: DeploymentConfigField + readonly derp_server_region_code: DeploymentConfigField + readonly derp_server_region_name: DeploymentConfigField + readonly derp_server_stun_address: DeploymentConfigField + readonly derp_server_relay_address: DeploymentConfigField + readonly derp_config_url: DeploymentConfigField + readonly derp_config_path: DeploymentConfigField + readonly prometheus_enabled: DeploymentConfigField + readonly prometheus_address: DeploymentConfigField + readonly pprof_enabled: DeploymentConfigField + readonly pprof_address: DeploymentConfigField + readonly cache_directory: DeploymentConfigField + readonly in_memory_database: DeploymentConfigField + readonly provisioner_daemon_count: DeploymentConfigField + readonly oauth2_github_client_id: DeploymentConfigField + readonly oauth2_github_allowed_organizations: DeploymentConfigField + readonly oauth2_github_allowed_teams: DeploymentConfigField + readonly oauth2_github_allow_signups: DeploymentConfigField + readonly oauth2_github_enterprise_base_url: DeploymentConfigField + readonly oidc_allow_signups: DeploymentConfigField + readonly oidc_client_id: DeploymentConfigField + readonly oidc_email_domain: DeploymentConfigField + readonly oidc_issuer_url: DeploymentConfigField + readonly oidc_scopes: DeploymentConfigField + readonly telemetry_enable: DeploymentConfigField + readonly telemetry_trace_enable: DeploymentConfigField + readonly telemetry_url: DeploymentConfigField + readonly tls_enable: DeploymentConfigField + readonly tls_cert_files: DeploymentConfigField + readonly tls_client_ca_file: DeploymentConfigField + readonly tls_client_auth: DeploymentConfigField + readonly tls_key_files: DeploymentConfigField + readonly tls_min_version: DeploymentConfigField + readonly trace_enable: DeploymentConfigField + readonly secure_auth_cookie: DeploymentConfigField + readonly ssh_keygen_algorithm: DeploymentConfigField + readonly auto_import_templates: DeploymentConfigField + readonly metrics_cache_refresh_interval: DeploymentConfigField + readonly agent_stat_refresh_interval: DeploymentConfigField + readonly audit_logging: DeploymentConfigField + readonly browser_only: DeploymentConfigField + readonly user_workspace_quota: DeploymentConfigField +} + +// From codersdk/deploymentconfig.go +export interface DeploymentConfigField { + readonly key: string readonly name: string + readonly usage: string readonly flag: string - readonly env_var: string readonly shorthand: string - readonly description: string readonly enterprise: boolean readonly hidden: 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 + readonly value: T } // From codersdk/features.go @@ -387,19 +366,6 @@ 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 hidden: boolean - readonly default: number - readonly value: number -} - // From codersdk/licenses.go export interface License { readonly id: number @@ -564,33 +530,6 @@ 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 hidden: 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 hidden: boolean - readonly default: string - readonly value: string -} - // From codersdk/templates.go export interface Template { readonly id: string @@ -999,3 +938,6 @@ export type WorkspaceStatus = // From codersdk/workspacebuilds.go export type WorkspaceTransition = "delete" | "start" | "stop" + +// From codersdk/deploymentconfig.go +export type Flaggable = string | boolean | number | string[] diff --git a/site/src/components/DeploySettingsLayout/DeploySettingsLayout.tsx b/site/src/components/DeploySettingsLayout/DeploySettingsLayout.tsx index b6abb27841f83..f9294d4108f7d 100644 --- a/site/src/components/DeploySettingsLayout/DeploySettingsLayout.tsx +++ b/site/src/components/DeploySettingsLayout/DeploySettingsLayout.tsx @@ -11,9 +11,9 @@ import React, { import { useActor } from "@xstate/react" import { XServiceContext } from "xServices/StateContext" import { Loader } from "components/Loader/Loader" -import { DeploymentFlags } from "api/typesGenerated" +import { DeploymentConfig } from "api/typesGenerated" -type DeploySettingsContextValue = { deploymentFlags: DeploymentFlags } +type DeploySettingsContextValue = { deploymentConfig: DeploymentConfig } const DeploySettingsContext = createContext< DeploySettingsContextValue | undefined @@ -33,9 +33,9 @@ export const DeploySettingsLayout: React.FC = ({ children, }) => { const xServices = useContext(XServiceContext) - const [state, send] = useActor(xServices.deploymentFlagsXService) + const [state, send] = useActor(xServices.deploymentConfigXService) const styles = useStyles() - const { deploymentFlags } = state.context + const { deploymentConfig } = state.context useEffect(() => { if (state.matches("idle")) { @@ -48,8 +48,10 @@ export const DeploySettingsLayout: React.FC = ({
- {deploymentFlags ? ( - + {deploymentConfig ? ( + {children} ) : ( diff --git a/site/src/components/DeploySettingsLayout/OptionsTable.tsx b/site/src/components/DeploySettingsLayout/OptionsTable.tsx index d54c614b48949..8b7fbd111d110 100644 --- a/site/src/components/DeploySettingsLayout/OptionsTable.tsx +++ b/site/src/components/DeploySettingsLayout/OptionsTable.tsx @@ -5,7 +5,7 @@ import TableCell from "@material-ui/core/TableCell" import TableContainer from "@material-ui/core/TableContainer" import TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" -import { DeploymentFlags } from "api/typesGenerated" +import { DeploymentConfig } from "api/typesGenerated" import { OptionDescription, OptionName, @@ -13,7 +13,7 @@ import { } from "components/DeploySettingsLayout/Option" import React from "react" -const OptionsTable: React.FC<{ options: Partial }> = ({ +const OptionsTable: React.FC<{ options: Partial }> = ({ options, }) => { const styles = useStyles() diff --git a/site/src/components/Navbar/Navbar.tsx b/site/src/components/Navbar/Navbar.tsx index 95355e5a1dbe7..56d763b4a66f1 100644 --- a/site/src/components/Navbar/Navbar.tsx +++ b/site/src/components/Navbar/Navbar.tsx @@ -22,7 +22,7 @@ export const Navbar: React.FC = () => { featureVisibility[FeatureNames.AuditLog] && Boolean(permissions?.viewAuditLog) const canViewDeployment = - experimental && Boolean(permissions?.viewDeploymentFlags) + experimental && Boolean(permissions?.viewDeploymentConfig) const onSignOut = () => authSend("SIGN_OUT") return ( diff --git a/site/src/pages/DeploySettingsPage/AuthSettingsPage.tsx b/site/src/pages/DeploySettingsPage/AuthSettingsPage.tsx index 008f9f25cbe22..150c0fb006afb 100644 --- a/site/src/pages/DeploySettingsPage/AuthSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/AuthSettingsPage.tsx @@ -12,7 +12,7 @@ import { Helmet } from "react-helmet-async" import { pageTitle } from "util/page" const AuthSettingsPage: React.FC = () => { - const { deploymentFlags } = useDeploySettings() + const { deploymentConfig: deploymentConfig } = useDeploySettings() return ( <> @@ -32,7 +32,7 @@ const AuthSettingsPage: React.FC = () => { /> - {deploymentFlags.oidc_client_id.value ? ( + {deploymentConfig.oidc_client_id.value ? ( ) : ( @@ -41,12 +41,12 @@ const AuthSettingsPage: React.FC = () => { @@ -60,7 +60,7 @@ const AuthSettingsPage: React.FC = () => { /> - {deploymentFlags.oauth2_github_client_id.value ? ( + {deploymentConfig.oauth2_github_client_id.value ? ( ) : ( @@ -69,17 +69,17 @@ const AuthSettingsPage: React.FC = () => { diff --git a/site/src/pages/DeploySettingsPage/GeneralSettingsPage.tsx b/site/src/pages/DeploySettingsPage/GeneralSettingsPage.tsx index ebf523fb0e5a7..59fd23e7a3308 100644 --- a/site/src/pages/DeploySettingsPage/GeneralSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/GeneralSettingsPage.tsx @@ -6,7 +6,7 @@ import { Helmet } from "react-helmet-async" import { pageTitle } from "util/page" const GeneralSettingsPage: React.FC = () => { - const { deploymentFlags } = useDeploySettings() + const { deploymentConfig: deploymentConfig } = useDeploySettings() return ( <> @@ -22,9 +22,9 @@ const GeneralSettingsPage: React.FC = () => { diff --git a/site/src/pages/DeploySettingsPage/NetworkSettingsPage.tsx b/site/src/pages/DeploySettingsPage/NetworkSettingsPage.tsx index 7bcf9cdede202..23e87a284e4b2 100644 --- a/site/src/pages/DeploySettingsPage/NetworkSettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/NetworkSettingsPage.tsx @@ -6,7 +6,7 @@ import { Helmet } from "react-helmet-async" import { pageTitle } from "util/page" const NetworkSettingsPage: React.FC = () => { - const { deploymentFlags } = useDeploySettings() + const { deploymentConfig: deploymentConfig } = useDeploySettings() return ( <> @@ -22,10 +22,10 @@ const NetworkSettingsPage: React.FC = () => { diff --git a/site/src/pages/DeploySettingsPage/SecuritySettingsPage.tsx b/site/src/pages/DeploySettingsPage/SecuritySettingsPage.tsx index 987c61b93c85f..0a7e3afdccae9 100644 --- a/site/src/pages/DeploySettingsPage/SecuritySettingsPage.tsx +++ b/site/src/pages/DeploySettingsPage/SecuritySettingsPage.tsx @@ -16,7 +16,7 @@ import { pageTitle } from "util/page" import { XServiceContext } from "xServices/StateContext" const SecuritySettingsPage: React.FC = () => { - const { deploymentFlags } = useDeploySettings() + const { deploymentConfig: deploymentConfig } = useDeploySettings() const xServices = useContext(XServiceContext) const [entitlementsState] = useActor(xServices.entitlementsXService) @@ -34,8 +34,8 @@ const SecuritySettingsPage: React.FC = () => { @@ -89,10 +89,10 @@ const SecuritySettingsPage: React.FC = () => { diff --git a/site/src/xServices/StateContext.tsx b/site/src/xServices/StateContext.tsx index 565ca3023a5b9..8df5003aff85e 100644 --- a/site/src/xServices/StateContext.tsx +++ b/site/src/xServices/StateContext.tsx @@ -3,7 +3,7 @@ import { createContext, FC, ReactNode } from "react" import { ActorRefFrom } from "xstate" import { authMachine } from "./auth/authXService" import { buildInfoMachine } from "./buildInfo/buildInfoXService" -import { deploymentFlagsMachine } from "./deploymentFlags/deploymentFlagsMachine" +import { deploymentConfigMachine } from "./deploymentConfig/deploymentConfigMachine" import { entitlementsMachine } from "./entitlements/entitlementsXService" import { siteRolesMachine } from "./roles/siteRolesXService" @@ -13,7 +13,7 @@ interface XServiceContextType { entitlementsXService: ActorRefFrom siteRolesXService: ActorRefFrom // Since the info here is used by multiple deployment settings page and we don't want to refetch them every time - deploymentFlagsXService: ActorRefFrom + deploymentConfigXService: ActorRefFrom } /** @@ -34,7 +34,7 @@ export const XServiceProvider: FC<{ children: ReactNode }> = ({ children }) => { buildInfoXService: useInterpret(buildInfoMachine), entitlementsXService: useInterpret(entitlementsMachine), siteRolesXService: useInterpret(siteRolesMachine), - deploymentFlagsXService: useInterpret(deploymentFlagsMachine), + deploymentConfigXService: useInterpret(deploymentConfigMachine), }} > {children} diff --git a/site/src/xServices/auth/authXService.ts b/site/src/xServices/auth/authXService.ts index af25518f8bcaa..79439dbc298a4 100644 --- a/site/src/xServices/auth/authXService.ts +++ b/site/src/xServices/auth/authXService.ts @@ -16,7 +16,7 @@ export const checks = { createTemplates: "createTemplates", deleteTemplates: "deleteTemplates", viewAuditLog: "viewAuditLog", - viewDeploymentFlags: "viewDeploymentFlags", + viewDeploymentConfig: "viewDeploymentConfig", createGroup: "createGroup", } as const @@ -57,7 +57,7 @@ export const permissionsToCheck = { }, action: "read", }, - [checks.viewDeploymentFlags]: { + [checks.viewDeploymentConfig]: { object: { resource_type: "deployment_flags", }, diff --git a/site/src/xServices/deploymentConfig/deploymentConfigMachine.ts b/site/src/xServices/deploymentConfig/deploymentConfigMachine.ts new file mode 100644 index 0000000000000..d5fd0f18bdc3b --- /dev/null +++ b/site/src/xServices/deploymentConfig/deploymentConfigMachine.ts @@ -0,0 +1,62 @@ +import { getDeploymentConfig } from "api/api" +import { DeploymentConfig } from "api/typesGenerated" +import { createMachine, assign } from "xstate" + +export const deploymentConfigMachine = createMachine( + { + id: "deploymentConfigMachine", + predictableActionArguments: true, + initial: "idle", + schema: { + context: {} as { + deploymentConfig?: DeploymentConfig + getDeploymentConfigError?: unknown + }, + events: {} as { type: "LOAD" }, + services: {} as { + getDeploymentConfig: { + data: DeploymentConfig + } + }, + }, + tsTypes: {} as import("./deploymentConfigMachine.typegen").Typegen0, + states: { + idle: { + on: { + LOAD: { + target: "loading", + }, + }, + }, + loading: { + invoke: { + src: "getDeploymentConfig", + onDone: { + target: "loaded", + actions: ["assignDeploymentConfig"], + }, + onError: { + target: "idle", + actions: ["assignGetDeploymentConfigError"], + }, + }, + }, + loaded: { + type: "final", + }, + }, + }, + { + services: { + getDeploymentConfig: getDeploymentConfig, + }, + actions: { + assignDeploymentConfig: assign({ + deploymentConfig: (_, { data }) => data, + }), + assignGetDeploymentConfigError: assign({ + getDeploymentConfigError: (_, { data }) => data, + }), + }, + }, +) diff --git a/site/src/xServices/deploymentFlags/deploymentFlagsMachine.ts b/site/src/xServices/deploymentFlags/deploymentFlagsMachine.ts deleted file mode 100644 index aa18e9d179ce1..0000000000000 --- a/site/src/xServices/deploymentFlags/deploymentFlagsMachine.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { getDeploymentFlags } from "api/api" -import { DeploymentFlags } from "api/typesGenerated" -import { createMachine, assign } from "xstate" - -export const deploymentFlagsMachine = createMachine( - { - id: "deploymentFlagsMachine", - predictableActionArguments: true, - initial: "idle", - schema: { - context: {} as { - deploymentFlags?: DeploymentFlags - getDeploymentFlagsError?: unknown - }, - events: {} as { type: "LOAD" }, - services: {} as { - getDeploymentFlags: { - data: DeploymentFlags - } - }, - }, - tsTypes: {} as import("./deploymentFlagsMachine.typegen").Typegen0, - states: { - idle: { - on: { - LOAD: { - target: "loading", - }, - }, - }, - loading: { - invoke: { - src: "getDeploymentFlags", - onDone: { - target: "loaded", - actions: ["assignDeploymentFlags"], - }, - onError: { - target: "idle", - actions: ["assignGetDeploymentFlagsError"], - }, - }, - }, - loaded: { - type: "final", - }, - }, - }, - { - services: { - getDeploymentFlags, - }, - actions: { - assignDeploymentFlags: assign({ - deploymentFlags: (_, { data }) => data, - }), - assignGetDeploymentFlagsError: assign({ - getDeploymentFlagsError: (_, { data }) => data, - }), - }, - }, -)