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 <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2020-10-02 14:19:34 +02:00
parent d9c36c2878
commit 977d3ae046
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
14 changed files with 48 additions and 126 deletions

View File

@ -101,6 +101,5 @@ func main() {
SchemaVersion: "0.1.0", SchemaVersion: "0.1.0",
Vendor: "Docker Inc.", Vendor: "Docker Inc.",
Version: "testing", Version: "testing",
Experimental: os.Getenv("HELLO_EXPERIMENTAL") != "",
}) })
} }

View File

@ -12,10 +12,9 @@ import (
) )
type fakeCandidate struct { type fakeCandidate struct {
path string path string
exec bool exec bool
meta string meta string
allowExperimental bool
} }
func (c *fakeCandidate) Path() string { func (c *fakeCandidate) Path() string {
@ -30,7 +29,7 @@ func (c *fakeCandidate) Metadata() ([]byte, error) {
} }
func TestValidateCandidate(t *testing.T) { func TestValidateCandidate(t *testing.T) {
var ( const (
goodPluginName = NamePrefix + "goodplugin" goodPluginName = NamePrefix + "goodplugin"
builtinName = NamePrefix + "builtin" builtinName = NamePrefix + "builtin"
@ -70,14 +69,12 @@ func TestValidateCandidate(t *testing.T) {
{name: "invalid schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, invalid: `plugin SchemaVersion "xyzzy" is not valid`}, {name: "invalid schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, invalid: `plugin SchemaVersion "xyzzy" is not valid`},
{name: "no vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`}, invalid: "plugin metadata does not define a vendor"}, {name: "no vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`}, invalid: "plugin metadata does not define a vendor"},
{name: "empty vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, invalid: "plugin metadata does not define a vendor"}, {name: "empty vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, invalid: "plugin metadata does not define a vendor"},
{name: "experimental required", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}, invalid: "requires experimental CLI"},
// This one should work // This one should work
{name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}}, {name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}},
{name: "valid + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`, allowExperimental: true}}, {name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}},
{name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental, allowExperimental: true}},
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
p, err := newPlugin(tc.c, fakeroot, tc.c.allowExperimental) p, err := newPlugin(tc.c, fakeroot)
if tc.err != "" { if tc.err != "" {
assert.ErrorContains(t, err, tc.err) assert.ErrorContains(t, err, tc.err)
} else if tc.invalid != "" { } else if tc.invalid != "" {

View File

@ -1,7 +1,6 @@
package manager package manager
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
@ -30,16 +29,6 @@ func (e errPluginNotFound) Error() string {
return "Error: No such CLI plugin: " + string(e) return "Error: No such CLI plugin: " + string(e)
} }
type errPluginRequireExperimental string
// Note: errPluginRequireExperimental implements notFound so that the plugin
// is skipped when listing the plugins.
func (e errPluginRequireExperimental) NotFound() {}
func (e errPluginRequireExperimental) Error() string {
return fmt.Sprintf("plugin candidate %q: requires experimental CLI", string(e))
}
type notFound interface{ NotFound() } type notFound interface{ NotFound() }
// IsNotFound is true if the given error is due to a plugin not being found. // 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
continue continue
} }
c := &candidate{paths[0]} c := &candidate{paths[0]}
p, err := newPlugin(c, rootcmd, dockerCli.ClientInfo().HasExperimental) p, err := newPlugin(c, rootcmd)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -181,19 +170,12 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
} }
c := &candidate{path: path} c := &candidate{path: path}
plugin, err := newPlugin(c, rootcmd, dockerCli.ClientInfo().HasExperimental) plugin, err := newPlugin(c, rootcmd)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if plugin.Err != nil { if plugin.Err != nil {
// TODO: why are we not returning plugin.Err? // TODO: why are we not returning plugin.Err?
err := plugin.Err.(*pluginError).Cause()
// if an experimental plugin was invoked directly while experimental mode is off
// provide a more useful error message than "not found".
if err, ok := err.(errPluginRequireExperimental); ok {
return nil, err
}
return nil, errPluginNotFound(name) return nil, errPluginNotFound(name)
} }
cmd := exec.Command(plugin.Path, args...) cmd := exec.Command(plugin.Path, args...)

View File

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

View File

@ -35,7 +35,7 @@ type Plugin struct {
// non-recoverable error. // non-recoverable error.
// //
// nolint: gocyclo // nolint: gocyclo
func newPlugin(c Candidate, rootcmd *cobra.Command, allowExperimental bool) (Plugin, error) { func newPlugin(c Candidate, rootcmd *cobra.Command) (Plugin, error) {
path := c.Path() path := c.Path()
if path == "" { if path == "" {
return Plugin{}, errors.New("plugin candidate path cannot be empty") return Plugin{}, errors.New("plugin candidate path cannot be empty")
@ -96,10 +96,6 @@ func newPlugin(c Candidate, rootcmd *cobra.Command, allowExperimental bool) (Plu
p.Err = wrapAsPluginError(err, "invalid metadata") p.Err = wrapAsPluginError(err, "invalid metadata")
return p, nil return p, nil
} }
if p.Experimental && !allowExperimental {
p.Err = &pluginError{errPluginRequireExperimental(p.Name)}
return p, nil
}
if p.Metadata.SchemaVersion != "0.1.0" { if p.Metadata.SchemaVersion != "0.1.0" {
p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion) p.Err = NewPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
return p, nil return p, nil

View File

@ -152,16 +152,6 @@ func (cli *DockerCli) ClientInfo() ClientInfo {
} }
func (cli *DockerCli) loadClientInfo() error { func (cli *DockerCli) loadClientInfo() error {
var experimentalValue string
// Environment variable always overrides configuration
if experimentalValue = os.Getenv("DOCKER_CLI_EXPERIMENTAL"); experimentalValue == "" {
experimentalValue = cli.ConfigFile().Experimental
}
hasExperimental, err := isEnabled(experimentalValue)
if err != nil {
return errors.Wrap(err, "Experimental field")
}
var v string var v string
if cli.client != nil { if cli.client != nil {
v = cli.client.ClientVersion() v = cli.client.ClientVersion()
@ -170,7 +160,7 @@ func (cli *DockerCli) loadClientInfo() error {
} }
cli.clientInfo = &ClientInfo{ cli.clientInfo = &ClientInfo{
DefaultVersion: v, DefaultVersion: v,
HasExperimental: hasExperimental, HasExperimental: true,
} }
return nil return nil
} }
@ -358,17 +348,6 @@ func resolveDefaultDockerEndpoint(opts *cliflags.CommonOptions) (docker.Endpoint
}, nil }, nil
} }
func isEnabled(value string) (bool, error) {
switch value {
case "enabled":
return true, nil
case "", "disabled":
return false, nil
default:
return false, errors.Errorf("%q is not valid, should be either enabled or disabled", value)
}
}
func (cli *DockerCli) initializeFromClient() { func (cli *DockerCli) initializeFromClient() {
ctx := context.Background() ctx := context.Background()
if strings.HasPrefix(cli.DockerEndpoint().Host, "tcp://") { if strings.HasPrefix(cli.DockerEndpoint().Host, "tcp://") {
@ -471,6 +450,8 @@ type ServerInfo struct {
// ClientInfo stores details about the supported features of the client // ClientInfo stores details about the supported features of the client
type ClientInfo struct { type ClientInfo struct {
// Deprecated: experimental CLI features always enabled. This field is kept
// for backward-compatibility, and is always "true".
HasExperimental bool HasExperimental bool
DefaultVersion string DefaultVersion string
} }

View File

@ -167,25 +167,24 @@ func TestInitializeFromClient(t *testing.T) {
} }
} }
// The CLI no longer disables/hides experimental CLI features, however, we need
// to verify that existing configuration files do not break
func TestExperimentalCLI(t *testing.T) { func TestExperimentalCLI(t *testing.T) {
defaultVersion := "v1.55" defaultVersion := "v1.55"
var testcases = []struct { var testcases = []struct {
doc string doc string
configfile string configfile string
expectedExperimentalCLI bool
}{ }{
{ {
doc: "default", doc: "default",
configfile: `{}`, configfile: `{}`,
expectedExperimentalCLI: false,
}, },
{ {
doc: "experimental", doc: "experimental",
configfile: `{ configfile: `{
"experimental": "enabled" "experimental": "enabled"
}`, }`,
expectedExperimentalCLI: true,
}, },
} }
@ -205,7 +204,8 @@ func TestExperimentalCLI(t *testing.T) {
cliconfig.SetDir(dir.Path()) cliconfig.SetDir(dir.Path())
err := cli.Initialize(flags.NewClientOptions()) err := cli.Initialize(flags.NewClientOptions())
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.Equal(testcase.expectedExperimentalCLI, cli.ClientInfo().HasExperimental)) // For backward-compatibility, HasExperimental will always be "true"
assert.Check(t, is.Equal(true, cli.ClientInfo().HasExperimental))
}) })
} }
} }

