Skip to content

Commit 977d3ae

Browse files
committed
Always enable experimental features
The CLI disabled experimental features by default, requiring users to set a configuration option to enable them. Disabling experimental features was a request from Enterprise users that did not want experimental features to be accessible. We are changing this policy, and now enable experimental features by default. Experimental features may still change and/or removed, and will be highlighted in the documentation and "usage" output. For example, the `docker manifest inspect --help` output now shows: EXPERIMENTAL: docker manifest inspect is an experimental feature. Experimental features provide early access to product functionality. These features may change between releases without warning or can be removed entirely from a future release. Learn more about experimental features: https://docs.docker.com/go/experimental/ Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent d9c36c2 commit 977d3ae

File tree

14 files changed

+48
-126
lines changed

14 files changed

+48
-126
lines changed

cli-plugins/examples/helloworld/main.go

-1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,5 @@ func main() {
101101
SchemaVersion: "0.1.0",
102102
Vendor: "Docker Inc.",
103103
Version: "testing",
104-
Experimental: os.Getenv("HELLO_EXPERIMENTAL") != "",
105104
})
106105
}

cli-plugins/manager/candidate_test.go

+6-9
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,9 @@ import (
1212
)
1313

1414
type fakeCandidate struct {
15-
path string
16-
exec bool
17-
meta string
18-
allowExperimental bool
15+
path string
16+
exec bool
17+
meta string
1918
}
2019

