Skip to content

Commit 2ed0d99

Browse files
thaJeztahaustinvazquez
authored andcommitted
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]> (cherry picked from commit ec912e5) Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent cbeddb1 commit 2ed0d99

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"
@@ -118,8 +119,8 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
118119
p.Err = wrapAsPluginError(err, "invalid metadata")
119120
return p, nil
120121
}
121-
if p.Metadata.SchemaVersion != "0.1.0" {
122-
p.Err = newPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
122+
if err := validateSchemaVersion(p.Metadata.SchemaVersion); err != nil {
123+
p.Err = &pluginError{cause: err}
123124
return p, nil
124125
}
125126
if p.Metadata.Vendor == "" {
@@ -129,6 +130,31 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
129130
return p, nil
130131
}
131132

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

0 commit comments

Comments
 (0)