View File

@ -103,7 +103,7 @@ func WrapCli(dockerCli command.Cli, opts Options) (*KubeCli, error) {
} }
func (c *KubeCli) composeClient() (*Factory, error) { func (c *KubeCli) composeClient() (*Factory, error) {
return NewFactory(c.kubeNamespace, c.kubeConfig, c.clientSet, c.ClientInfo().HasExperimental) return NewFactory(c.kubeNamespace, c.kubeConfig, c.clientSet)
} }
func (c *KubeCli) checkHostsMatch() error { func (c *KubeCli) checkHostsMatch() error {

View File

@ -18,11 +18,10 @@ type Factory struct {
coreClientSet corev1.CoreV1Interface coreClientSet corev1.CoreV1Interface
appsClientSet appsv1beta2.AppsV1beta2Interface appsClientSet appsv1beta2.AppsV1beta2Interface
clientSet *kubeclient.Clientset clientSet *kubeclient.Clientset
experimental bool
} }
// NewFactory creates a kubernetes client factory // NewFactory creates a kubernetes client factory
func NewFactory(namespace string, config *restclient.Config, clientSet *kubeclient.Clientset, experimental bool) (*Factory, error) { func NewFactory(namespace string, config *restclient.Config, clientSet *kubeclient.Clientset) (*Factory, error) {
coreClientSet, err := corev1.NewForConfig(config) coreClientSet, err := corev1.NewForConfig(config)
if err != nil { if err != nil {
return nil, err return nil, err
@ -39,7 +38,6 @@ func NewFactory(namespace string, config *restclient.Config, clientSet *kubeclie
coreClientSet: coreClientSet, coreClientSet: coreClientSet,
appsClientSet: appsClientSet, appsClientSet: appsClientSet,
clientSet: clientSet, clientSet: clientSet,
experimental: experimental,
}, nil }, nil
} }
@ -85,7 +83,7 @@ func (s *Factory) DaemonSets() typesappsv1beta2.DaemonSetInterface {
// Stacks returns a client for Docker's Stack on Kubernetes // Stacks returns a client for Docker's Stack on Kubernetes
func (s *Factory) Stacks(allNamespaces bool) (StackClient, error) { func (s *Factory) Stacks(allNamespaces bool) (StackClient, error) {
version, err := kubernetes.GetStackAPIVersion(s.clientSet.Discovery(), s.experimental) version, err := kubernetes.GetStackAPIVersion(s.clientSet.Discovery())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,9 +2,9 @@ package system
import ( import (
"context" "context"
"fmt"
"runtime" "runtime"
"sort" "sort"
"strconv"
"text/tabwriter" "text/tabwriter"
"text/template" "text/template"
"time" "time"
@ -83,7 +83,7 @@ type clientVersion struct {
Arch string Arch string
BuildTime string `json:",omitempty"` BuildTime string `json:",omitempty"`
Context string Context string
Experimental bool Experimental bool `json:",omitempty"` // Deprecated: experimental CLI features always enabled. This field is kept for backward-compatibility, and is always "true"
} }
type kubernetesVersion struct { type kubernetesVersion struct {
@ -157,7 +157,7 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error {
BuildTime: reformatDate(version.BuildTime), BuildTime: reformatDate(version.BuildTime),
Os: runtime.GOOS, Os: runtime.GOOS,
Arch: arch(), Arch: arch(),
Experimental: dockerCli.ClientInfo().HasExperimental, Experimental: true,
Context: dockerCli.CurrentContext(), Context: dockerCli.CurrentContext(),
}, },
} }
@ -199,7 +199,7 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error {
"Os": sv.Os, "Os": sv.Os,
"Arch": sv.Arch, "Arch": sv.Arch,
"BuildTime": reformatDate(vd.Server.BuildTime), "BuildTime": reformatDate(vd.Server.BuildTime),
"Experimental": fmt.Sprintf("%t", sv.Experimental), "Experimental": strconv.FormatBool(sv.Experimental),
}, },
}) })
} }
@ -274,13 +274,13 @@ func getKubernetesVersion(dockerCli command.Cli, kubeConfig string) *kubernetesV
logrus.Debugf("failed to get Kubernetes client: %s", err) logrus.Debugf("failed to get Kubernetes client: %s", err)
return &version return &version
} }
version.StackAPI = getStackVersion(kubeClient, dockerCli.ClientInfo().HasExperimental) version.StackAPI = getStackVersion(kubeClient)
version.Kubernetes = getKubernetesServerVersion(kubeClient) version.Kubernetes = getKubernetesServerVersion(kubeClient)
return &version return &version
} }
func getStackVersion(client *kubernetesClient.Clientset, experimental bool) string { func getStackVersion(client *kubernetesClient.Clientset) string {
apiVersion, err := kubernetes.GetStackAPIVersion(client, experimental) apiVersion, err := kubernetes.GetStackAPIVersion(client)
if err != nil { if err != nil {
logrus.Debugf("failed to get Stack API version: %s", err) logrus.Debugf("failed to get Stack API version: %s", err)
return "Unknown" return "Unknown"

View File

@ -336,12 +336,11 @@ func hideSubcommandIf(subcmd *cobra.Command, condition func(string) bool, annota
func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error { func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
var ( var (
buildKitDisabled = func(_ string) bool { v, _ := command.BuildKitEnabled(details.ServerInfo()); return !v } buildKitDisabled = func(_ string) bool { v, _ := command.BuildKitEnabled(details.ServerInfo()); return !v }
buildKitEnabled = func(_ string) bool { v, _ := command.BuildKitEnabled(details.ServerInfo()); return v } buildKitEnabled = func(_ string) bool { v, _ := command.BuildKitEnabled(details.ServerInfo()); return v }
notExperimental = func(_ string) bool { return !details.ServerInfo().HasExperimental } notExperimental = func(_ string) bool { return !details.ServerInfo().HasExperimental }
notExperimentalCLI = func(_ string) bool { return !details.ClientInfo().HasExperimental } notOSType = func(v string) bool { return v != details.ServerInfo().OSType }
notOSType = func(v string) bool { return v != details.ServerInfo().OSType } versionOlderThan = func(v string) bool { return versions.LessThan(details.Client().ClientVersion(), v) }
versionOlderThan = func(v string) bool { return versions.LessThan(details.Client().ClientVersion(), v) }
) )
cmd.Flags().VisitAll(func(f *pflag.Flag) { cmd.Flags().VisitAll(func(f *pflag.Flag) {
@ -359,7 +358,6 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
hideFlagIf(f, buildKitDisabled, "buildkit") hideFlagIf(f, buildKitDisabled, "buildkit")
hideFlagIf(f, buildKitEnabled, "no-buildkit") hideFlagIf(f, buildKitEnabled, "no-buildkit")
hideFlagIf(f, notExperimental, "experimental") hideFlagIf(f, notExperimental, "experimental")
hideFlagIf(f, notExperimentalCLI, "experimentalCLI")
hideFlagIf(f, notOSType, "ostype") hideFlagIf(f, notOSType, "ostype")
hideFlagIf(f, versionOlderThan, "version") hideFlagIf(f, versionOlderThan, "version")
}) })
@ -368,7 +366,6 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
hideSubcommandIf(subcmd, buildKitDisabled, "buildkit") hideSubcommandIf(subcmd, buildKitDisabled, "buildkit")
hideSubcommandIf(subcmd, buildKitEnabled, "no-buildkit") hideSubcommandIf(subcmd, buildKitEnabled, "no-buildkit")
hideSubcommandIf(subcmd, notExperimental, "experimental") hideSubcommandIf(subcmd, notExperimental, "experimental")
hideSubcommandIf(subcmd, notExperimentalCLI, "experimentalCLI")
hideSubcommandIf(subcmd, notOSType, "ostype") hideSubcommandIf(subcmd, notOSType, "ostype")
hideSubcommandIf(subcmd, versionOlderThan, "version") hideSubcommandIf(subcmd, versionOlderThan, "version")
} }
@ -417,9 +414,6 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
if _, ok := f.Annotations["experimental"]; ok && !details.ServerInfo().HasExperimental { if _, ok := f.Annotations["experimental"]; ok && !details.ServerInfo().HasExperimental {
errs = append(errs, fmt.Sprintf(`"--%s" is only supported on a Docker daemon with experimental features enabled`, f.Name)) errs = append(errs, fmt.Sprintf(`"--%s" is only supported on a Docker daemon with experimental features enabled`, f.Name))
} }
if _, ok := f.Annotations["experimentalCLI"]; ok && !details.ClientInfo().HasExperimental {
errs = append(errs, fmt.Sprintf(`"--%s" is only supported on a Docker cli with experimental cli features enabled`, f.Name))
}
// buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case // buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case
}) })
if len(errs) > 0 { if len(errs) > 0 {
@ -441,9 +435,6 @@ func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error {
if _, ok := curr.Annotations["experimental"]; ok && !details.ServerInfo().HasExperimental { if _, ok := curr.Annotations["experimental"]; ok && !details.ServerInfo().HasExperimental {
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath()) return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
} }
if _, ok := curr.Annotations["experimentalCLI"]; ok && !details.ClientInfo().HasExperimental {
return fmt.Errorf("%s is only supported on a Docker cli with experimental cli features enabled", cmd.CommandPath())
}
} }
return nil return nil
} }

