Skip to content

Instantly share code, notes, and snippets.

@nathanleclaire
Last active August 29, 2015 14:15
Show Gist options
  • Save nathanleclaire/c4a3ecfe45a53c29d7d0 to your computer and use it in GitHub Desktop.
Save nathanleclaire/c4a3ecfe45a53c29d7d0 to your computer and use it in GitHub Desktop.
A proposal to add configuration to Docker

docker config proposal

Abstract

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.

Motivation

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.

Potential use cases

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's user.name and user.email fields.

CLI

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.

Implementation

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.

Tradeoffs and Design Questions

  • 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment