Skip to content

Commit 56b9965

Browse files
authored
feat: add --experiments flag to replace --experimental (#5767)
- Deprecates the --experimental flag - Adds a new flag --experiments which supports passing multiple comma-separated values or a wildcard value. - Exposes a new endpoint /api/v2/experiments that returns the list of enabled experiments. - Deprecates the field Features.Experimental in favour of this new API. - Updates apidocgen to support type aliases (shoutout to @mtojek). - Modifies apitypings to support generating slice types. - Updates develop.sh to pass additional args after -- to $CODERD_SHIM.
1 parent 47c3d72 commit 56b9965

File tree

29 files changed

+593
-41
lines changed

29 files changed

+593
-41
lines changed

cli/deployment/config.go

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -446,10 +446,19 @@ func newConfig() *codersdk.DeploymentConfig {
446446
Default: 512,
447447
},
448448
},
449+
// DEPRECATED: use Experiments instead.
449450
Experimental: &codersdk.DeploymentConfigField[bool]{
450-
Name: "Experimental",
451-
Usage: "Enable experimental features. Experimental features are not ready for production.",
452-
Flag: "experimental",
451+
Name: "Experimental",
452+
Usage: "Enable experimental features. Experimental features are not ready for production.",
453+
Flag: "experimental",
454+
Default: false,
455+
Hidden: true,
456+
},
457+
Experiments: &codersdk.DeploymentConfigField[[]string]{
458+
Name: "Experiments",
459+
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.",
460+
Flag: "experiments",
461+
Default: []string{},
453462
},
454463
UpdateCheck: &codersdk.DeploymentConfigField[bool]{
455464
Name: "Update Check",
@@ -557,12 +566,12 @@ func setConfig(prefix string, vip *viper.Viper, target interface{}) {
557566
// with a comma, but Viper only supports with a space. This
558567
// is a small hack around it!
559568
rawSlice := reflect.ValueOf(vip.GetStringSlice(prefix)).Interface()
560-
slice, ok := rawSlice.([]string)
569+
stringSlice, ok := rawSlice.([]string)
561570
if !ok {
562571
panic(fmt.Sprintf("string slice is of type %T", rawSlice))
563572
}
564-
value := make([]string, 0, len(slice))
565-
for _, entry := range slice {
573+
value := make([]string, 0, len(stringSlice))
574+
for _, entry := range stringSlice {
566575
value = append(value, strings.Split(entry, ",")...)
567576
}
568577
val.FieldByName("Value").Set(reflect.ValueOf(value))

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: "Experiments - no features",
237+
Env: map[string]string{
238+
"CODER_EXPERIMENTS": "",
239+
},
240+
Valid: func(config *codersdk.DeploymentConfig) {
241+
require.Empty(t, config.Experiments.Value)
242+
},
243+
}, {
244+
Name: "Experiments - multiple features",
245+
Env: map[string]string{
246+
"CODER_EXPERIMENTS": "foo,bar",
247+
},
248+
Valid: func(config *codersdk.DeploymentConfig) {
249+
expected := []string{"foo", "bar"}
250+
require.ElementsMatch(t, expected, config.Experiments.Value)
251+
},
235252
}} {
236253
tc := tc
237254
t.Run(tc.Name, func(t *testing.T) {

cli/testdata/coder_server_--help.golden

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,12 @@ 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.
67-
Consumes $CODER_EXPERIMENTAL
64+
--experiments 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.
69+
Consumes $CODER_EXPERIMENTS
6870
-h, --help help for server
6971
--http-address string HTTP bind address of the server. Unset to
7072
disable the HTTP endpoint.

coderd/apidoc/docs.go

Lines changed: 47 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: 39 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: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"net/url"
1212
"path/filepath"
1313
"regexp"
14+
"strings"
1415
"sync"
1516
"sync/atomic"
1617
"time"
@@ -52,6 +53,7 @@ import (
5253
"github.com/coder/coder/coderd/telemetry"
5354
"github.com/coder/coder/coderd/tracing"
5455
"github.com/coder/coder/coderd/updatecheck"
56+
"github.com/coder/coder/coderd/util/slice"
5557
"github.com/coder/coder/coderd/wsconncache"
5658
"github.com/coder/coder/codersdk"
5759
"github.com/coder/coder/provisionerd/proto"
@@ -220,6 +222,7 @@ func New(options *Options) *API {
220222
},
221223
metricsCache: metricsCache,
222224
Auditor: atomic.Pointer[audit.Auditor]{},
225+
Experiments: initExperiments(options.Logger, options.DeploymentConfig.Experiments.Value, options.DeploymentConfig.Experimental.Value),
223226
}
224227
if options.UpdateCheckOptions != nil {
225228
api.updateChecker = updatecheck.New(
@@ -348,6 +351,10 @@ func New(options *Options) *API {
348351
r.Post("/csp/reports", api.logReportCSPViolations)
349352

350353
r.Get("/buildinfo", buildInfo)
354+
r.Route("/experiments", func(r chi.Router) {
355+
r.Use(apiKeyMiddleware)
356+
r.Get("/", api.handleExperimentsGet)
357+
})
351358
r.Get("/updatecheck", api.updateCheck)
352359
r.Route("/config", func(r chi.Router) {
353360
r.Use(apiKeyMiddleware)
@@ -646,6 +653,10 @@ type API struct {
646653
metricsCache *metricscache.Cache
647654
workspaceAgentCache *wsconncache.Cache
648655
updateChecker *updatecheck.Checker
656+
657+
// Experiments contains the list of experiments currently enabled.
658+
// This is used to gate features that are not yet ready for production.
659+
Experiments codersdk.Experiments
649660
}
650661

651662
// Close waits for all WebSocket connections to drain before returning.
@@ -752,3 +763,27 @@ func (api *API) CreateInMemoryProvisionerDaemon(ctx context.Context, debounce ti
752763

753764
return proto.NewDRPCProvisionerDaemonClient(clientSession), nil
754765
}
766+
767+
// nolint:revive
768+
func initExperiments(log slog.Logger, raw []string, legacyAll bool) codersdk.Experiments {
769+
exps := make([]codersdk.Experiment, 0, len(raw))
770+
for _, v := range raw {
771+
switch v {
772+
case "*":
773+
exps = append(exps, codersdk.ExperimentsAll...)
774+
default:
775+
ex := codersdk.Experiment(strings.ToLower(v))
776+
if !slice.Contains(codersdk.ExperimentsAll, ex) {
777+
log.Warn(context.Background(), "🐉 HERE BE DRAGONS: opting into hidden experiment", slog.F("experiment", ex))
778+
}
779+
exps = append(exps, ex)
780+
}
781+
}
782+
783+
// --experiments takes precedence over --experimental. It's deprecated.
784+
if legacyAll && len(raw) == 0 {
785+
log.Warn(context.Background(), "--experimental is deprecated, use --experiments='*' instead")
786+
exps = append(exps, codersdk.ExperimentsAll...)
787+
}
788+
return exps
789+
}

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}, // This route requires AuthN, but not AuthZ.
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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
// @Security CoderSessionToken
12+
// @Produce json
13+
// @Tags General
14+
// @Success 200 {array} codersdk.Experiment
15+
// @Router /experiments [get]
16+
func (api *API) handleExperimentsGet(rw http.ResponseWriter, r *http.Request) {
17+
ctx := r.Context()
18+
httpapi.Write(ctx, rw, http.StatusOK, api.Experiments)
19+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy