Merge pull request #29866 from vieux/1.13.0-rc5-cherrpicks

1.13.0 rc5 cherrypicks
This commit is contained in:
Tibor Vass 2017-01-04 11:04:22 -08:00 committed by GitHub
commit 1afd41e4f7
44 changed files with 626 additions and 356 deletions

View file

@ -1497,74 +1497,39 @@ definitions:
type: "string"
example:
Id: "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078"
Name: "tiborvass/no-remove"
Name: "tiborvass/sample-volume-plugin"
Tag: "latest"
Active: true
Config:
Mounts:
- Name: ""
Description: ""
Settable: null
Source: "/data"
Destination: "/data"
Type: "bind"
Options:
- "shared"
- "rbind"
- Name: ""
Description: ""
Settable: null
Source: null
Destination: "/foobar"
Type: "tmpfs"
Options: null
Settings:
Env:
- "DEBUG=1"
- "DEBUG=0"
Args: null
Devices: null
Manifest:
ManifestVersion: "v0"
Description: "A test plugin for Docker"
Config:
Description: "A sample volume plugin for Docker"
Documentation: "https://docs.docker.com/engine/extend/plugins/"
Interface:
Types:
- "docker.volumedriver/1.0"
Socket: "plugins.sock"
Entrypoint:
- "plugin-no-remove"
- "/usr/bin/sample-volume-plugin"
- "/data"
WorkDir: ""
User: {}
Network:
Type: "host"
Capabilities: null
Mounts:
- Name: ""
Description: ""
Settable: null
Source: "/data"
Destination: "/data"
Type: "bind"
Options:
- "shared"
- "rbind"
- Name: ""
Description: ""
Settable: null
Source: null
Destination: "/foobar"
Type: "tmpfs"
Options: null
Devices:
- Name: "device"
Description: "a host device to mount"
Settable: null
Path: "/dev/cpu_dma_latency"
Type: ""
Linux:
Capabilities: null
DeviceCreation: false
Devices: null
Mounts: null
PropagatedMount: "/data"
Env:
- Name: "DEBUG"
Description: "If set, prints debug messages"
Settable: null
Value: "1"
Value: "0"
Args:
Name: "args"
Description: "command line arguments"
@ -6366,74 +6331,39 @@ paths:
$ref: "#/definitions/Plugin"
example:
- Id: "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078"
Name: "tiborvass/no-remove"
Name: "tiborvass/sample-volume-plugin"
Tag: "latest"
Active: true
Config:
Mounts:
- Name: ""
Description: ""
Settable: null
Source: "/data"
Destination: "/data"
Type: "bind"
Options:
- "shared"
- "rbind"
- Name: ""
Description: ""
Settable: null
Source: null
Destination: "/foobar"
Type: "tmpfs"
Options: null
Settings:
Env:
- "DEBUG=1"
- "DEBUG=0"
Args: null
Devices: null
Manifest:
ManifestVersion: "v0"
Description: "A test plugin for Docker"
Config:
Description: "A sample volume plugin for Docker"
Documentation: "https://docs.docker.com/engine/extend/plugins/"
Interface:
Types:
- "docker.volumedriver/1.0"
Socket: "plugins.sock"
Entrypoint:
- "plugin-no-remove"
- "/usr/bin/sample-volume-plugin"
- "/data"
WorkDir: ""
User: {}
Network:
Type: "host"
Capabilities: null
Mounts:
- Name: ""
Description: ""
Settable: null
Source: "/data"
Destination: "/data"
Type: "bind"
Options:
- "shared"
- "rbind"
- Name: ""
Description: ""
Settable: null
Source: null
Destination: "/foobar"
Type: "tmpfs"
Options: null
Devices:
- Name: "device"
Description: "a host device to mount"
Settable: null
Path: "/dev/cpu_dma_latency"
Type: ""
Linux:
Capabilities: null
DeviceCreation: false
Devices: null
Mounts: null
PropagatedMount: "/data"
Env:
- Name: "DEBUG"
Description: "If set, prints debug messages"
Settable: null
Value: "1"
Value: "0"
Args:
Name: "args"
Description: "command line arguments"
@ -6582,6 +6512,7 @@ paths:
required: true
type: "string"
tags: ["Plugin"]
/plugins/{name}:
delete:
summary: "Remove a plugin"
operationId: "PluginDelete"

View file

@ -297,17 +297,17 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str
}
b.runConfig.Image = b.image
cmd := b.runConfig.Cmd
b.runConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) WORKDIR %s", b.runConfig.WorkingDir)))
defer func(cmd strslice.StrSlice) { b.runConfig.Cmd = cmd }(cmd)
if hit, err := b.probeCache(); err != nil {
return err
} else if hit {
return nil
}
container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: b.runConfig})
// Actually copy the struct
workdirConfig := *b.runConfig
workdirConfig.Cmd = strslice.StrSlice(append(getShell(b.runConfig), fmt.Sprintf("#(nop) WORKDIR %s", b.runConfig.WorkingDir)))
container, err := b.docker.ContainerCreate(types.ContainerCreateConfig{Config: &workdirConfig})
if err != nil {
return err
}

View file

@ -17,7 +17,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts inspectOptions
cmd := &cobra.Command{
Use: "inspect [OPTIONS] PLUGIN|ID [PLUGIN|ID...]",
Use: "inspect [OPTIONS] PLUGIN [PLUGIN...]",
Short: "Display detailed information on one or more plugins",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {

View file

@ -2,13 +2,14 @@ package secret
import (
"fmt"
"io"
"io/ioutil"
"os"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/system"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -16,6 +17,7 @@ import (
type createOptions struct {
name string
file string
labels opts.ListOpts
}
@ -26,7 +28,7 @@ func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
cmd := &cobra.Command{
Use: "create [OPTIONS] SECRET",
Short: "Create a secret using stdin as content",
Short: "Create a secret from a file or STDIN as content",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
createOpts.name = args[0]
@ -35,6 +37,7 @@ func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
}
flags := cmd.Flags()
flags.VarP(&createOpts.labels, "label", "l", "Secret labels")
flags.StringVarP(&createOpts.file, "file", "f", "", "Read from a file or STDIN ('-')")
return cmd
}
@ -43,9 +46,23 @@ func runSecretCreate(dockerCli *command.DockerCli, options createOptions) error
client := dockerCli.Client()
ctx := context.Background()
secretData, err := ioutil.ReadAll(os.Stdin)
if options.file == "" {
return fmt.Errorf("Please specify either a file name or STDIN ('-') with --file")
}
var in io.Reader = dockerCli.In()
if options.file != "-" {
file, err := system.OpenSequential(options.file)
if err != nil {
return err
}
in = file
defer file.Close()
}
secretData, err := ioutil.ReadAll(in)
if err != nil {
return fmt.Errorf("Error reading content from STDIN: %v", err)
return fmt.Errorf("Error reading content from %q: %v", options.file, err)
}
spec := swarm.SecretSpec{

View file

@ -6,7 +6,6 @@ import (
"golang.org/x/net/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/idresolver"
@ -16,7 +15,6 @@ import (
)
type psOptions struct {
all bool
filter opts.FilterOpt
noTrunc bool
namespace string
@ -36,7 +34,6 @@ func newPsCommand(dockerCli *command.DockerCli) *cobra.Command {
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.all, "all", "a", false, "Display all tasks")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
@ -51,10 +48,6 @@ func runPS(dockerCli *command.DockerCli, opts psOptions) error {
filter := opts.filter.Value()
filter.Add("label", labelNamespace+"="+opts.namespace)
if !opts.all && !filter.Include("desired-state") {
filter.Add("desired-state", string(swarm.TaskStateRunning))
filter.Add("desired-state", string(swarm.TaskStateAccepted))
}
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
if err != nil {

View file

@ -59,11 +59,15 @@ func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) {
// TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file")
commonOpts.TLSOptions = &tlsconfig.Options{}
commonOpts.TLSOptions = &tlsconfig.Options{
CAFile: filepath.Join(dockerCertPath, DefaultCaFile),
CertFile: filepath.Join(dockerCertPath, DefaultCertFile),
KeyFile: filepath.Join(dockerCertPath, DefaultKeyFile),
}
tlsOptions := commonOpts.TLSOptions
flags.StringVar(&tlsOptions.CAFile, "tlscacert", filepath.Join(dockerCertPath, DefaultCaFile), "Trust certs signed only by this CA")
flags.StringVar(&tlsOptions.CertFile, "tlscert", filepath.Join(dockerCertPath, DefaultCertFile), "Path to TLS certificate file")
flags.StringVar(&tlsOptions.KeyFile, "tlskey", filepath.Join(dockerCertPath, DefaultKeyFile), "Path to TLS key file")
flags.Var(opts.NewQuotedString(&tlsOptions.CAFile), "tlscacert", "Trust certs signed only by this CA")
flags.Var(opts.NewQuotedString(&tlsOptions.CertFile), "tlscert", "Path to TLS certificate file")
flags.Var(opts.NewQuotedString(&tlsOptions.KeyFile), "tlskey", "Path to TLS key file")
hostOpt := opts.NewNamedListOptsRef("hosts", &commonOpts.Hosts, opts.ValidateHost)
flags.VarP(hostOpt, "host", "H", "Daemon socket(s) to connect to")

42
cli/flags/common_test.go Normal file
View file

@ -0,0 +1,42 @@
package flags
import (
"path/filepath"
"testing"
"github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/testutil/assert"
"github.com/spf13/pflag"
)
func TestCommonOptionsInstallFlags(t *testing.T) {
flags := pflag.NewFlagSet("testing", pflag.ContinueOnError)
opts := NewCommonOptions()
opts.InstallFlags(flags)
err := flags.Parse([]string{
"--tlscacert=\"/foo/cafile\"",
"--tlscert=\"/foo/cert\"",
"--tlskey=\"/foo/key\"",
})
assert.NilError(t, err)
assert.Equal(t, opts.TLSOptions.CAFile, "/foo/cafile")
assert.Equal(t, opts.TLSOptions.CertFile, "/foo/cert")
assert.Equal(t, opts.TLSOptions.KeyFile, "/foo/key")
}
func defaultPath(filename string) string {
return filepath.Join(cliconfig.ConfigDir(), filename)
}
func TestCommonOptionsInstallFlagsWithDefaults(t *testing.T) {
flags := pflag.NewFlagSet("testing", pflag.ContinueOnError)
opts := NewCommonOptions()
opts.InstallFlags(flags)
err := flags.Parse([]string{})
assert.NilError(t, err)
assert.Equal(t, opts.TLSOptions.CAFile, defaultPath("ca.pem"))
assert.Equal(t, opts.TLSOptions.CertFile, defaultPath("cert.pem"))
assert.Equal(t, opts.TLSOptions.KeyFile, defaultPath("key.pem"))
}

View file

@ -22,17 +22,20 @@ func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (<-c
messages := make(chan events.Message)
errs := make(chan error, 1)
started := make(chan struct{})
go func() {
defer close(errs)
query, err := buildEventsQueryParams(cli.version, options)
if err != nil {
close(started)
errs <- err
return
}
resp, err := cli.get(ctx, "/events", query, nil)
if err != nil {
close(started)
errs <- err
return
}
@ -40,6 +43,7 @@ func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (<-c
decoder := json.NewDecoder(resp.body)
close(started)
for {
select {
case <-ctx.Done():
@ -61,6 +65,7 @@ func (cli *Client) Events(ctx context.Context, options types.EventsOptions) (<-c
}
}
}()
<-started
return messages, errs
}

View file

@ -3139,7 +3139,7 @@ _docker_node_ps() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve --no-trunc" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--filter -f --help --no-resolve --no-trunc" -- "$cur" ) )
;;
*)
__docker_complete_nodes_plus_self
@ -3223,10 +3223,13 @@ _docker_plugin_create() {
_docker_plugin_disable() {
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--force -f --help" -- "$cur" ) )
;;
*)
__docker_complete_plugins_installed
local counter=$(__docker_pos_first_nonflag)
if [ $cword -eq $counter ]; then
__docker_complete_plugins_installed
fi
;;
esac
}
@ -3243,7 +3246,10 @@ _docker_plugin_enable() {
COMPREPLY=( $( compgen -W "--help --timeout" -- "$cur" ) )
;;
*)
__docker_complete_plugins_installed
local counter=$(__docker_pos_first_nonflag '--timeout')
if [ $cword -eq $counter ]; then
__docker_complete_plugins_installed
fi
;;
esac
}
@ -3266,9 +3272,15 @@ _docker_plugin_inspect() {
}
_docker_plugin_install() {
case "$prev" in
--alias)
return
;;
esac
case "$cur" in
-*)
COMPREPLY=( $( compgen -W "--disable --grant-all-permissions--help" -- "$cur" ) )
COMPREPLY=( $( compgen -W "--alias --disable --grant-all-permissions --help" -- "$cur" ) )
;;
esac
}
@ -3955,7 +3967,6 @@ _docker() {
container
cp
create
daemon
diff
events
exec

View file

@ -65,11 +65,10 @@ func merge(userConf, imageConf *containertypes.Config) error {
if userConf.Labels == nil {
userConf.Labels = map[string]string{}
}
if imageConf.Labels != nil {
for l := range userConf.Labels {
imageConf.Labels[l] = userConf.Labels[l]
for l, v := range imageConf.Labels {
if _, ok := userConf.Labels[l]; !ok {
userConf.Labels[l] = v
}
userConf.Labels = imageConf.Labels
}
if len(userConf.Entrypoint) == 0 {

View file

@ -67,7 +67,9 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
}
// if Restart Policy changed, we need to update container monitor
container.UpdateMonitor(hostConfig.RestartPolicy)
if hostConfig.RestartPolicy.Name != "" {
container.UpdateMonitor(hostConfig.RestartPolicy)
}
// If container is not running, update hostConfig struct is enough,
// resources will be updated when the container is started again.

View file

@ -171,52 +171,49 @@ Config provides the base accessible fields for working with V0 plugin format
## Example Config
*Example showing the 'tiborvass/no-remove' plugin config.*
*Example showing the 'tiborvass/sample-volume-plugin' plugin config.*
```json
{
"description": "A test plugin for Docker",
"documentation": "https://docs.docker.com/engine/extend/plugins/",
"entrypoint": ["plugin-no-remove", "/data"],
"interface": {
"types": ["docker.volumedriver/1.0"],
"socket": "plugins.sock"
},
"network": {
"type": "host"
},
"mounts": [
{
"source": "/data",
"destination": "/data",
"type": "bind",
"options": ["shared", "rbind"]
},
{
"destination": "/foobar",
"type": "tmpfs"
}
],
"args": {
"name": "args",
"description": "command line arguments",
"value": []
},
"env": [
{
"name": "DEBUG",
"description": "If set, prints debug messages",
"value": "1"
}
],
"linux": {
"devices": [
{
"name": "device",
"description": "a host device to mount",
"path": "/dev/cpu_dma_latency"
}
]
}
"Args": {
"Description": "",
"Name": "",
"Settable": null,
"Value": null
},
"Description": "A sample volume plugin for Docker",
"Documentation": "https://docs.docker.com/engine/extend/plugins/",
"Entrypoint": [
"/usr/bin/sample-volume-plugin",
"/data"
],
"Env": [
{
"Description": "",
"Name": "DEBUG",
"Settable": [
"value"
],
"Value": "0"
}
],
"Interface": {
"Socket": "plugin.sock",
"Types": [
"docker.volumedriver/1.0"
]
},
"Linux": {
"Capabilities": null,
"DeviceCreation": false,
"Devices": null
},
"Mounts": null,
"Network": {
"Type": ""
},
"PropagatedMount": "/data",
"User": {},
"Workdir": ""
}
```

View file

@ -69,8 +69,8 @@ enabled, and use it to create a volume.
```bash
$ docker plugin ls
NAME TAG ENABLED
vieux/sshfs latest true
ID NAME TAG DESCRIPTION ENABLED
69553ca1d789 vieux/sshfs latest the `sshfs` plugin true
```
3. Create a volume using the plugin.

View file

@ -684,16 +684,18 @@ configuration file or using the `--add-runtime` command line argument.
The following is an example adding 2 runtimes via the configuration:
```json
"default-runtime": "runc",
"runtimes": {
"runc": {
"path": "runc"
},
"custom": {
"path": "/usr/local/bin/my-runc-replacement",
"runtimeArgs": [
"--debug"
]
{
"default-runtime": "runc",
"runtimes": {
"runc": {
"path": "runc"
},
"custom": {
"path": "/usr/local/bin/my-runc-replacement",
"runtimeArgs": [
"--debug"
]
}
}
}
```

View file

@ -194,8 +194,8 @@ relative to the current time on the client machine:
2015-12-23T21:38:25.119625123Z network connect 8b111217944ba0ba844a65b13efcd57dc494932ee2527577758f939315ba2c5b (name=test-event-network-local, container=b4be644031a3d90b400f88ab3d4bdf4dc23adb250e696b6328b85441abe2c54e, type=bridge)
$ docker events --filter 'type=plugin' (experimental)
2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
**Format:**

View file

@ -106,6 +106,7 @@ read the [`dockerd`](dockerd.md) reference page.
| [volume create](volume_create.md) | Creates a new volume where containers can consume and store data |
| [volume inspect](volume_inspect.md) | Display information about a volume |
| [volume ls](volume_ls.md) | Lists all the volumes Docker knows about |
| [volume prune](volume_prune.md) | Remove all unused volumes |
| [volume rm](volume_rm.md) | Remove one or more volumes |

View file

@ -22,7 +22,6 @@ Usage: docker node ps [OPTIONS] [NODE...]
List tasks running on one or more nodes, defaults to current node.
Options:
-a, --all Display all instances
-f, --filter value Filter output based on conditions provided
--help Print usage
--no-resolve Do not map IDs to Names

View file

@ -30,27 +30,27 @@ see [`docker plugin install`](plugin_install.md). Without the `-f` option,
a plugin that has references (eg, volumes, networks) cannot be disabled.
The following example shows that the `no-remove` plugin is installed
The following example shows that the `sample-volume-plugin` plugin is installed
and enabled:
```bash
$ docker plugin ls
NAME TAG DESCRIPTION ENABLED
tiborvass/no-remove latest A test plugin for Docker true
ID NAME TAG DESCRIPTION ENABLED
69553ca1d123 tiborvass/sample-volume-plugin latest A test plugin for Docker true
```
To disable the plugin, use the following command:
```bash
$ docker plugin disable tiborvass/no-remove
$ docker plugin disable tiborvass/sample-volume-plugin
tiborvass/no-remove
tiborvass/sample-volume-plugin
$ docker plugin ls
NAME TAG DESCRIPTION ENABLED
tiborvass/no-remove latest A test plugin for Docker false
ID NAME TAG DESCRIPTION ENABLED
69553ca1d123 tiborvass/sample-volume-plugin latest A test plugin for Docker false
```
## Related information

View file

@ -29,27 +29,27 @@ Enables a plugin. The plugin must be installed before it can be enabled,
see [`docker plugin install`](plugin_install.md).
The following example shows that the `no-remove` plugin is installed,
The following example shows that the `sample-volume-plugin` plugin is installed,
but disabled:
```bash
$ docker plugin ls
NAME TAG DESCRIPTION ENABLED
tiborvass/no-remove latest A test plugin for Docker false
ID NAME TAG DESCRIPTION ENABLED
69553ca1d123 tiborvass/sample-volume-plugin latest A test plugin for Docker false
```
To enable the plugin, use the following command:
```bash
$ docker plugin enable tiborvass/no-remove
$ docker plugin enable tiborvass/sample-volume-plugin
tiborvass/no-remove
tiborvass/sample-volume-plugin
$ docker plugin ls
NAME TAG DESCRIPTION ENABLED
tiborvass/no-remove latest A test plugin for Docker true
ID NAME TAG DESCRIPTION ENABLED
69553ca1d123 tiborvass/sample-volume-plugin latest A test plugin for Docker true
```
## Related information

View file

@ -16,7 +16,7 @@ keywords: "plugin, inspect"
# plugin inspect
```markdown
Usage: docker plugin inspect [OPTIONS] PLUGIN|ID [PLUGIN|ID...]
Usage: docker plugin inspect [OPTIONS] PLUGIN [PLUGIN...]
Display detailed information on one or more plugins
@ -31,12 +31,12 @@ in a JSON array.
Example output:
```bash
$ docker plugin inspect tiborvass/no-remove:latest
$ docker plugin inspect tiborvass/sample-volume-plugin:latest
```
```JSON
{
"Id": "8c74c978c434745c3ade82f1bc0acf38d04990eaf494fa507c16d9f1daa99c21",
"Name": "tiborvass/no-remove:latest",
"Name": "tiborvass/sample-volume-plugin:latest",
"Enabled": true,
"Config": {
"Mounts": [
@ -79,7 +79,7 @@ $ docker plugin inspect tiborvass/no-remove:latest
"Socket": "plugins.sock"
},
"Entrypoint": [
"plugin-no-remove",
"plugin-sample-volume-plugin",
"/data"
],
"Workdir": "",
@ -143,7 +143,7 @@ $ docker plugin inspect tiborvass/no-remove:latest
```bash
$ docker plugin inspect -f '{{.Id}}' tiborvass/no-remove:latest
$ docker plugin inspect -f '{{.Id}}' tiborvass/sample-volume-plugin:latest
```
```
8c74c978c434745c3ade82f1bc0acf38d04990eaf494fa507c16d9f1daa99c21

View file

@ -33,20 +33,20 @@ the registry. Note that the minimum required registry version to distribute
plugins is 2.3.0
The following example installs `no-remove` plugin and [set](plugin_set.md) it's env variable
The following example installs `vieus/sshfs` plugin and [set](plugin_set.md) it's env variable
`DEBUG` to 1. Install consists of pulling the plugin from Docker Hub, prompting
the user to accept the list of privileges that the plugin needs, settings parameters
and enabling the plugin.
```bash
$ docker plugin install tiborvass/no-remove DEBUG=1
$ docker plugin install vieux/sshfs DEBUG=1
Plugin "tiborvass/no-remove" is requesting the following privileges:
Plugin "vieux/sshfs" is requesting the following privileges:
- network: [host]
- mount: [/data]
- device: [/dev/cpu_dma_latency]
- device: [/dev/fuse]
- capabilities: [CAP_SYS_ADMIN]
Do you grant the above permissions? [y/N] y
tiborvass/no-remove
vieux/sshfs
```
After the plugin is installed, it appears in the list of plugins:
@ -54,8 +54,8 @@ After the plugin is installed, it appears in the list of plugins:
```bash
$ docker plugin ls
NAME TAG DESCRIPTION ENABLED
tiborvass/no-remove latest A test plugin for Docker true
ID NAME TAG DESCRIPTION ENABLED
69553ca1d123 vieux/sshfs latest sshFS plugin for Docker true
```
## Related information

View file

@ -36,8 +36,8 @@ Example output:
```bash
$ docker plugin ls
ID NAME TAG DESCRIPTION ENABLED
69553ca1d123 tiborvass/no-remove latest A test plugin for Docker true
ID NAME TAG DESCRIPTION ENABLED
69553ca1d123 tiborvass/sample-volume-plugin latest A test plugin for Docker true
```
## Related information

View file

@ -32,9 +32,8 @@ The following example shows how to push a sample `user/plugin`.
```bash
$ docker plugin ls
NAME TAG DESCRIPTION ENABLED
user/plugin latest A sample plugin for Docker false
ID NAME TAG DESCRIPTION ENABLED
69553ca1d456 user/plugin latest A sample plugin for Docker false
$ docker plugin push user/plugin
```

View file

@ -33,14 +33,14 @@ a plugin using the [`docker plugin disable`](plugin_disable.md) before removing
it (or use --force, use of force is not recommended, since it can affect
functioning of running containers using the plugin).
The following example disables and removes the `no-remove:latest` plugin;
The following example disables and removes the `sample-volume-plugin:latest` plugin;
```bash
$ docker plugin disable tiborvass/no-remove
tiborvass/no-remove
$ docker plugin disable tiborvass/sample-volume-plugin
tiborvass/sample-volume-plugin
$ docker plugin rm tiborvass/no-remove:latest
tiborvass/no-remove
$ docker plugin rm tiborvass/sample-volume-plugin:latest
tiborvass/sample-volume-plugin
```
## Related information

View file

@ -33,15 +33,15 @@ The settings currently supported are:
* args
The following example change the env variable `DEBUG` on the
`no-remove` plugin.
`sample-volume-plugin` plugin.
```bash
$ docker plugin inspect -f {{.Settings.Env}} tiborvass/no-remove
$ docker plugin inspect -f {{.Settings.Env}} tiborvass/sample-volume-plugin
[DEBUG=0]
$ docker plugin set tiborvass/no-remove DEBUG=1
$ docker plugin set tiborvass/sample-volume-plugin DEBUG=1
$ docker plugin inspect -f {{.Settings.Env}} tiborvass/no-remove
$ docker plugin inspect -f {{.Settings.Env}} tiborvass/sample-volume-plugin
[DEBUG=1]
```

View file

@ -16,15 +16,17 @@ keywords: ["secret, create"]
# secret create
```Markdown
Usage: docker secret create [OPTIONS] SECRET
Usage: docker secret create [OPTIONS] SECRET
Create a secret from a file or STDIN as content
Create a secret using stdin as content
Options:
--help Print usage
-l, --label list Secret labels (default [])
-f, --file string Read from a file or STDIN ('-')
--help Print usage
-l, --label list Secret labels (default [])
```
Creates a secret using standard input for the secret content. You must run this
Creates a secret using standard input or from a file for the secret content. You must run this
command on a manager node.
## Examples
@ -32,7 +34,18 @@ command on a manager node.
### Create a secret
```bash
$ cat secret.json | docker secret create secret.json
$ cat secret.json | docker secret create -f - secret.json
mhv17xfe3gh6xc4rij5orpfds
$ docker secret ls
ID NAME CREATED UPDATED SIZE
mhv17xfe3gh6xc4rij5orpfds secret.json 2016-10-27 23:25:43.909181089 +0000 UTC 2016-10-27 23:25:43.909181089 +0000 UTC 1679
```
### Create a secret with a file
```bash
$ docker secret create --file secret.in secret.json
mhv17xfe3gh6xc4rij5orpfds
$ docker secret ls
@ -43,7 +56,7 @@ mhv17xfe3gh6xc4rij5orpfds secret.json 2016-10-27 23:25:43.90918108
### Create a secret with labels
```bash
$ cat secret.json | docker secret create secret.json --label env=dev --label rev=20161102
$ cat secret.json | docker secret create secret.json -f - --label env=dev --label rev=20161102
jtn7g6aukl5ky7nr9gvwafoxh
$ docker secret inspect secret.json

View file

@ -21,10 +21,10 @@ Usage: docker stack ps [OPTIONS] STACK
List the tasks in the stack
Options:
-a, --all Display all tasks
-f, --filter value Filter output based on conditions provided
--no-resolve Do not map IDs to Names
--no-trunc Do not truncate output
-f, --filter filter Filter output based on conditions provided
--help Print usage
--no-resolve Do not map IDs to Names
--no-trunc Do not truncate output
```
Lists the tasks that are running as part of the specified stack. This

View file

@ -9,65 +9,26 @@ issues associated with it. If necessary, links are provided to additional
documentation on an issue. As an active Docker user and community member,
please feel free to provide any feedback on these features you wish.
## Install Docker experimental
## Use Docker experimental
Unlike the regular Docker binary, the experimental channels is built and
updated nightly on https://experimental.docker.com. From one day to the
next, new features may appear, while existing experimental features may be
refined or entirely removed.
Experimental features are now included in the standard Docker binaries as of
version 1.13.0.
For enabling experimental features, you need to start the Docker daemon with
`--experimental` flag.
You can also enable the daemon flag via `/etc/docker/daemon.json`. e.g.
1. Verify that you have `curl` installed.
```json
{
"experimental": true
}
```
$ which curl
Then make sure the experimental flag is enabled:
If `curl` isn't installed, install it after updating your manager:
$ sudo apt-get update
$ sudo apt-get install curl
2. Get the latest Docker package.
$ curl -sSL https://experimental.docker.com/ | sh
The system prompts you for your `sudo` password. Then, it downloads and
installs Docker and its dependencies.
>**Note**: If your company is behind a filtering proxy, you may find that the
>`apt-key`
>command fails for the Docker repo during installation. To work around this,
>add the key directly using the following:
>
> $ curl -sSL https://experimental.docker.com/gpg | sudo apt-key add -
3. Verify `docker` is installed correctly.
$ sudo docker run hello-world
This command downloads a test image and runs it in a container.
### Get the Linux binary
To download the latest experimental `docker` binary for Linux,
use the following URLs:
https://experimental.docker.com/builds/Linux/i386/docker-latest.tgz
https://experimental.docker.com/builds/Linux/x86_64/docker-latest.tgz
After downloading the appropriate binary, you can follow the instructions
[here](https://docs.docker.com/engine/installation/binaries/#/get-the-docker-engine-binaries) to run the `docker` daemon.
> **Note**
>
> 1) You can get the MD5 and SHA256 hashes by appending .md5 and .sha256 to the URLs respectively
>
> 2) You can get the compressed binaries by appending .tgz to the URLs
### Build an experimental binary
You can also build the experimental binary from the standard development environment by adding
`DOCKER_EXPERIMENTAL=1` to the environment where you run `make` to build Docker binaries. For example,
to build a Docker binary with the experimental features enabled:
$ DOCKER_EXPERIMENTAL=1 make binary
```bash
$ docker version -f '{{.Server.Experimental}}'
true
```
## Current experimental features

View file

@ -184,7 +184,7 @@ Function Validate-DCO($headCommit, $upstreamCommit) {
$usernameRegex='[a-zA-Z0-9][a-zA-Z0-9-]+'
$dcoPrefix="Signed-off-by:"
$dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \\(github: ($githubUsernameRegex)\\))?$"
$dcoRegex="^(Docker-DCO-1.1-)?$dcoPrefix ([^<]+) <([^<>@]+@[^<>]+)>( \\(github: ($usernameRegex)\\))?$"
$counts = Invoke-Expression "git diff --numstat $upstreamCommit...$headCommit"
if ($LASTEXITCODE -ne 0) { Throw "Failed git diff --numstat" }

View file

@ -7357,3 +7357,43 @@ func (s *DockerSuite) TestBuildWindowsEnvCaseInsensitive(c *check.C) {
c.Fatalf("Case insensitive environment variables on Windows failed. Got %s", res)
}
}
// Test case for 29667
func (s *DockerSuite) TestBuildWorkdirImageCmd(c *check.C) {
testRequires(c, DaemonIsLinux)
image := "testworkdirimagecmd"
dockerfile := `
FROM busybox
WORKDIR /foo/bar
`
out, err := buildImage(image, dockerfile, true)
c.Assert(err, checker.IsNil, check.Commentf("Output: %s", out))
out, _ = dockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", image)
c.Assert(strings.TrimSpace(out), checker.Equals, `["sh"]`)
image = "testworkdirlabelimagecmd"
dockerfile = `
FROM busybox
WORKDIR /foo/bar
LABEL a=b
`
out, err = buildImage(image, dockerfile, true)
c.Assert(err, checker.IsNil, check.Commentf("Output: %s", out))
out, _ = dockerCmd(c, "inspect", "--format", "{{ json .Config.Cmd }}", image)
c.Assert(strings.TrimSpace(out), checker.Equals, `["sh"]`)
}
// Test case for 28902/28090
func (s *DockerSuite) TestBuildWorkdirCmd(c *check.C) {
testRequires(c, DaemonIsLinux)
dockerFile := `
FROM golang:1.7-alpine
WORKDIR /
`
_, err := buildImage("testbuildworkdircmd", dockerFile, false)
c.Assert(err, checker.IsNil)
}

View file

@ -142,3 +142,16 @@ func (s *DockerSuite) TestCommitChange(c *check.C) {
}
}
}
func (s *DockerSuite) TestCommitChangeLabels(c *check.C) {
dockerCmd(c, "run", "--name", "test", "--label", "some=label", "busybox", "true")
imageID, _ := dockerCmd(c, "commit",
"--change", "LABEL some=label2",
"test", "test-commit")
imageID = strings.TrimSpace(imageID)
c.Assert(inspectField(c, imageID, "Config.Labels"), checker.Equals, "map[some:label2]")
// check that container labels didn't change
c.Assert(inspectField(c, "test", "Config.Labels"), checker.Equals, "map[some:label]")
}

View file

@ -54,6 +54,10 @@ func (s *DockerExternalGraphdriverSuite) SetUpTest(c *check.C) {
s.d = NewDaemon(c)
}
func (s *DockerExternalGraphdriverSuite) OnTimeout(c *check.C) {
s.d.DumpStackAndQuit()
}
func (s *DockerExternalGraphdriverSuite) TearDownTest(c *check.C) {
s.d.Stop()
s.ds.TearDownTest(c)

View file

@ -456,3 +456,11 @@ func (s *DockerSuite) TestInspectUnknownObject(c *check.C) {
c.Assert(out, checker.Contains, "Error: No such object: foobar")
c.Assert(err.Error(), checker.Contains, "Error: No such object: foobar")
}
func (s *DockerSuite) TestInpectInvalidReference(c *check.C) {
// This test should work on both Windows and Linux
out, _, err := dockerCmdWithError("inspect", "FooBar")
c.Assert(err, checker.NotNil)
c.Assert(out, checker.Contains, "Error: No such object: FooBar")
c.Assert(err.Error(), checker.Contains, "Error: No such object: FooBar")
}

View file

@ -3,6 +3,10 @@
package main
import (
"io/ioutil"
"os"
"strings"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/integration/checker"
"github.com/go-check/check"
@ -104,3 +108,33 @@ func (s *DockerSwarmSuite) TestSecretCreateResolve(c *check.C) {
c.Assert(out, checker.Not(checker.Contains), id)
c.Assert(out, checker.Not(checker.Contains), fake)
}
func (s *DockerSwarmSuite) TestSecretCreateWithFile(c *check.C) {
d := s.AddDaemon(c, true, true)
testFile, err := ioutil.TempFile("", "secretCreateTest")
c.Assert(err, checker.IsNil, check.Commentf("failed to create temporary file"))
defer os.Remove(testFile.Name())
testData := "TESTINGDATA"
_, err = testFile.Write([]byte(testData))
c.Assert(err, checker.IsNil, check.Commentf("failed to write to temporary file"))
testName := "test_secret"
out, err := d.Cmd("secret", "create", "--file", testFile.Name(), testName)
c.Assert(err, checker.IsNil)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "", check.Commentf(out))
id := strings.TrimSpace(out)
secret := d.getSecret(c, id)
c.Assert(secret.Spec.Name, checker.Equals, testName)
testName = "test_secret_2"
out, err = d.Cmd("secret", "create", testName, "-f", testFile.Name())
c.Assert(err, checker.IsNil)
c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "", check.Commentf(out))
id = strings.TrimSpace(out)
secret = d.getSecret(c, id)
c.Assert(secret.Spec.Name, checker.Equals, testName)
}

View file

@ -5,7 +5,10 @@ package main
import (
"encoding/json"
"fmt"
"github.com/kr/pty"
"os/exec"
"strings"
"time"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/integration/checker"
@ -250,3 +253,31 @@ func (s *DockerSuite) TestUpdateMemoryWithSwapMemory(c *check.C) {
dockerCmd(c, "update", "--memory", "800M", "--memory-swap", "1000M", name)
}
func (s *DockerSuite) TestUpdateNotAffectMonitorRestartPolicy(c *check.C) {
testRequires(c, DaemonIsLinux, cpuShare)
out, _ := dockerCmd(c, "run", "-tid", "--restart=always", "busybox", "sh")
id := strings.TrimSpace(string(out))
dockerCmd(c, "update", "--cpu-shares", "512", id)
cpty, tty, err := pty.Open()
c.Assert(err, checker.IsNil)
defer cpty.Close()
cmd := exec.Command(dockerBinary, "attach", id)
cmd.Stdin = tty
c.Assert(cmd.Start(), checker.IsNil)
defer cmd.Process.Kill()
_, err = cpty.Write([]byte("exit\n"))
c.Assert(err, checker.IsNil)
c.Assert(cmd.Wait(), checker.IsNil)
// container should restart again and keep running
err = waitInspect(id, "{{.RestartCount}}", "1", 30*time.Second)
c.Assert(err, checker.IsNil)
c.Assert(waitRun(id), checker.IsNil)
}

View file

@ -168,8 +168,8 @@ Lines. For information about JSON Lines, please refer to http://jsonlines.org/ .
2015-12-23T21:38:25.119625123Z network connect 8b111217944ba0ba844a65b13efcd57dc494932ee2527577758f939315ba2c5b (name=test-event-network-local, container=b4be644031a3d90b400f88ab3d4bdf4dc23adb250e696b6328b85441abe2c54e, type=bridge)
$ docker events --filter 'type=plugin' (experimental)
2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/no-remove:latest)
2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest)
# HISTORY

View file

@ -20,6 +20,7 @@ dockerd - Enable daemon mode
[**-D**|**--debug**]
[**--default-gateway**[=*DEFAULT-GATEWAY*]]
[**--default-gateway-v6**[=*DEFAULT-GATEWAY-V6*]]
[**--default-runtime**[=*runc*]]
[**--default-ulimit**[=*[]*]]
[**--disable-legacy-registry**]
[**--dns**[=*[]*]]
@ -84,7 +85,35 @@ following format.
# OPTIONS
**--add-runtime**=[]
Set additional OCI compatible runtime.
Runtimes can be registered with the daemon either via the
configuration file or using the `--add-runtime` command line argument.
The following is an example adding 2 runtimes via the configuration:
```json
{
"default-runtime": "runc",
"runtimes": {
"runc": {
"path": "runc"
},
"custom": {
"path": "/usr/local/bin/my-runc-replacement",
"runtimeArgs": [
"--debug"
]
}
}
}
```
This is the same example via the command line:
```bash
$ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-runc-replacement
```
**Note**: defining runtime arguments via the command line is not supported.
**--api-cors-header**=""
Set CORS headers in the Engine API. Default is cors disabled. Give urls like
@ -132,6 +161,9 @@ following format.
**--default-gateway-v6**=""
IPv6 address of the container default gateway
**--default-runtime**="runc"
Set default runtime if there're more than one specified by `--add-runtime`.
**--default-ulimit**=[]
Default ulimits for containers.

37
opts/quotedstring.go Normal file
View file

@ -0,0 +1,37 @@
package opts
// QuotedString is a string that may have extra quotes around the value. The
// quotes are stripped from the value.
type QuotedString struct {
value *string
}
// Set sets a new value
func (s *QuotedString) Set(val string) error {
*s.value = trimQuotes(val)
return nil
}
// Type returns the type of the value
func (s *QuotedString) Type() string {
return "string"
}
func (s *QuotedString) String() string {
return string(*s.value)
}
func trimQuotes(value string) string {
lastIndex := len(value) - 1
for _, char := range []byte{'\'', '"'} {
if value[0] == char && value[lastIndex] == char {
return value[1:lastIndex]
}
}
return value
}
// NewQuotedString returns a new quoted string option
func NewQuotedString(value *string) *QuotedString {
return &QuotedString{value: value}
}

28
opts/quotedstring_test.go Normal file
View file

@ -0,0 +1,28 @@
package opts
import (
"github.com/docker/docker/pkg/testutil/assert"
"testing"
)
func TestQuotedStringSetWithQuotes(t *testing.T) {
value := ""
qs := NewQuotedString(&value)
assert.NilError(t, qs.Set("\"something\""))
assert.Equal(t, qs.String(), "something")
assert.Equal(t, value, "something")
}
func TestQuotedStringSetWithMismatchedQuotes(t *testing.T) {
value := ""
qs := NewQuotedString(&value)
assert.NilError(t, qs.Set("\"something'"))
assert.Equal(t, qs.String(), "\"something'")
}
func TestQuotedStringSetWithNoQuotes(t *testing.T) {
value := ""
qs := NewQuotedString(&value)
assert.NilError(t, qs.Set("something"))
assert.Equal(t, qs.String(), "something")
}

View file

@ -0,0 +1,37 @@
package plugins
import (
"path/filepath"
"runtime"
"sync"
"testing"
"time"
)
// regression test for deadlock in handlers
func TestPluginAddHandler(t *testing.T) {
// make a plugin which is pre-activated
p := &Plugin{activateWait: sync.NewCond(&sync.Mutex{})}
p.Manifest = &Manifest{Implements: []string{"bananas"}}
storage.plugins["qwerty"] = p
testActive(t, p)
Handle("bananas", func(_ string, _ *Client) {})
testActive(t, p)
}
func testActive(t *testing.T, p *Plugin) {
done := make(chan struct{})
go func() {
p.waitActive()
close(done)
}()
select {
case <-time.After(100 * time.Millisecond):
_, f, l, _ := runtime.Caller(1)
t.Fatalf("%s:%d: deadlock in waitActive", filepath.Base(f), l)
case <-done:
}
}

View file

@ -70,12 +70,12 @@ type Plugin struct {
// Manifest of the plugin (see above)
Manifest *Manifest `json:"-"`
// error produced by activation
activateErr error
// specifies if the activation sequence is completed (not if it is successful or not)
activated bool
// wait for activation to finish
activateWait *sync.Cond
// error produced by activation
activateErr error
// keeps track of callback handlers run against this plugin
handlersRun bool
}
// BasePath returns the path to which all paths returned by the plugin are relative to.
@ -112,19 +112,51 @@ func NewLocalPlugin(name, addr string) *Plugin {
func (p *Plugin) activate() error {
p.activateWait.L.Lock()
if p.activated {
if p.activated() {
p.runHandlers()
p.activateWait.L.Unlock()
return p.activateErr
}
p.activateErr = p.activateWithLock()
p.activated = true
p.runHandlers()
p.activateWait.L.Unlock()
p.activateWait.Broadcast()
return p.activateErr
}
// runHandlers runs the registered handlers for the implemented plugin types
// This should only be run after activation, and while the activation lock is held.
func (p *Plugin) runHandlers() {
if !p.activated() {
return
}
handlers.RLock()
if !p.handlersRun {
for _, iface := range p.Manifest.Implements {
hdlrs, handled := handlers.extpointHandlers[iface]
if !handled {
continue
}
for _, handler := range hdlrs {
handler(p.name, p.client)
}
}
p.handlersRun = true
}
handlers.RUnlock()
}
// activated returns if the plugin has already been activated.
// This should only be called with the activation lock held
func (p *Plugin) activated() bool {
return p.Manifest != nil
}
func (p *Plugin) activateWithLock() error {
c, err := NewClient(p.Addr, p.TLSConfig)
if err != nil {
@ -138,24 +170,12 @@ func (p *Plugin) activateWithLock() error {
}
p.Manifest = m
handlers.RLock()
for _, iface := range m.Implements {
hdlrs, handled := handlers.extpointHandlers[iface]
if !handled {
continue
}
for _, handler := range hdlrs {
handler(p.name, p.client)
}
}
handlers.RUnlock()
return nil
}
func (p *Plugin) waitActive() error {
p.activateWait.L.Lock()
for !p.activated {
for !p.activated() {
p.activateWait.Wait()
}
p.activateWait.L.Unlock()
@ -163,7 +183,7 @@ func (p *Plugin) waitActive() error {
}
func (p *Plugin) implements(kind string) bool {
if err := p.waitActive(); err != nil {
if p.Manifest == nil {
return false
}
for _, driver := range p.Manifest.Implements {
@ -232,7 +252,7 @@ func Get(name, imp string) (*Plugin, error) {
if err != nil {
return nil, err
}
if pl.implements(imp) {
if err := pl.waitActive(); err == nil && pl.implements(imp) {
logrus.Debugf("%s implements: %s", name, imp)
return pl, nil
}
@ -249,9 +269,17 @@ func Handle(iface string, fn func(string, *Client)) {
hdlrs = append(hdlrs, fn)
handlers.extpointHandlers[iface] = hdlrs
storage.Lock()
for _, p := range storage.plugins {
p.activated = false
p.activateWait.L.Lock()
if p.activated() && p.implements(iface) {
p.handlersRun = false
}
p.activateWait.L.Unlock()
}
storage.Unlock()
handlers.Unlock()
}
@ -292,7 +320,7 @@ func GetAll(imp string) ([]*Plugin, error) {
logrus.Error(pl.err)
continue
}
if pl.pl.implements(imp) {
if err := pl.pl.waitActive(); err == nil && pl.pl.implements(imp) {
out = append(out, pl.pl)
}
}

View file

@ -58,7 +58,7 @@ func (ps *Store) GetV2Plugin(refOrID string) (*v2.Plugin, error) {
func (ps *Store) validateName(name string) error {
for _, p := range ps.plugins {
if p.Name() == name {
return errors.Errorf("%v already exists", name)
return errors.Errorf("plugin %q already exists", name)
}
}
return nil
@ -232,7 +232,7 @@ func (ps *Store) resolvePluginID(idOrName string) (string, error) {
ref, err := reference.ParseNamed(idOrName)
if err != nil {
return "", errors.Wrapf(err, "failed to parse %v", idOrName)
return "", errors.WithStack(ErrNotFound(idOrName))
}
if _, ok := ref.(reference.Canonical); ok {
logrus.Warnf("canonical references cannot be resolved: %v", ref.String())

View file

@ -3,17 +3,13 @@ package store
import (
"encoding/json"
"github.com/Sirupsen/logrus"
"github.com/boltdb/bolt"
"github.com/pkg/errors"
)
var volumeBucketName = []byte("volumes")
type dbEntry struct {
Key []byte
Value []byte
}
type volumeMetadata struct {
Name string
Driver string
@ -67,12 +63,26 @@ func removeMeta(tx *bolt.Tx, name string) error {
return errors.Wrap(b.Delete([]byte(name)), "error removing volume metadata")
}
func listEntries(tx *bolt.Tx) []*dbEntry {
var entries []*dbEntry
// listMeta is used during restore to get the list of volume metadata
// from the on-disk database.
// Any errors that occur are only logged.
func listMeta(tx *bolt.Tx) []volumeMetadata {
var ls []volumeMetadata
b := tx.Bucket(volumeBucketName)
b.ForEach(func(k, v []byte) error {
entries = append(entries, &dbEntry{k, v})
if len(v) == 0 {
// don't try to unmarshal an empty value
return nil
}
var m volumeMetadata
if err := json.Unmarshal(v, &m); err != nil {
// Just log the error
logrus.Errorf("Error while reading volume metadata for volume %q: %v", string(k), err)
return nil
}
ls = append(ls, m)
return nil
})
return entries
return ls
}

View file

@ -1,7 +1,6 @@
package store
import (
"encoding/json"
"sync"
"github.com/Sirupsen/logrus"
@ -17,45 +16,38 @@ import (
// It does not probe the available drivers to find anything that may have been added
// out of band.
func (s *VolumeStore) restore() {
var entries []*dbEntry
var ls []volumeMetadata
s.db.View(func(tx *bolt.Tx) error {
entries = listEntries(tx)
ls = listMeta(tx)
return nil
})
chRemove := make(chan []byte, len(entries))
chRemove := make(chan *volumeMetadata, len(ls))
var wg sync.WaitGroup
for _, entry := range entries {
for _, meta := range ls {
wg.Add(1)
// this is potentially a very slow operation, so do it in a goroutine
go func(entry *dbEntry) {
go func(meta volumeMetadata) {
defer wg.Done()
var meta volumeMetadata
if len(entry.Value) != 0 {
if err := json.Unmarshal(entry.Value, &meta); err != nil {
logrus.Errorf("Error while reading volume metadata for volume %q: %v", string(entry.Key), err)
// don't return here, we can try with `getVolume` below
}
}
var v volume.Volume
var err error
if meta.Driver != "" {
v, err = lookupVolume(meta.Driver, string(entry.Key))
v, err = lookupVolume(meta.Driver, meta.Name)
if err != nil && err != errNoSuchVolume {
logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", string(entry.Key)).Warn("Error restoring volume")
logrus.WithError(err).WithField("driver", meta.Driver).WithField("volume", meta.Name).Warn("Error restoring volume")
return
}
if v == nil {
// doesn't exist in the driver, remove it from the db
chRemove <- entry.Key
chRemove <- &meta
return
}
} else {
v, err = s.getVolume(string(entry.Key))
v, err = s.getVolume(meta.Name)
if err != nil {
if err == errNoSuchVolume {
chRemove <- entry.Key
chRemove <- &meta
}
return
}
@ -75,15 +67,15 @@ func (s *VolumeStore) restore() {
s.labels[v.Name()] = meta.Labels
s.names[v.Name()] = v
s.globalLock.Unlock()
}(entry)
}(meta)
}
wg.Wait()
close(chRemove)
s.db.Update(func(tx *bolt.Tx) error {
for k := range chRemove {
if err := removeMeta(tx, string(k)); err != nil {
logrus.Warnf("Error removing stale entry from volume db: %v", err)
for meta := range chRemove {
if err := removeMeta(tx, meta.Name); err != nil {
logrus.WithField("volume", meta.Name).Warnf("Error removing stale entry from volume db: %v", err)
}
}
return nil