Skip to content

Commit b1faaef

Browse files
authored
feat: deployment flags (#4426)
1 parent b50bb99 commit b1faaef

File tree

11 files changed

+950
-285
lines changed

11 files changed

+950
-285
lines changed

cli/deployment/flags.go

Lines changed: 455 additions & 0 deletions
Large diffs are not rendered by default.

cli/root.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/coder/coder/cli/cliflag"
2323
"github.com/coder/coder/cli/cliui"
2424
"github.com/coder/coder/cli/config"
25+
"github.com/coder/coder/cli/deployment"
2526
"github.com/coder/coder/coderd"
2627
"github.com/coder/coder/codersdk"
2728
)
@@ -98,7 +99,9 @@ func Core() []*cobra.Command {
9899
}
99100

100101
func AGPL() []*cobra.Command {
101-
all := append(Core(), Server(func(_ context.Context, o *coderd.Options) (*coderd.API, error) {
102+
df := deployment.Flags()
103+
all := append(Core(), Server(df, func(_ context.Context, o *coderd.Options) (*coderd.API, error) {
104+
o.DeploymentFlags = &df
102105
return coderd.New(o), nil
103106
}))
104107
return all

cli/server.go

Lines changed: 137 additions & 260 deletions
Large diffs are not rendered by default.

coderd/coderd.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ type Options struct {
8282
MetricsCacheRefreshInterval time.Duration
8383
AgentStatsRefreshInterval time.Duration
8484
Experimental bool
85+
DeploymentFlags *codersdk.DeploymentFlags
8586
}
8687

8788
// New constructs a Coder API handler.
@@ -259,6 +260,10 @@ func New(options *Options) *API {
259260
})
260261
})
261262
})
263+
r.Route("/flags", func(r chi.Router) {
264+
r.Use(apiKeyMiddleware)
265+
r.Get("/deployment", api.deploymentFlags)
266+
})
262267
r.Route("/audit", func(r chi.Router) {
263268
r.Use(
264269
apiKeyMiddleware,

coderd/coderdtest/coderdtest.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ type Options struct {
8383
IncludeProvisionerDaemon bool
8484
MetricsCacheRefreshInterval time.Duration
8585
AgentStatsRefreshInterval time.Duration
86+
DeploymentFlags *codersdk.DeploymentFlags
8687
}
8788

8889
// New constructs a codersdk client connected to an in-memory API instance.
@@ -237,6 +238,7 @@ func NewOptions(t *testing.T, options *Options) (*httptest.Server, context.Cance
237238
AutoImportTemplates: options.AutoImportTemplates,
238239
MetricsCacheRefreshInterval: options.MetricsCacheRefreshInterval,
239240
AgentStatsRefreshInterval: options.AgentStatsRefreshInterval,
241+
DeploymentFlags: options.DeploymentFlags,
240242
}
241243
}
242244

coderd/flags.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package coderd
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/coder/coder/cli/deployment"
7+
"github.com/coder/coder/coderd/httpapi"
8+
"github.com/coder/coder/coderd/rbac"
9+
)
10+
11+
func (api *API) deploymentFlags(rw http.ResponseWriter, r *http.Request) {
12+
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceDeploymentFlags) {
13+
httpapi.Forbidden(rw)
14+
return
15+
}
16+
17+
httpapi.Write(r.Context(), rw, http.StatusOK, deployment.RemoveSensitiveValues(*api.DeploymentFlags))
18+
}