View File

@ -66,7 +66,6 @@ by the `docker` command line:
* `DOCKER_API_VERSION` The API version to use (e.g. `1.19`) * `DOCKER_API_VERSION` The API version to use (e.g. `1.19`)
* `DOCKER_CONFIG` The location of your client configuration files. * `DOCKER_CONFIG` The location of your client configuration files.
* `DOCKER_CLI_EXPERIMENTAL` Enable experimental features for the cli (e.g. `enabled` or `disabled`)
* `DOCKER_HOST` Daemon socket to connect to. * `DOCKER_HOST` Daemon socket to connect to.
* `DOCKER_STACK_ORCHESTRATOR` Configure the default orchestrator to use when using `docker stack` management commands. * `DOCKER_STACK_ORCHESTRATOR` Configure the default orchestrator to use when using `docker stack` management commands.
* `DOCKER_CONTENT_TRUST` When set Docker uses notary to sign and verify images. * `DOCKER_CONTENT_TRUST` When set Docker uses notary to sign and verify images.
@ -317,27 +316,8 @@ Following is a sample `config.json` file:
### Experimental features ### Experimental features
Experimental features provide early access to future product functionality. Experimental features provide early access to future product functionality.
These features are intended only for testing and feedback as they may change These features are intended for testing and feedback, and they may change
between releases without warning or can be removed entirely from a future between releases without warning or can be removed from a future release.
release.
> Experimental features must not be used in production environments.
{: .warning }
To enable experimental features, edit the `config.json` file and set
`experimental` to `enabled`. The example below enables experimental features
in a `config.json` file that already enables a debug feature.
```json
{
"experimental": "enabled",
"debug": true
}
```
You can also enable experimental features from the Docker Desktop menu. See the
[Docker Desktop Getting Started page](https://docs.docker.com/docker-for-mac#experimental-features)
for more information.
### Notary ### Notary

View File

@ -24,18 +24,18 @@ const (
) )
// GetStackAPIVersion returns the most appropriate stack API version installed. // GetStackAPIVersion returns the most appropriate stack API version installed.
func GetStackAPIVersion(serverGroups discovery.ServerGroupsInterface, experimental bool) (StackVersion, error) { func GetStackAPIVersion(serverGroups discovery.ServerGroupsInterface) (StackVersion, error) {
groups, err := serverGroups.ServerGroups() groups, err := serverGroups.ServerGroups()
if err != nil { if err != nil {
return "", err return "", err
} }
return getAPIVersion(groups, experimental) return getAPIVersion(groups)
} }
func getAPIVersion(groups *metav1.APIGroupList, experimental bool) (StackVersion, error) { func getAPIVersion(groups *metav1.APIGroupList) (StackVersion, error) {
switch { switch {
case experimental && findVersion(apiv1alpha3.SchemeGroupVersion, groups.Groups): case findVersion(apiv1alpha3.SchemeGroupVersion, groups.Groups):
return StackAPIV1Alpha3, nil return StackAPIV1Alpha3, nil
case findVersion(apiv1beta2.SchemeGroupVersion, groups.Groups): case findVersion(apiv1beta2.SchemeGroupVersion, groups.Groups):
return StackAPIV1Beta2, nil return StackAPIV1Beta2, nil

View File

@ -12,20 +12,18 @@ func TestGetStackAPIVersion(t *testing.T) {
var tests = []struct { var tests = []struct {
description string description string
groups *metav1.APIGroupList groups *metav1.APIGroupList
experimental bool
err bool err bool
expectedStack StackVersion expectedStack StackVersion
}{ }{
{"no stack api", makeGroups(), false, true, ""}, {"no stack api", makeGroups(), true, ""},
{"v1beta1", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1"}}), false, false, StackAPIV1Beta1}, {"v1beta1", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1"}}), false, StackAPIV1Beta1},
{"v1beta2", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta2"}}), false, false, StackAPIV1Beta2}, {"v1beta2", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta2"}}), false, StackAPIV1Beta2},
{"most recent has precedence", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1", "v1beta2"}}), false, false, StackAPIV1Beta2}, {"most recent has precedence", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1", "v1beta2"}}), false, StackAPIV1Beta2},
{"most recent has precedence", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1", "v1beta2", "v1alpha3"}}), false, false, StackAPIV1Beta2}, {"most recent has precedence", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1", "v1beta2", "v1alpha3"}}), false, StackAPIV1Alpha3},
{"most recent has precedence", makeGroups(groupVersion{"compose.docker.com", []string{"v1beta1", "v1beta2", "v1alpha3"}}), true, false, StackAPIV1Alpha3},
} }
for _, test := range tests { for _, test := range tests {
version, err := getAPIVersion(test.groups, test.experimental) version, err := getAPIVersion(test.groups)
if test.err { if test.err {
assert.ErrorContains(t, err, "") assert.ErrorContains(t, err, "")
} else { } else {