Skip to content

Commit ec912e5

Browse files
committed
cli-plugins/manager: allow schema-versions <= 2.0.0
The CLI currently hard-codes the schema-version for CLI plugins to "0.1.0", which doesn't allow us to expand the schema for plugins. As there's many plugins that we shipped already, we can't break compatibility until we reach 2.0.0, but we can expand the schema with non-breaking changes. This patch makes the validation more permissive to allow new schema versions <= 2.0.0. Note that existing CLIs will still invalidate such versions, so we cannot update the version until such CLIs are no longer expected to be used, but this patch lays the ground-work to open that option. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 8fbb70a commit ec912e5

File tree

2 files changed

+51
-5
lines changed

2 files changed

+51
-5
lines changed

cli-plugins/manager/candidate_test.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func TestValidateCandidate(t *testing.T) {
5555
// Either err or invalid may be non-empty, but not both (both can be empty for a good plugin).
5656
err string
5757
invalid string
58+
expVer string
5859
}{
5960
// Invalid cases.
6061
{
@@ -95,12 +96,17 @@ func TestValidateCandidate(t *testing.T) {
9596
{
9697
name: "empty schemaversion",
9798
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`},
98-
invalid: `plugin SchemaVersion "" is not valid`,
99+
invalid: `plugin SchemaVersion version cannot be empty`,
99100
},
100101
{
101102
name: "invalid schemaversion",
102103
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`},
103-
invalid: `plugin SchemaVersion "xyzzy" is not valid`,
104+
invalid: `plugin SchemaVersion "xyzzy" has wrong format: must be <major>.<minor>.<patch>`,
105+
},
106+
{
107+
name: "invalid schemaversion major",
108+
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "2.0.0"}`},
109+
invalid: `plugin SchemaVersion "2.0.0" is not supported: must be lower than 2.0.0`,
104110
},
105111
{
106112
name: "no vendor",
@@ -117,11 +123,25 @@ func TestValidateCandidate(t *testing.T) {
117123
{
118124
name: "valid",
119125
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`},
126+
expVer: "0.1.0",
120127
},
121128
{
122129
// Including the deprecated "experimental" field should not break processing.
123130
name: "with legacy experimental",
124131
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`},
132+
expVer: "0.1.0",
133+
},
134+
{
135+
// note that this may not be supported by older CLIs
136+
name: "new minor schema version",
137+
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.2.0", "Vendor": "e2e-testing"}`},
138+
expVer: "0.2.0",
139+
},
140+
{
141+
// note that this may not be supported by older CLIs
142+
name: "new major schema version",
143+
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "1.0.0", "Vendor": "e2e-testing"}`},
144+
expVer: "1.0.0",
125145
},
126146
} {
127147
t.Run(tc.name, func(t *testing.T) {
@@ -136,7 +156,7 @@ func TestValidateCandidate(t *testing.T) {
136156
default:
137157
assert.NilError(t, err)
138158
assert.Equal(t, metadata.NamePrefix+p.Name, goodPluginName)
139-
assert.Equal(t, p.SchemaVersion, "0.1.0")
159+
assert.Equal(t, p.SchemaVersion, tc.expVer)
140160
assert.Equal(t, p.Vendor, "e2e-testing")
141161
}
142162
})

cli-plugins/manager/plugin.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"os/exec"
1111
"path/filepath"
12+
"strconv"
1213
"strings"
1314

1415
"github.com/docker/cli/cli-plugins/metadata"
@@ -115,8 +116,8 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
115116
p.Err = wrapAsPluginError(err, "invalid metadata")
116117
return p, nil
117118
}
118-
if p.Metadata.SchemaVersion != "0.1.0" {
119-
p.Err = newPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
119+
if err := validateSchemaVersion(p.Metadata.SchemaVersion); err != nil {
120+
p.Err = &pluginError{cause: err}
120121
return p, nil
121122
}
122123
if p.Metadata.Vendor == "" {
@@ -126,6 +127,31 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
126127
return p, nil
127128
}
128129

130+
// validateSchemaVersion validates if the plugin's schemaVersion is supported.
131+
//
132+
// The current schema-version is "0.1.0", but we don't want to break compatibility
133+
// until v2.0.0 of the schema version. Check for the major version to be < 2.0.0.
134+
//
135+
// Note that CLI versions before 28.4.1 may not support these versions as they were
136+
// hard-coded to only accept "0.1.0".
137+
func validateSchemaVersion(version string) error {
138+
if version == "0.1.0" {
139+
return nil
140+
}
141+
if version == "" {
142+
return errors.New("plugin SchemaVersion version cannot be empty")
143+
}
144+
major, _, ok := strings.Cut(version, ".")
145+
majorVersion, err := strconv.Atoi(major)
146+
if !ok || err != nil {
147+
return fmt.Errorf("plugin SchemaVersion %q has wrong format: must be <major>.<minor>.<patch>", version)
148+
}
149+
if majorVersion > 1 {
150+
return fmt.Errorf("plugin SchemaVersion %q is not supported: must be lower than 2.0.0", version)
151+
}
152+
return nil
153+
}
154+
129155
// RunHook executes the plugin's hooks command
130156
// and returns its unprocessed output.
131157
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {

0 commit comments

Comments
 (0)