2120
func (c *fakeCandidate) Path() string {
@@ -30,7 +29,7 @@ func (c *fakeCandidate) Metadata() ([]byte, error) {
3029
}
3130

3231
func TestValidateCandidate(t *testing.T) {
33-
var (
32+
const (
3433
goodPluginName = NamePrefix + "goodplugin"
3534

3635
builtinName = NamePrefix + "builtin"
@@ -70,14 +69,12 @@ func TestValidateCandidate(t *testing.T) {
7069
{name: "invalid schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, invalid: `plugin SchemaVersion "xyzzy" is not valid`},
7170
{name: "no vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`}, invalid: "plugin metadata does not define a vendor"},
7271
{name: "empty vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, invalid: "plugin metadata does not define a vendor"},
73-
{name: "experimental required", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}, invalid: "requires experimental CLI"},
7472
// This one should work
7573
{name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}},
76-
{name: "valid + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`, allowExperimental: true}},
77-
{name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental, allowExperimental: true}},
74+
{name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}},
7875
} {
7976
t.Run(tc.name, func(t *testing.T) {
80-
p, err := newPlugin(tc.c, fakeroot, tc.c.allowExperimental)
77+
p, err := newPlugin(tc.c, fakeroot)
8178
if tc.err != "" {
8279
assert.ErrorContains(t, err, tc.err)
8380
} else if tc.invalid != "" {

cli-plugins/manager/manager.go

+2-20
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package manager
22

33
import (
4-
"fmt"
54
"io/ioutil"
65
"os"
76
"os/exec"
@@ -30,16 +29,6 @@ func (e errPluginNotFound) Error() string {
3029
return "Error: No such CLI plugin: " + string(e)
3130
}
3231

33-
type errPluginRequireExperimental string
34-
35-
// Note: errPluginRequireExperimental implements notFound so that the plugin
36-
// is skipped when listing the plugins.
37-
func (e errPluginRequireExperimental) NotFound() {}
38-
39-
func (e errPluginRequireExperimental) Error() string {
40-
return fmt.Sprintf("plugin candidate %q: requires experimental CLI", string(e))
41-
}
42-
4332
type notFound interface{ NotFound() }
4433

4534
// IsNotFound is true if the given error is due to a plugin not being found.
@@ -133,7 +122,7 @@ func ListPlugins(dockerCli command.Cli, rootcmd *cobra.Command) ([]Plugin, error
133122
continue
134123
}
135124
c := &candidate{paths[0]}
136-
p, err := newPlugin(c, rootcmd, dockerCli.ClientInfo().HasExperimental)
125+
p, err := newPlugin(c, rootcmd)
137126
if err != nil {
138127
return nil, err
139128
}
@@ -181,19 +170,12 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
181170
}
182171

183172
c := &candidate{path: path}
184-
plugin, err := newPlugin(c, rootcmd, dockerCli.ClientInfo().HasExperimental)
173+
plugin, err := newPlugin(c, rootcmd)
185174
if err != nil {
186175
return nil, err
187176
}
188177
if plugin.Err != nil {
189178
// TODO: why are we not returning plugin.Err?
190-
191-
err := plugin.Err.(*pluginError).Cause()
192-
// if an experimental plugin was invoked directly while experimental mode is off
193-
// provide a more useful error message than "not found".
194-
if err, ok := err.(errPluginRequireExperimental); ok {
195-
return nil, err
196-
}
197179
return nil, errPluginNotFound(name)
198180
}
199181
cmd := exec.Command(plugin.Path, args...)

cli-plugins/manager/metadata.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ type Metadata struct {
2323
// URL is a pointer to the plugin's homepage.
2424
URL string `json:",omitempty"`
2525
// Experimental specifies whether the plugin is experimental.
26-
// Experimental plugins are not displayed on non-experimental CLIs.
26+
// Deprecated: experimental features are now always enabled in the CLI
2727
Experimental bool `json:",omitempty"`
2828
}

cli-plugins/manager/plugin.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ type Plugin struct {
3535
// non-recoverable error.
3636
//
3737
// nolint: gocyclo
38-
func newPlugin(c Candidate, rootcmd *cobra.Command, allowExperimental bool) (Plugin, error) {
38+
func newPlugin(c Candidate, rootcmd *cobra.Command) (Plugin, error) {
3939
path := c.Path()
4040
if path == "" {
4141
return Plugin{}, errors.New("plugin candidate path cannot be empty")
@@ -96,10 +96,6 @@ func newPlugin(c Candidate, rootcmd *cobra.Command, allowExperimental bool) (Plu
9696
p.Err = wrapAsPluginError(err, "invalid metadata")
9797
return p, nil
9898
}
99-
if p.Experimental && !allowExperimental {
100-
p.Err = &pluginError{errPluginRequireExperimental(p.Name)}
101-
return p, nil
102-
}
10399
if p.Metadata.SchemaVersion != "0.1.0" {
104100
p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
105101
return p, nil

cli/command/cli.go

+3-22
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,6 @@ func (cli *DockerCli) ClientInfo() ClientInfo {
152152
}
153153

154154
func (cli *DockerCli) loadClientInfo() error {
155-
var experimentalValue string
156-
// Environment variable always overrides configuration
157-
if experimentalValue = os.Getenv("DOCKER_CLI_EXPERIMENTAL"); experimentalValue == "" {
158-
experimentalValue = cli.ConfigFile().Experimental
159-
}
160-
hasExperimental, err := isEnabled(experimentalValue)
161-
if err != nil {
162-
return errors.Wrap(err, "Experimental field")
163-
}
164-
165155
var v string
166156
if cli.client != nil {
167157
v = cli.client.ClientVersion()
@@ -170,7 +160,7 @@ func (cli *DockerCli) loadClientInfo() error {
170160
}
171161
cli.clientInfo = &ClientInfo{
172162
DefaultVersion: v,
173-
HasExperimental: hasExperimental,
163+
HasExperimental: true,
174164
}
175165
return nil
176166
}
@@ -358,17 +348,6 @@ func resolveDefaultDockerEndpoint(opts *cliflags.CommonOptions) (docker.Endpoint
358348
}, nil
359349
}
360350

361-
func isEnabled(value string) (bool, error) {
362-
switch value {
363-
case "enabled":
364-
return true, nil
365-
case "", "disabled":
366-
return false, nil
367-
default:
368-
return false, errors.Errorf("%q is not valid, should be either enabled or disabled", value)
369-
}
370-
}
371-
372351
func (cli *DockerCli) initializeFromClient() {
373352
ctx := context.Background()
374353
if strings.HasPrefix(cli.DockerEndpoint().Host, "tcp://") {
@@ -471,6 +450,8 @@ type ServerInfo struct {
471450

472451
// ClientInfo stores details about the supported features of the client
473452
type ClientInfo struct {
453+
// Deprecated: experimental CLI features always enabled. This field is kept
454+
// for backward-compatibility, and is always "true".
474455
HasExperimental bool
475456
DefaultVersion string
476457
}

cli/command/cli_test.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -167,25 +167,24 @@ func TestInitializeFromClient(t *testing.T) {
167167
}
168168
}
169169

170+
// The CLI no longer disables/hides experimental CLI features, however, we need
171+
// to verify that existing configuration files do not break
170172
func TestExperimentalCLI(t *testing.T) {
171173
defaultVersion := "v1.55"
172174

173175
var testcases = []struct {
174-
doc string
175-
configfile string
176-
expectedExperimentalCLI bool
176+
doc string
177+
configfile string
177178
}{
178179
{
179-
doc: "default",
180-
configfile: `{}`,
181-
expectedExperimentalCLI: false,
180+
doc: "default",
181+
configfile: `{}`,
182182
},
183183
{
184184
doc: "experimental",
185185
configfile: `{
186186
"experimental": "enabled"
187187
}`,
188-
expectedExperimentalCLI: true,
189188
},
190189
}
191190

@@ -205,7 +204,8 @@ func TestExperimentalCLI(t *testing.T) {
205204
cliconfig.SetDir(dir.Path())
206205
err := cli.Initialize(flags.NewClientOptions())
207206
assert.NilError(t, err)
208-
assert.Check(t, is.Equal(testcase.expectedExperimentalCLI, cli.ClientInfo().HasExperimental))
207+
// For backward-compatibility, HasExperimental will always be "true"
208+
assert.Check(t, is.Equal(true, cli.ClientInfo().HasExperimental))
209209
})
210210
}
211211
}

cli/command/stack/kubernetes/cli.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) {
103103
}
104104

105105
func (c *KubeCli) composeClient() (*Factory, error) {
106-
return NewFactory(c.kubeNamespace, c.kubeConfig, c.clientSet, c.ClientInfo().HasExperimental)
106+
return NewFactory(c.kubeNamespace, c.kubeConfig, c.clientSet)
107107
}
108108

109109
func (c *KubeCli) checkHostsMatch() error {

cli/command/stack/kubernetes/client.go

+2-4
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ type Factory struct {
1818
coreClientSet corev1.CoreV1Interface
1919
appsClientSet appsv1beta2.AppsV1beta2Interface
2020
clientSet *kubeclient.Clientset
21-
experimental bool
2221
}
2322

2423
// NewFactory creates a kubernetes client factory
25-
func NewFactory(namespace string, config *restclient.Config, clientSet *kubeclient.Clientset, experimental bool) (*Factory, error) {
24+
func NewFactory(namespace string, config *restclient.Config, clientSet *kubeclient.Clientset) (*Factory, error) {
2625
coreClientSet, err := corev1.NewForConfig(config)
2726
if err != nil {
2827
return nil, err
@@ -39,7 +38,6 @@ func NewFactory(namespace string, config *restclient.Config, clientSet *kubeclie
3938
coreClientSet: coreClientSet,
4039
appsClientSet: appsClientSet,
4140
clientSet: clientSet,
42-
experimental: experimental,
4341
}, nil
4442
}
4543

@@ -85,7 +83,7 @@ func (s *Factory) DaemonSets() typesappsv1beta2.DaemonSetInterface {
8583

8684
// Stacks returns a client for Docker's Stack on Kubernetes
8785
func (s *Factory) Stacks(allNamespaces bool) (StackClient, error) {
88-
version, err := kubernetes.GetStackAPIVersion(s.clientSet.Discovery(), s.experimental)
86+
version, err := kubernetes.GetStackAPIVersion(s.clientSet.Discovery())
8987
if err != nil {
9088
return nil, err
9189
}

cli/command/system/version.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ package system
22

33
import (
44
"context"
5-
"fmt"
65
"runtime"
76
"sort"
7+
"strconv"
88
"text/tabwriter"
99
"text/template"
1010
"time"
@@ -83,7 +83,7 @@ type clientVersion struct {
8383
Arch string
8484
BuildTime string `json:",omitempty"`
8585
Context string
86-
Experimental bool
86+
Experimental bool `json:",omitempty"` // Deprecated: experimental CLI features always enabled. This field is kept for backward-compatibility, and is always "true"
8787
}
8888

8989
type kubernetesVersion struct {
@@ -157,7 +157,7 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error {
157157
BuildTime: reformatDate(version.BuildTime),
158158
Os: runtime.GOOS,
159159
Arch: arch(),
160-
Experimental: dockerCli.ClientInfo().HasExperimental,
160+
Experimental: true,
161161
Context: dockerCli.CurrentContext(),
162162
},
163163
}
@@ -199,7 +199,7 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error {
199199
"Os": sv.Os,
200200
"Arch": sv.Arch,
201201
"BuildTime": reformatDate(vd.Server.BuildTime),
202-
"Experimental": fmt.Sprintf("%t", sv.Experimental),
202+
"Experimental": strconv.FormatBool(sv.Experimental),
203203
},
204204
})
205205
}
@@ -274,13 +274,13 @@ func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesV
274274
logrus.Debugf("failed to get Kubernetes client: %s", err)
275275
return &version
276276
}
277-
version.StackAPI = getStackVersion(kubeClient, dockerCli.ClientInfo().HasExperimental)
277+
version.StackAPI = getStackVersion(kubeClient)
278278
version.Kubernetes = getKubernetesServerVersion(kubeClient)
279279
return &version
280280
}
281281

282-
func getStackVersion(client *kubernetesClient.Clientset, experimental bool) string {
283-
apiVersion, err := kubernetes.GetStackAPIVersion(client, experimental)
282+
func getStackVersion(client *kubernetesClient.Clientset) string {
283+
apiVersion, err := kubernetes.GetStackAPIVersion(client)
284284
if err != nil {
285285
logrus.Debugf("failed to get Stack API version: %s", err)
286286
return "Unknown"

cmd/docker/docker.go

+5-14
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,11 @@ func hideSubcommandIf(subcmd *cobra.Command, condition func(string) bool, annota
336336

337337
func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
338338
var (
339-
buildKitDisabled = func(_ string) bool { v, _ := command.BuildKitEnabled(details.ServerInfo()); return !v }
340-
buildKitEnabled = func(_ string) bool { v, _ := command.BuildKitEnabled(details.ServerInfo()); return v }
341-
notExperimental = func(_ string) bool { return !details.ServerInfo().HasExperimental }
342-
notExperimentalCLI = func(_ string) bool { return !details.ClientInfo().HasExperimental }
343-
notOSType = func(v string) bool { return v != details.ServerInfo().OSType }
344-
versionOlderThan = func(v string) bool { return versions.LessThan(details.Client().ClientVersion(), v) }
339+
buildKitDisabled = func(_ string) bool { v, _ := command.BuildKitEnabled(details.ServerInfo()); return !v }
340+
buildKitEnabled = func(_ string) bool { v, _ := command.BuildKitEnabled(details.ServerInfo()); return v }
341+
notExperimental = func(_ string) bool { return !details.ServerInfo().HasExperimental }
342+
notOSType = func(v string) bool { return v != details.ServerInfo().OSType }
343+
versionOlderThan = func(v string) bool { return versions.LessThan(details.Client().ClientVersion(), v) }
345344
)
346345

347346
cmd.Flags().VisitAll(func(f *pflag.Flag) {
@@ -359,7 +358,6 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
359358
hideFlagIf(f, buildKitDisabled, "buildkit")
360359
hideFlagIf(f, buildKitEnabled, "no-buildkit")
361360
hideFlagIf(f, notExperimental, "experimental")
362-
hideFlagIf(f, notExperimentalCLI, "experimentalCLI")
363361
hideFlagIf(f, notOSType, "ostype")
364362
hideFlagIf(f, versionOlderThan, "version")
365363
})
@@ -368,7 +366,6 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
368366
hideSubcommandIf(subcmd, buildKitDisabled, "buildkit")
369367
hideSubcommandIf(subcmd, buildKitEnabled, "no-buildkit")
370368
hideSubcommandIf(subcmd, notExperimental, "experimental")
371-
hideSubcommandIf(subcmd, notExperimentalCLI, "experimentalCLI")
372369
hideSubcommandIf(subcmd, notOSType, "ostype")
373370
hideSubcommandIf(subcmd, versionOlderThan, "version")
374371
}
@@ -417,9 +414,6 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
417414
if _, ok := f.Annotations["experimental"]; ok && !details.ServerInfo().HasExperimental {
418415
errs = append(errs, fmt.Sprintf(`"--%s" is only supported on a Docker daemon with experimental features enabled`, f.Name))
419416
}
420-
if _, ok := f.Annotations["experimentalCLI"]; ok && !details.ClientInfo().HasExperimental {
421-
errs = append(errs, fmt.Sprintf(`"--%s" is only supported on a Docker cli with experimental cli features enabled`, f.Name))
422-
}
423417
// buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case
424418
})
425419
if len(errs) > 0 {
@@ -441,9 +435,6 @@ func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error {
441435
if _, ok := curr.Annotations["experimental"]; ok && !details.ServerInfo().HasExperimental {
442436
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
443437
}
444-
if _, ok := curr.Annotations["experimentalCLI"]; ok && !details.ClientInfo().HasExperimental {
445-
return fmt.Errorf("%s is only supported on a Docker cli with experimental cli features enabled", cmd.CommandPath())
446-
}
447438
}
448439
return nil
449440
}

0 commit comments

Comments
 (0)