This proposal introduces a new command, docker config
, which can be used to get and set configuration values from the Docker command line client. Eventually these may be used to modify the program's default behavior. This proposal is a summary of myself and @duglin's discussion about solving this.
For a variety of reasons the Docker community seems to desire a way to store, set, and access options which dictate the manner in which Docker should behave. For instance, if the user wants to control the host which the Docker client executes commands against, they can set DOCKER_HOST
or -H
, but they have no way to persist this value out of the box or allow other programs to change them seamlessly. Docker configuration would allow users to do so,
There is the .dockercfg
file, but this file is so tied up in the logic related to the registry (e.g. https://github.com/docker/docker/blob/9054e4dca3320ec769a02aed8c19aac577688992/api/client/commands.go#L270) that its name is somewhat of a misnomer, and using it for anything other than what it was originally intended for is painful. It might more accurately be called .docker-registry-auth
or so on, and so we propose adding a new method of configuration that the registry logic could eventually use, rather than vice versa.
To be clear, this will be only for client-side configuration, though we could discuss expanding the idea(s) to the server side if there is interest.
Additionally, having a "canonical" configration implementation in Docker could set a precedent for projects such as Machine and Swarm, who will likely have a simliar use for the package but won't / shouldn't re-roll their own implementation from scratch. Instead, they could just use Docker's code with a different backend or simply a different file, and they could be on their merry way.
This could be used to:
- Create a configuration hierarchy which allows users to set global defaults and then override selectively them using environment variables and/or command line flags.
- Specify additional parameters or headers to be sent with Docker API requests
- Store data which may be useful in the future, such as
git
'suser.name
anduser.email
fields.
Some inspiration will be taken from git
's configuration interface. Configuration is loosely grouped into various categories and values which are nested using the .
character.
To set a value:
$ docker config Core.Debug true
To access an existing configuration value:
$ docker config Core.Debug
true
To dump the whole configuration as JSON:
$ docker config --dump
{
"Core": {
"Debug": true
},
"Host": "boot2docker:2376",
"TlsVerify": "true"
...etc.
}
Let us know if you have any ideas or concerns about the CLI - it's still very much a work in progress.
Very specific implementation details will be avoided in this proposal, but the general idea is this:
There will be a new pkg
directory: config
.
config
will contain a definition for a Config
interface:
type Config interface {
Dump() (string, error) // Return JSON string of all configuration information
GetString(string) (string, error) // Get or set a keyed parameter of type string
SetString(string, string) error
GetBool(string) (bool, error) // Get or set a keyed parameter of type boolean
SetBool(string, bool) error
GetInt(string) (int, error) // Get or set a keyed parameter of type integer
SetInt(string, int) error
Load() error // Load pre-existing configuration information
Save() error // Persist any changes to configuration information
}
Example implementation:
package main
import (
"encoding/json"
"fmt"
"log"
"reflect"
"strconv"
)
// docker config Core.Debug true
// docker config Core.Host tcp://1.2.3.4:2376
// docker config Core.Tls true
// docker config Core.TlsVerify true
// docker config Run.Interactive true
// docker config Run.Tty true
// docker config Run.Rm true
// docker run ubuntu bash
// # the container is gone when we exit it!
type Config interface {
Get(key string) (string, error)
Set(key string, val string) error
Dump() (string, error)
Save() error
Load() error
}
type CoreOptions struct {
Host string
Tls string
TlsVerify string
}
func GetConfigBool(c Config, key string) (bool, error) {
val, err := c.Get(key)
if err != nil {
return false, err
}
str, err := strconv.ParseBool(val)
if err != nil {
return false, err
}
return str, nil
}
type PlaintextConfig struct {
Core *CoreOptions
}
func NewPlaintextConfig() *PlaintextConfig {
return &PlaintextConfig{
Core: &CoreOptions{
Host: "unix:///var/run/docker.sock",
TlsVerify: "false",
Tls: "false",
},
}
}
func (p *PlaintextConfig) Get(key string) (string, error) {
var (
mark reflect.Value
)
r := reflect.ValueOf(key)
f := reflect.Indirect(r).FieldByName(key)
return f.String(), nil
}
func (p *PlaintextConfig) Set(key string, val string) error {
f := reflect.ValueOf(p).Elem().FieldByName(key)
// todo: check if the field CanSet() == false and so on
f.SetString(val)
return nil
}
func (p *PlaintextConfig) Dump() (string, error) {
b, err := json.Marshal(p)
if err != nil {
log.Fatal(err)
}
return string(b), nil
}
func (p *PlaintextConfig) Save() error {
return nil
}
func (p *PlaintextConfig) Load() error {
return nil
}
func main() {
config := NewPlaintextConfig()
host, err := config.Get("Core.Host")
if err != nil {
log.Fatal(err)
}
fmt.Println(host)
tls, err := GetConfigBool(config, "Core.TlsVerify")
if err != nil {
log.Fatal(err)
}
fmt.Println(tls)
dump, err := config.Dump()
if err != nil {
log.Fatal(err)
}
fmt.Println(dump)
}
The basic idea would be to make the "back-end" for the configuration flexible, so that in addition to the starting point of a plaintext
driver which could define the JSON-backed client-side implementation, the groundwork would be laid for other ways of accessing configuration in the future. In particular, there could be some kind of secretstore
backend that could store sensitive configuration information securely.
- JSON is much harder to edit by hand than an INI-style configuration file, but INI-style files would require an additional parsing layer to be put in place as Go does not have built-in support for them. JSON also makes one-liners like
echo "foo=bar" >>~/.docker/myconfig
pretty impossible. - With the prototype(s) I've been working on, values always get stored as strings. They are accessed from code using the typed methods listed above, but how should getting and setting non-scalar values such as maps and arrays be handled? Should that be handled at all?
- As opposed to
git
, which makes a big distinction between "system", "global", and "directory"-level configuration, this will be only "global". Does anyone foresee that causing issues? Because we didn't see any particular reason to have project-level defaults other than "it might be nice", we decided it should probably be kept simple at first.
http://stackoverflow.com/questions/24333494/golang-reflection-on-embedded-structs