coderd/flags_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package coderd_test
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/coder/coder/cli/deployment"
10+
"github.com/coder/coder/coderd/coderdtest"
11+
"github.com/coder/coder/testutil"
12+
)
13+
14+
const (
15+
secretValue = "********"
16+
)
17+
18+
func TestDeploymentFlagSecrets(t *testing.T) {
19+
t.Parallel()
20+
hi := "hi"
21+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
22+
defer cancel()
23+
df := deployment.Flags()
24+
// check if copy works for non-secret values
25+
df.AccessURL.Value = hi
26+
// check if secrets are removed
27+
df.OAuth2GithubClientSecret.Value = hi
28+
df.OIDCClientSecret.Value = hi
29+
df.PostgresURL.Value = hi
30+
df.SCIMAuthHeader.Value = hi
31+
32+
client := coderdtest.New(t, &coderdtest.Options{
33+
DeploymentFlags: &df,
34+
})
35+
_ = coderdtest.CreateFirstUser(t, client)
36+
scrubbed, err := client.DeploymentFlags(ctx)
37+
require.NoError(t, err)
38+
// ensure df is unchanged
39+
require.EqualValues(t, hi, df.OAuth2GithubClientSecret.Value)
40+
// ensure normal values pass through
41+
require.EqualValues(t, hi, scrubbed.AccessURL.Value)
42+
// ensure secrets are removed
43+
require.EqualValues(t, secretValue, scrubbed.OAuth2GithubClientSecret.Value)
44+
require.EqualValues(t, secretValue, scrubbed.OIDCClientSecret.Value)
45+
require.EqualValues(t, secretValue, scrubbed.PostgresURL.Value)
46+
require.EqualValues(t, secretValue, scrubbed.SCIMAuthHeader.Value)
47+
}

coderd/rbac/object.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ var (
133133
ResourceLicense = Object{
134134
Type: "license",
135135
}
136+
137+
// ResourceDeploymentFlags
138+
ResourceDeploymentFlags = Object{
139+
Type: "deployment_flags",
140+
}
136141
)
137142

138143
// Object is used to create objects for authz checks when you have none in

codersdk/flags.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package codersdk
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"time"
8+
9+
"golang.org/x/xerrors"
10+
)
11+
12+
type DeploymentFlags struct {
13+
AccessURL StringFlag `json:"access_url"`
14+
WildcardAccessURL StringFlag `json:"wildcard_access_url"`
15+
Address StringFlag `json:"address"`
16+
AutobuildPollInterval DurationFlag `json:"autobuild_poll_interval"`
17+
DerpServerEnable BoolFlag `json:"derp_server_enabled"`
18+
DerpServerRegionID IntFlag `json:"derp_server_region_id"`
19+
DerpServerRegionCode StringFlag `json:"derp_server_region_code"`
20+
DerpServerRegionName StringFlag `json:"derp_server_region_name"`
21+
DerpServerSTUNAddresses StringArrayFlag `json:"derp_server_stun_address"`
22+
DerpConfigURL StringFlag `json:"derp_config_url"`
23+
DerpConfigPath StringFlag `json:"derp_config_path"`
24+
PromEnabled BoolFlag `json:"prom_enabled"`
25+
PromAddress StringFlag `json:"prom_address"`
26+
PprofEnabled BoolFlag `json:"pprof_enabled"`
27+
PprofAddress StringFlag `json:"pprof_address"`
28+
CacheDir StringFlag `json:"cache_dir"`
29+
InMemoryDatabase BoolFlag `json:"in_memory_database"`
30+
ProvisionerDaemonCount IntFlag `json:"provisioner_daemon_count"`
31+
PostgresURL StringFlag `json:"postgres_url"`
32+
OAuth2GithubClientID StringFlag `json:"oauth2_github_client_id"`
33+
OAuth2GithubClientSecret StringFlag `json:"oauth2_github_client_secret"`
34+
OAuth2GithubAllowedOrganizations StringArrayFlag `json:"oauth2_github_allowed_organizations"`
35+
OAuth2GithubAllowedTeams StringArrayFlag `json:"oauth2_github_allowed_teams"`
36+
OAuth2GithubAllowSignups BoolFlag `json:"oauth2_github_allow_signups"`
37+
OAuth2GithubEnterpriseBaseURL StringFlag `json:"oauth2_github_enterprise_base_url"`
38+
OIDCAllowSignups BoolFlag `json:"oidc_allow_signups"`
39+
OIDCClientID StringFlag `json:"oidc_client_id"`
40+
OIDCClientSecret StringFlag `json:"oidc_cliet_secret"`
41+
OIDCEmailDomain StringFlag `json:"oidc_email_domain"`
42+
OIDCIssuerURL StringFlag `json:"oidc_issuer_url"`
43+
OIDCScopes StringArrayFlag `json:"oidc_scopes"`
44+
TelemetryEnable BoolFlag `json:"telemetry_enable"`
45+
TelemetryTraceEnable BoolFlag `json:"telemetry_trace_enable"`
46+
TelemetryURL StringFlag `json:"telemetry_url"`
47+
TLSEnable BoolFlag `json:"tls_enable"`
48+
TLSCertFiles StringArrayFlag `json:"tls_cert_files"`
49+
TLSClientCAFile StringFlag `json:"tls_client_ca_file"`
50+
TLSClientAuth StringFlag `json:"tls_client_auth"`
51+
TLSKeyFiles StringArrayFlag `json:"tls_key_tiles"`
52+
TLSMinVersion StringFlag `json:"tls_min_version"`
53+
TraceEnable BoolFlag `json:"trace_enable"`
54+
SecureAuthCookie BoolFlag `json:"secure_auth_cookie"`
55+
SSHKeygenAlgorithm StringFlag `json:"ssh_keygen_algorithm"`
56+
AutoImportTemplates StringArrayFlag `json:"auto_import_templates"`
57+
MetricsCacheRefreshInterval DurationFlag `json:"metrics_cache_refresh_interval"`
58+
AgentStatRefreshInterval DurationFlag `json:"agent_stat_refresh_interval"`
59+
Verbose BoolFlag `json:"verbose"`
60+
AuditLogging BoolFlag `json:"audit_logging"`
61+
BrowserOnly BoolFlag `json:"browser_only"`
62+
SCIMAuthHeader StringFlag `json:"scim_auth_header"`
63+
UserWorkspaceQuota IntFlag `json:"user_workspace_quota"`
64+
}
65+
66+
type StringFlag struct {
67+
Name string `json:"name"`
68+
Flag string `json:"flag"`
69+
EnvVar string `json:"env_var"`
70+
Shorthand string `json:"shorthand"`
71+
Description string `json:"description"`
72+
Enterprise bool `json:"enterprise"`
73+
Secret bool `json:"secret"`
74+
Default string `json:"default"`
75+
Value string `json:"value"`
76+
}
77+
78+
type BoolFlag struct {
79+
Name string `json:"name"`
80+
Flag string `json:"flag"`
81+
EnvVar string `json:"env_var"`
82+
Shorthand string `json:"shorthand"`
83+
Description string `json:"description"`
84+
Enterprise bool `json:"enterprise"`
85+
Default bool `json:"default"`
86+
Value bool `json:"value"`
87+
}
88+
89+
type IntFlag struct {
90+
Name string `json:"name"`
91+
Flag string `json:"flag"`
92+
EnvVar string `json:"env_var"`
93+
Shorthand string `json:"shorthand"`
94+
Description string `json:"description"`
95+
Enterprise bool `json:"enterprise"`
96+
Default int `json:"default"`
97+
Value int `json:"value"`
98+
}
99+
100+
type DurationFlag struct {
101+
Name string `json:"name"`
102+
Flag string `json:"flag"`
103+
EnvVar string `json:"env_var"`
104+
Shorthand string `json:"shorthand"`
105+
Description string `json:"description"`
106+
Enterprise bool `json:"enterprise"`
107+
Default time.Duration `json:"default"`
108+
Value time.Duration `json:"value"`
109+
}
110+
111+
type StringArrayFlag struct {
112+
Name string `json:"name"`
113+
Flag string `json:"flag"`
114+
EnvVar string `json:"env_var"`
115+
Shorthand string `json:"shorthand"`
116+
Description string `json:"description"`
117+
Enterprise bool `json:"enterprise"`
118+
Default []string `json:"default"`
119+
Value []string `json:"value"`
120+
}
121+
122+
// DeploymentFlags returns the deployment level flags for the coder server.
123+
func (c *Client) DeploymentFlags(ctx context.Context) (DeploymentFlags, error) {
124+
res, err := c.Request(ctx, http.MethodGet, "/api/v2/flags/deployment", nil)
125+
if err != nil {
126+
return DeploymentFlags{}, xerrors.Errorf("execute request: %w", err)
127+
}
128+
defer res.Body.Close()
129+
130+
if res.StatusCode != http.StatusOK {
131+
return DeploymentFlags{}, readBodyAsError(res)
132+
}
133+
134+
var df DeploymentFlags
135+
return df, json.NewDecoder(res.Body).Decode(&df)
136+
}

