diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index 656184993f..338c8db780 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -49,6 +49,7 @@ type ConfigFile struct { Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"` CurrentContext string `json:"currentContext,omitempty"` CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"` + Plugins map[string]json.RawMessage `json:"plugins,omitempty"` } // ProxyConfig contains proxy configuration settings @@ -70,6 +71,7 @@ func New(fn string) *ConfigFile { AuthConfigs: make(map[string]types.AuthConfig), HTTPHeaders: make(map[string]string), Filename: fn, + Plugins: make(map[string]json.RawMessage), } } diff --git a/cli/config/configfile/file_test.go b/cli/config/configfile/file_test.go index 5c21e8c426..badfa047d3 100644 --- a/cli/config/configfile/file_test.go +++ b/cli/config/configfile/file_test.go @@ -1,6 +1,8 @@ package configfile import ( + "bytes" + "encoding/json" "io/ioutil" "os" "testing" @@ -9,6 +11,7 @@ import ( "github.com/docker/cli/cli/config/types" "gotest.tools/assert" is "gotest.tools/assert/cmp" + "gotest.tools/golden" ) func TestEncodeAuth(t *testing.T) { @@ -429,3 +432,64 @@ func TestSave(t *testing.T) { assert.NilError(t, err) assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}")) } + +func TestPluginConfig(t *testing.T) { + configFile := New("test-plugin") + defer os.Remove("test-plugin") + + type PluginConfig1 struct { + Data1 string `json:"data1"` + Data2 int `json:"data2"` + } + type PluginConfig2 struct { + Data3 string `json:"data3"` + } + p1 := PluginConfig1{ + Data1: "some string", + Data2: 42, + } + p2 := PluginConfig2{ + Data3: "some other string", + } + + plugin1, err := json.MarshalIndent(p1, "", "\t") + assert.NilError(t, err) + configFile.Plugins["plugin1"] = plugin1 + + plugin2, err := json.MarshalIndent(p2, "", "\t") + assert.NilError(t, err) + configFile.Plugins["plugin2"] = plugin2 + + // Save a config file with some plugin config + err = configFile.Save() + assert.NilError(t, err) + + // Read it back and check it has the expected content + cfg, err := ioutil.ReadFile("test-plugin") + assert.NilError(t, err) + golden.Assert(t, string(cfg), "plugin-config.golden") + + // Load it, resave and check again that the content is + // preserved through a load/save cycle. + configFile2 := New("test-plugin2") + defer os.Remove("test-plugin2") + assert.NilError(t, configFile2.LoadFromReader(bytes.NewReader(cfg))) + err = configFile2.Save() + assert.NilError(t, err) + cfg, err = ioutil.ReadFile("test-plugin2") + assert.NilError(t, err) + golden.Assert(t, string(cfg), "plugin-config.golden") + + // Check that the contents was retained properly + var p1bis PluginConfig1 + assert.Assert(t, is.Contains(configFile2.Plugins, "plugin1")) + err = json.Unmarshal(configFile2.Plugins["plugin1"], &p1bis) + assert.NilError(t, err) + assert.DeepEqual(t, p1, p1bis) + + var p2bis PluginConfig2 + assert.Assert(t, is.Contains(configFile2.Plugins, "plugin2")) + err = json.Unmarshal(configFile2.Plugins["plugin2"], &p2bis) + assert.NilError(t, err) + assert.DeepEqual(t, p2, p2bis) +} diff --git a/cli/config/configfile/testdata/plugin-config.golden b/cli/config/configfile/testdata/plugin-config.golden new file mode 100644 index 0000000000..45c5d11e6d --- /dev/null +++ b/cli/config/configfile/testdata/plugin-config.golden @@ -0,0 +1,12 @@ +{ + "auths": {}, + "plugins": { + "plugin1": { + "data1": "some string", + "data2": 42 + }, + "plugin2": { + "data3": "some other string" + } + } +} \ No newline at end of file diff --git a/docs/extend/cli_plugins.md b/docs/extend/cli_plugins.md index c47d93809e..4721bafcd1 100644 --- a/docs/extend/cli_plugins.md +++ b/docs/extend/cli_plugins.md @@ -75,6 +75,18 @@ A plugin is required to support all of the global options of the top-level CLI, i.e. those listed by `man docker 1` with the exception of `-v`. +## Configuration + +Plugins are expected to make use of existing global configuration +where it makes sense and likewise to consider extending the global +configuration (by patching `docker/cli` to add new fields) where that +is sensible. + +Where plugins unavoidably require specific configuration the +`.plugins.«name»` key in the global `config.json` is reserved for +their use. However the preference should be for shared/global +configuration whenever that makes sense. + ## Connecting to the docker engine For consistency plugins should prefer to dial the engine by using the