Skip to content

Commit 76c7b91

Browse files
committed
feat: support multiple values for --experimental
1 parent 501cfa9 commit 76c7b91

File tree

27 files changed

+543
-46
lines changed

27 files changed

+543
-46
lines changed

cli/deployment/config.go

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -446,10 +446,11 @@ func newConfig() *codersdk.DeploymentConfig {
446446
Default: 512,
447447
},
448448
},
449-
Experimental: &codersdk.DeploymentConfigField[bool]{
450-
Name: "Experimental",
451-
Usage: "Enable experimental features. Experimental features are not ready for production.",
452-
Flag: "experimental",
449+
Experimental: &codersdk.DeploymentConfigField[codersdk.Experiments]{
450+
Name: "Experimental",
451+
Usage: "Enable one or more experiments. These are not ready for production. Separate multiple experiments with commas, or enter '*' to opt-in to all available experiments.",
452+
Flag: "experimental",
453+
Default: []string{},
453454
},
454455
UpdateCheck: &codersdk.DeploymentConfigField[bool]{
455456
Name: "Update Check",
@@ -557,15 +558,37 @@ func setConfig(prefix string, vip *viper.Viper, target interface{}) {
557558
// with a comma, but Viper only supports with a space. This
558559
// is a small hack around it!
559560
rawSlice := reflect.ValueOf(vip.GetStringSlice(prefix)).Interface()
560-
slice, ok := rawSlice.([]string)
561+
stringSlice, ok := rawSlice.([]string)
561562
if !ok {
562563
panic(fmt.Sprintf("string slice is of type %T", rawSlice))
563564
}
564-
value := make([]string, 0, len(slice))
565-
for _, entry := range slice {
565+
value := make([]string, 0, len(stringSlice))
566+
for _, entry := range stringSlice {
566567
value = append(value, strings.Split(entry, ",")...)
567568
}
568569
val.FieldByName("Value").Set(reflect.ValueOf(value))
570+
case codersdk.Experiments:
571+
// As []string above, but we support setting wildcard values
572+
// '*' or 'true' to enable experiments listed in codersdk.ExperimentsAll.
573+
// Experiments not listed in codersdk.ExperimentsAll must be enabled
574+
// explicitly.
575+
vip.MustBindEnv(prefix, env)
576+
rawSlice := reflect.ValueOf(vip.GetStringSlice(prefix)).Interface()
577+
stringSlice, ok := rawSlice.([]string)
578+
if !ok {
579+
panic(fmt.Sprintf("string slice is of type %T", rawSlice))
580+
}
581+
value := make([]string, 0, len(stringSlice))
582+
for _, entry := range stringSlice {
583+
for _, val := range strings.Split(entry, ",") {
584+
if val == "*" || val == "true" {
585+
value = append(value, codersdk.ExperimentsAll...)
586+
} else {
587+
value = append(value, val)
588+
}
589+
}
590+
}
591+
val.FieldByName("Value").Set(reflect.ValueOf(codersdk.Experiments(value)))
569592
case []codersdk.GitAuthConfig:
570593
// Do not bind to CODER_GITAUTH, instead bind to CODER_GITAUTH_0_*, etc.
571594
values := readSliceFromViper[codersdk.GitAuthConfig](vip, prefix, value)
@@ -743,7 +766,7 @@ func setFlags(prefix string, flagset *pflag.FlagSet, vip *viper.Viper, target in
743766
_ = flagset.IntP(flg, shorthand, vip.GetInt(prefix), usage)
744767
case time.Duration:
745768
_ = flagset.DurationP(flg, shorthand, vip.GetDuration(prefix), usage)
746-
case []string:
769+
case []string, codersdk.Experiments:
747770
_ = flagset.StringSliceP(flg, shorthand, vip.GetStringSlice(prefix), usage)
748771
case []codersdk.GitAuthConfig:
749772
// Ignore this one!

cli/deployment/config_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,23 @@ func TestConfig(t *testing.T) {
232232
require.Equal(t, config.Prometheus.Enable.Value, true)
233233
require.Equal(t, config.Prometheus.Address.Value, config.Prometheus.Address.Default)
234234
},
235+
}, {
236+
Name: "Experimental - no features",
237+
Env: map[string]string{
238+
"CODER_EXPERIMENTAL": "",
239+
},
240+
Valid: func(config *codersdk.DeploymentConfig) {
241+
require.Empty(t, config.Experimental.Value)
242+
},
243+
}, {
244+
Name: "Experimental - multiple features",
245+
Env: map[string]string{
246+
"CODER_EXPERIMENTAL": "foo,bar",
247+
},
248+
Valid: func(config *codersdk.DeploymentConfig) {
249+
expected := []string{"foo", "bar"}
250+
require.ElementsMatch(t, expected, config.Experimental.Value)
251+
},
235252
}} {
236253
tc := tc
237254
t.Run(tc.Name, func(t *testing.T) {

cli/testdata/coder_server_--help.golden

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,11 @@ Flags:
6161
Consumes
6262
$CODER_DERP_SERVER_STUN_ADDRESSES
6363
(default [stun.l.google.com:19302])
64-
--experimental Enable experimental features.
65-
Experimental features are not ready for
66-
production.
64+
--experimental strings Enable one or more experiments. These are
65+
not ready for production. Separate
66+
multiple experiments with commas, or
67+
enter '*' to opt-in to all available
68+
experiments.
6769
Consumes $CODER_EXPERIMENTAL
6870
-h, --help help for server
6971
--http-address string HTTP bind address of the server. Unset to

coderd/apidoc/docs.go

Lines changed: 63 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 59 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,7 @@ func New(options *Options) *API {
348348
r.Post("/csp/reports", api.logReportCSPViolations)
349349

350350
r.Get("/buildinfo", buildInfo)
351+
r.Get("/experiments", api.handleExperimentsGet)
351352
r.Get("/updatecheck", api.updateCheck)
352353
r.Route("/config", func(r chi.Router) {
353354
r.Use(apiKeyMiddleware)

coderd/coderdtest/authorize.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
4848
"GET:/healthz": {NoAuthorize: true},
4949
"GET:/api/v2": {NoAuthorize: true},
5050
"GET:/api/v2/buildinfo": {NoAuthorize: true},
51+
"GET:/api/v2/experiments": {NoAuthorize: true},
5152
"GET:/api/v2/updatecheck": {NoAuthorize: true},
5253
"GET:/api/v2/users/first": {NoAuthorize: true},
5354
"POST:/api/v2/users/first": {NoAuthorize: true},

coderd/coderdtest/coderdtest.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ type Options struct {
8585
AppHostname string
8686
AWSCertificates awsidentity.Certificates
8787
Authorizer rbac.Authorizer
88-
Experimental bool
8988
AzureCertificates x509.VerifyOptions
9089
GithubOAuth2Config *coderd.GithubOAuth2Config
9190
RealIPConfig *httpmw.RealIPConfig

coderd/experiments.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/coderd/httpapi"
7+
)
8+
9+
// @Summary Get experiments
10+
// @ID get-experiments
11+
// @Produce json
12+
// @Tags General
13+
// @Success 200 {array} string
14+
// @Router /experiments [get]
15+
func (api *API) handleExperimentsGet(rw http.ResponseWriter, r *http.Request) {
16+
ctx := r.Context()
17+
httpapi.Write(ctx, rw, http.StatusOK, api.DeploymentConfig.Experimental.Value)
18+
}

0 commit comments

Comments
 (0)