enterprise/cli/server.go

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,43 @@ import (
55

66
"github.com/spf13/cobra"
77

8-
"github.com/coder/coder/cli/cliflag"
98
"github.com/coder/coder/cli/cliui"
9+
"github.com/coder/coder/cli/deployment"
1010
"github.com/coder/coder/enterprise/coderd"
1111

1212
agpl "github.com/coder/coder/cli"
1313
agplcoderd "github.com/coder/coder/coderd"
1414
)
1515

1616
func server() *cobra.Command {
17-
var (
18-
auditLogging bool
19-
browserOnly bool
20-
scimAuthHeader string
21-
userWorkspaceQuota int
22-
)
23-
cmd := agpl.Server(func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, error) {
24-
api, err := coderd.New(ctx, &coderd.Options{
25-
AuditLogging: auditLogging,
26-
BrowserOnly: browserOnly,
27-
SCIMAPIKey: []byte(scimAuthHeader),
28-
UserWorkspaceQuota: userWorkspaceQuota,
17+
dflags := deployment.Flags()
18+
cmd := agpl.Server(dflags, func(ctx context.Context, options *agplcoderd.Options) (*agplcoderd.API, error) {
19+
options.DeploymentFlags = &dflags
20+
o := &coderd.Options{
21+
AuditLogging: dflags.AuditLogging.Value,
22+
BrowserOnly: dflags.BrowserOnly.Value,
23+
SCIMAPIKey: []byte(dflags.SCIMAuthHeader.Value),
24+
UserWorkspaceQuota: dflags.UserWorkspaceQuota.Value,
2925
Options: options,
30-
})
26+
}
27+
api, err := coderd.New(ctx, o)
3128
if err != nil {
3229
return nil, err
3330
}
3431
return api.AGPL, nil
3532
})
36-
enterpriseOnly := cliui.Styles.Keyword.Render("This is an Enterprise feature. Contact sales@coder.com for licensing")
37-
38-
cliflag.BoolVarP(cmd.Flags(), &auditLogging, "audit-logging", "", "CODER_AUDIT_LOGGING", true,
39-
"Specifies whether audit logging is enabled. "+enterpriseOnly)
40-
cliflag.BoolVarP(cmd.Flags(), &browserOnly, "browser-only", "", "CODER_BROWSER_ONLY", false,
41-
"Whether Coder only allows connections to workspaces via the browser. "+enterpriseOnly)
42-
cliflag.StringVarP(cmd.Flags(), &scimAuthHeader, "scim-auth-header", "", "CODER_SCIM_API_KEY", "",
43-
"Enables SCIM and sets the authentication header for the built-in SCIM server. New users are automatically created with OIDC authentication. "+enterpriseOnly)
44-
cliflag.IntVarP(cmd.Flags(), &userWorkspaceQuota, "user-workspace-quota", "", "CODER_USER_WORKSPACE_QUOTA", 0,
45-
"A positive number applies a limit on how many workspaces each user can create. "+enterpriseOnly)
33+
34+
// append enterprise description to flags
35+
enterpriseOnly := cliui.Styles.Keyword.Render(" This is an Enterprise feature. Contact sales@coder.com for licensing")
36+
dflags.AuditLogging.Description += enterpriseOnly
37+
dflags.BrowserOnly.Description += enterpriseOnly
38+
dflags.SCIMAuthHeader.Description += enterpriseOnly
39+
dflags.UserWorkspaceQuota.Description += enterpriseOnly
40+
41+
deployment.BoolFlag(cmd.Flags(), &dflags.AuditLogging)
42+
deployment.BoolFlag(cmd.Flags(), &dflags.BrowserOnly)
43+
deployment.StringFlag(cmd.Flags(), &dflags.SCIMAuthHeader)
44+
deployment.IntFlag(cmd.Flags(), &dflags.UserWorkspaceQuota)
4645

4746
return cmd
4847
}

0 commit comments

Comments
 (0)