Merge pull request #23661 from tiborvass/cherry-picks-for-1.12.0-rc2
Cherry picks for 1.12.0 rc2
|
@ -214,7 +214,7 @@ be found.
|
|||
|
||||
### Misc
|
||||
|
||||
+ When saving linked images together with `docker save` a subsequent `docker load` will correctly restore their parent/child relationship ([#21385](https://github.com/docker/docker/pull/c))
|
||||
+ When saving linked images together with `docker save` a subsequent `docker load` will correctly restore their parent/child relationship ([#21385](https://github.com/docker/docker/pull/21385))
|
||||
+ Support for building the Docker cli for OpenBSD was added ([#21325](https://github.com/docker/docker/pull/21325))
|
||||
+ Labels can now be applied at network, volume and image creation ([#21270](https://github.com/docker/docker/pull/21270))
|
||||
* The `dockremap` is now created as a system user ([#21266](https://github.com/docker/docker/pull/21266))
|
||||
|
|
|
@ -161,7 +161,7 @@ RUN useradd --create-home --gid docker unprivilegeduser
|
|||
|
||||
VOLUME /var/lib/docker
|
||||
WORKDIR /go/src/github.com/docker/docker
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
||||
ENV DOCKER_BUILDTAGS apparmor selinux seccomp
|
||||
|
||||
# Let us use a .bashrc file
|
||||
RUN ln -sfv $PWD/.bashrc ~/.bashrc
|
||||
|
|
|
@ -4,8 +4,8 @@ package bundlefile
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Bundlefile stores the contents of a bundlefile
|
||||
|
@ -34,19 +34,28 @@ type Port struct {
|
|||
}
|
||||
|
||||
// LoadFile loads a bundlefile from a path to the file
|
||||
func LoadFile(path string) (*Bundlefile, error) {
|
||||
reader, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func LoadFile(reader io.Reader) (*Bundlefile, error) {
|
||||
bundlefile := &Bundlefile{}
|
||||
|
||||
if err := json.NewDecoder(reader).Decode(bundlefile); err != nil {
|
||||
decoder := json.NewDecoder(reader)
|
||||
if err := decoder.Decode(bundlefile); err != nil {
|
||||
switch jsonErr := err.(type) {
|
||||
case *json.SyntaxError:
|
||||
return nil, fmt.Errorf(
|
||||
"JSON syntax error at byte %v: %s",
|
||||
jsonErr.Offset,
|
||||
jsonErr.Error())
|
||||
case *json.UnmarshalTypeError:
|
||||
return nil, fmt.Errorf(
|
||||
"Unexpected type at byte %v. Expected %s but received %s.",
|
||||
jsonErr.Offset,
|
||||
jsonErr.Type,
|
||||
jsonErr.Value)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bundlefile, err
|
||||
return bundlefile, nil
|
||||
}
|
||||
|
||||
// Print writes the contents of the bundlefile to the output writer
|
||||
|
|
79
api/client/bundlefile/bundlefile_test.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
// +build experimental
|
||||
|
||||
package bundlefile
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
)
|
||||
|
||||
func TestLoadFileV01Success(t *testing.T) {
|
||||
reader := strings.NewReader(`{
|
||||
"Version": "0.1",
|
||||
"Services": {
|
||||
"redis": {
|
||||
"Image": "redis@sha256:4b24131101fa0117bcaa18ac37055fffd9176aa1a240392bb8ea85e0be50f2ce",
|
||||
"Networks": ["default"]
|
||||
},
|
||||
"web": {
|
||||
"Image": "dockercloud/hello-world@sha256:fe79a2cfbd17eefc344fb8419420808df95a1e22d93b7f621a7399fd1e9dca1d",
|
||||
"Networks": ["default"],
|
||||
"User": "web"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
bundle, err := LoadFile(reader)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, bundle.Version, "0.1")
|
||||
assert.Equal(t, len(bundle.Services), 2)
|
||||
}
|
||||
|
||||
func TestLoadFileSyntaxError(t *testing.T) {
|
||||
reader := strings.NewReader(`{
|
||||
"Version": "0.1",
|
||||
"Services": unquoted string
|
||||
}`)
|
||||
|
||||
_, err := LoadFile(reader)
|
||||
assert.Error(t, err, "syntax error at byte 37: invalid character 'u'")
|
||||
}
|
||||
|
||||
func TestLoadFileTypeError(t *testing.T) {
|
||||
reader := strings.NewReader(`{
|
||||
"Version": "0.1",
|
||||
"Services": {
|
||||
"web": {
|
||||
"Image": "redis",
|
||||
"Networks": "none"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
_, err := LoadFile(reader)
|
||||
assert.Error(t, err, "Unexpected type at byte 94. Expected []string but received string")
|
||||
}
|
||||
|
||||
func TestPrint(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
bundle := &Bundlefile{
|
||||
Version: "0.1",
|
||||
Services: map[string]Service{
|
||||
"web": {
|
||||
Image: "image",
|
||||
Command: []string{"echo", "something"},
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.NilError(t, Print(&buffer, bundle))
|
||||
output := buffer.String()
|
||||
assert.Contains(t, output, "\"Image\": \"image\"")
|
||||
assert.Contains(t, output,
|
||||
`"Command": [
|
||||
"echo",
|
||||
"something"
|
||||
]`)
|
||||
}
|
|
@ -41,7 +41,8 @@ func runRestart(dockerCli *client.DockerCli, opts *restartOptions) error {
|
|||
ctx := context.Background()
|
||||
var errs []string
|
||||
for _, name := range opts.containers {
|
||||
if err := dockerCli.Client().ContainerRestart(ctx, name, time.Duration(opts.nSeconds)*time.Second); err != nil {
|
||||
timeout := time.Duration(opts.nSeconds) * time.Second
|
||||
if err := dockerCli.Client().ContainerRestart(ctx, name, &timeout); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
} else {
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", name)
|
||||
|
|
|
@ -43,7 +43,8 @@ func runStop(dockerCli *client.DockerCli, opts *stopOptions) error {
|
|||
|
||||
var errs []string
|
||||
for _, container := range opts.containers {
|
||||
if err := dockerCli.Client().ContainerStop(ctx, container, time.Duration(opts.time)*time.Second); err != nil {
|
||||
timeout := time.Duration(opts.time) * time.Second
|
||||
if err := dockerCli.Client().ContainerStop(ctx, container, &timeout); err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
} else {
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container)
|
||||
|
|
|
@ -155,6 +155,10 @@ func (ctx ContainerContext) Write() {
|
|||
ctx.postformat(tmpl, &containerContext{})
|
||||
}
|
||||
|
||||
func isDangling(image types.Image) bool {
|
||||
return len(image.RepoTags) == 1 && image.RepoTags[0] == "<none>:<none>" && len(image.RepoDigests) == 1 && image.RepoDigests[0] == "<none>@<none>"
|
||||
}
|
||||
|
||||
func (ctx ImageContext) Write() {
|
||||
switch ctx.Format {
|
||||
case tableFormatKey:
|
||||
|
@ -200,42 +204,98 @@ virtual_size: {{.Size}}
|
|||
}
|
||||
|
||||
for _, image := range ctx.Images {
|
||||
images := []*imageContext{}
|
||||
if isDangling(image) {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: "<none>",
|
||||
tag: "<none>",
|
||||
digest: "<none>",
|
||||
})
|
||||
} else {
|
||||
repoTags := map[string][]string{}
|
||||
repoDigests := map[string][]string{}
|
||||
|
||||
repoTags := image.RepoTags
|
||||
repoDigests := image.RepoDigests
|
||||
|
||||
if len(repoTags) == 1 && repoTags[0] == "<none>:<none>" && len(repoDigests) == 1 && repoDigests[0] == "<none>@<none>" {
|
||||
// dangling image - clear out either repoTags or repoDigests so we only show it once below
|
||||
repoDigests = []string{}
|
||||
}
|
||||
// combine the tags and digests lists
|
||||
tagsAndDigests := append(repoTags, repoDigests...)
|
||||
for _, repoAndRef := range tagsAndDigests {
|
||||
repo := "<none>"
|
||||
tag := "<none>"
|
||||
digest := "<none>"
|
||||
|
||||
if !strings.HasPrefix(repoAndRef, "<none>") {
|
||||
ref, err := reference.ParseNamed(repoAndRef)
|
||||
for _, refString := range append(image.RepoTags) {
|
||||
ref, err := reference.ParseNamed(refString)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
repo = ref.Name()
|
||||
|
||||
switch x := ref.(type) {
|
||||
case reference.Canonical:
|
||||
digest = x.Digest().String()
|
||||
case reference.NamedTagged:
|
||||
tag = x.Tag()
|
||||
if nt, ok := ref.(reference.NamedTagged); ok {
|
||||
repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag())
|
||||
}
|
||||
}
|
||||
imageCtx := &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
digest: digest,
|
||||
for _, refString := range append(image.RepoDigests) {
|
||||
ref, err := reference.ParseNamed(refString)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if c, ok := ref.(reference.Canonical); ok {
|
||||
repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String())
|
||||
}
|
||||
}
|
||||
|
||||
for repo, tags := range repoTags {
|
||||
digests := repoDigests[repo]
|
||||
|
||||
// Do not display digests as their own row
|
||||
delete(repoDigests, repo)
|
||||
|
||||
if !ctx.Digest {
|
||||
// Ignore digest references, just show tag once
|
||||
digests = nil
|
||||
}
|
||||
|
||||
for _, tag := range tags {
|
||||
if len(digests) == 0 {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
digest: "<none>",
|
||||
})
|
||||
continue
|
||||
}
|
||||
// Display the digests for each tag
|
||||
for _, dgst := range digests {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
digest: dgst,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Show rows for remaining digest only references
|
||||
for repo, digests := range repoDigests {
|
||||
// If digests are displayed, show row per digest
|
||||
if ctx.Digest {
|
||||
for _, dgst := range digests {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: "<none>",
|
||||
digest: dgst,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
images = append(images, &imageContext{
|
||||
trunc: ctx.Trunc,
|
||||
i: image,
|
||||
repo: repo,
|
||||
tag: "<none>",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, imageCtx := range images {
|
||||
err = ctx.contextFormat(tmpl, imageCtx)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
@ -301,7 +301,6 @@ func TestImageContextWrite(t *testing.T) {
|
|||
},
|
||||
`REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
image tag1 imageID1 24 hours ago 0 B
|
||||
image <none> imageID1 24 hours ago 0 B
|
||||
image tag2 imageID2 24 hours ago 0 B
|
||||
<none> <none> imageID3 24 hours ago 0 B
|
||||
`,
|
||||
|
@ -312,7 +311,7 @@ image tag2 imageID2 24 hours ago
|
|||
Format: "table {{.Repository}}",
|
||||
},
|
||||
},
|
||||
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
|
||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
|
@ -322,7 +321,6 @@ image tag2 imageID2 24 hours ago
|
|||
Digest: true,
|
||||
},
|
||||
`REPOSITORY DIGEST
|
||||
image <none>
|
||||
image sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
|
||||
image <none>
|
||||
<none> <none>
|
||||
|
@ -335,7 +333,7 @@ image <none>
|
|||
Quiet: true,
|
||||
},
|
||||
},
|
||||
"REPOSITORY\nimage\nimage\nimage\n<none>\n",
|
||||
"REPOSITORY\nimage\nimage\n<none>\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
|
@ -344,7 +342,7 @@ image <none>
|
|||
Quiet: true,
|
||||
},
|
||||
},
|
||||
"imageID1\nimageID1\nimageID2\nimageID3\n",
|
||||
"imageID1\nimageID2\nimageID3\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
|
@ -355,8 +353,7 @@ image <none>
|
|||
Digest: true,
|
||||
},
|
||||
`REPOSITORY TAG DIGEST IMAGE ID CREATED SIZE
|
||||
image tag1 <none> imageID1 24 hours ago 0 B
|
||||
image <none> sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B
|
||||
image tag1 sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf imageID1 24 hours ago 0 B
|
||||
image tag2 <none> imageID2 24 hours ago 0 B
|
||||
<none> <none> <none> imageID3 24 hours ago 0 B
|
||||
`,
|
||||
|
@ -369,7 +366,7 @@ image tag2 <none>
|
|||
},
|
||||
Digest: true,
|
||||
},
|
||||
"imageID1\nimageID1\nimageID2\nimageID3\n",
|
||||
"imageID1\nimageID2\nimageID3\n",
|
||||
},
|
||||
// Raw Format
|
||||
{
|
||||
|
@ -384,12 +381,6 @@ image_id: imageID1
|
|||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
repository: image
|
||||
tag: <none>
|
||||
image_id: imageID1
|
||||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
repository: image
|
||||
tag: tag2
|
||||
image_id: imageID2
|
||||
|
@ -402,7 +393,7 @@ image_id: imageID3
|
|||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
`, expectedTime, expectedTime, expectedTime, expectedTime),
|
||||
`, expectedTime, expectedTime, expectedTime),
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
|
@ -413,13 +404,6 @@ virtual_size: 0 B
|
|||
},
|
||||
fmt.Sprintf(`repository: image
|
||||
tag: tag1
|
||||
digest: <none>
|
||||
image_id: imageID1
|
||||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
repository: image
|
||||
tag: <none>
|
||||
digest: sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf
|
||||
image_id: imageID1
|
||||
created_at: %s
|
||||
|
@ -439,7 +423,7 @@ image_id: imageID3
|
|||
created_at: %s
|
||||
virtual_size: 0 B
|
||||
|
||||
`, expectedTime, expectedTime, expectedTime, expectedTime),
|
||||
`, expectedTime, expectedTime, expectedTime),
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
|
@ -449,7 +433,6 @@ virtual_size: 0 B
|
|||
},
|
||||
},
|
||||
`image_id: imageID1
|
||||
image_id: imageID1
|
||||
image_id: imageID2
|
||||
image_id: imageID3
|
||||
`,
|
||||
|
@ -461,7 +444,7 @@ image_id: imageID3
|
|||
Format: "{{.Repository}}",
|
||||
},
|
||||
},
|
||||
"image\nimage\nimage\n<none>\n",
|
||||
"image\nimage\n<none>\n",
|
||||
},
|
||||
{
|
||||
ImageContext{
|
||||
|
@ -470,7 +453,7 @@ image_id: imageID3
|
|||
},
|
||||
Digest: true,
|
||||
},
|
||||
"image\nimage\nimage\n<none>\n",
|
||||
"image\nimage\n<none>\n",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ func (r *IDResolver) get(ctx context.Context, t interface{}, id string) (string,
|
|||
}
|
||||
return id, nil
|
||||
case swarm.Service:
|
||||
service, err := r.client.ServiceInspect(ctx, id)
|
||||
service, _, err := r.client.ServiceInspectWithRaw(ctx, id)
|
||||
if err != nil {
|
||||
return id, nil
|
||||
}
|
||||
|
|
|
@ -94,6 +94,10 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||
fmt.Fprintf(cli.out, "Default Runtime: %s\n", info.DefaultRuntime)
|
||||
}
|
||||
|
||||
fmt.Fprintf(cli.out, "Security Options:")
|
||||
ioutils.FprintfIfNotEmpty(cli.out, " %s", strings.Join(info.SecurityOptions, " "))
|
||||
fmt.Fprintf(cli.out, "\n")
|
||||
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Kernel Version: %s\n", info.KernelVersion)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Operating System: %s\n", info.OperatingSystem)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "OSType: %s\n", info.OSType)
|
||||
|
|
|
@ -33,7 +33,7 @@ func runAccept(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string)
|
|||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(id, "attempting to accept a node in the swarm.")
|
||||
fmt.Fprintf(dockerCli.Out(), "Node %s accepted in the swarm.\n", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -33,7 +33,7 @@ func runDemote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string)
|
|||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(id, "attempting to demote a manager in the swarm.")
|
||||
fmt.Fprintf(dockerCli.Out(), "Manager %s demoted in the swarm.\n", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\t%s\n"
|
||||
listItemFmt = "%s\t%s\t%s\t%s\t%s\t%s\n"
|
||||
)
|
||||
|
||||
type listOptions struct {
|
||||
|
@ -74,7 +74,7 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
|
|||
// Ignore flushing errors
|
||||
defer writer.Flush()
|
||||
|
||||
fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS", "LEADER")
|
||||
fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "MEMBERSHIP", "STATUS", "AVAILABILITY", "MANAGER STATUS")
|
||||
for _, node := range nodes {
|
||||
name := node.Spec.Name
|
||||
availability := string(node.Spec.Availability)
|
||||
|
@ -84,14 +84,13 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
|
|||
name = node.Description.Hostname
|
||||
}
|
||||
|
||||
leader := ""
|
||||
if node.ManagerStatus != nil && node.ManagerStatus.Leader {
|
||||
leader = "Yes"
|
||||
}
|
||||
|
||||
reachability := ""
|
||||
if node.ManagerStatus != nil {
|
||||
reachability = string(node.ManagerStatus.Reachability)
|
||||
if node.ManagerStatus.Leader {
|
||||
reachability = "Leader"
|
||||
} else {
|
||||
reachability = string(node.ManagerStatus.Reachability)
|
||||
}
|
||||
}
|
||||
|
||||
ID := node.ID
|
||||
|
@ -107,8 +106,7 @@ func printTable(out io.Writer, nodes []swarm.Node, info types.Info) {
|
|||
client.PrettyPrint(membership),
|
||||
client.PrettyPrint(string(node.Status.State)),
|
||||
client.PrettyPrint(availability),
|
||||
client.PrettyPrint(reachability),
|
||||
leader)
|
||||
client.PrettyPrint(reachability))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ func runPromote(dockerCli *client.DockerCli, flags *pflag.FlagSet, args []string
|
|||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(id, "attempting to promote a node to a manager in the swarm.")
|
||||
fmt.Fprintf(dockerCli.Out(), "Node %s promoted to a manager in the swarm.\n", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -21,7 +21,11 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
Short: "Update a node",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runUpdate(dockerCli, args[0], mergeNodeUpdate(flags))
|
||||
if err := runUpdate(dockerCli, args[0], mergeNodeUpdate(flags)); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Out(), args[0])
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -47,7 +51,6 @@ func runUpdate(dockerCli *client.DockerCli, nodeID string, mergeNode func(node *
|
|||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", nodeID)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -3,21 +3,39 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newDisableCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "disable",
|
||||
Use: "disable PLUGIN",
|
||||
Short: "Disable a plugin",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return dockerCli.Client().PluginDisable(context.Background(), args[0])
|
||||
return runDisable(dockerCli, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runDisable(dockerCli *client.DockerCli, name string) error {
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
return dockerCli.Client().PluginDisable(context.Background(), ref.String())
|
||||
}
|
||||
|
|
|
@ -3,21 +3,39 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newEnableCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "enable",
|
||||
Use: "enable PLUGIN",
|
||||
Short: "Enable a plugin",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return dockerCli.Client().PluginEnable(context.Background(), args[0])
|
||||
return runEnable(dockerCli, args[0])
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runEnable(dockerCli *client.DockerCli, name string) error {
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
return dockerCli.Client().PluginEnable(context.Background(), ref.String())
|
||||
}
|
||||
|
|
|
@ -4,16 +4,18 @@ package plugin
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect",
|
||||
Use: "inspect PLUGIN",
|
||||
Short: "Inspect a plugin",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
@ -25,7 +27,18 @@ func newInspectCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
}
|
||||
|
||||
func runInspect(dockerCli *client.DockerCli, name string) error {
|
||||
p, err := dockerCli.Client().PluginInspect(context.Background(), name)
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
p, err := dockerCli.Client().PluginInspect(context.Background(), ref.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -3,35 +3,52 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type pluginOptions struct {
|
||||
name string
|
||||
grantPerms bool
|
||||
disable bool
|
||||
}
|
||||
|
||||
func newInstallCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var options pluginOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "install",
|
||||
Use: "install PLUGIN",
|
||||
Short: "Install a plugin",
|
||||
Args: cli.RequiresMinArgs(1), // TODO: allow for set args
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runInstall(dockerCli, args[0], args[1:])
|
||||
options.name = args[0]
|
||||
return runInstall(dockerCli, options)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&options.grantPerms, "grant-all-permissions", false, "grant all permissions necessary to run the plugin")
|
||||
flags.BoolVar(&options.disable, "disable", false, "do not enable the plugin on install")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runInstall(dockerCli *client.DockerCli, name string, args []string) error {
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
func runInstall(dockerCli *client.DockerCli, opts pluginOptions) error {
|
||||
named, err := reference.ParseNamed(opts.name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
named = reference.WithDefaultTag(named)
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
|
@ -46,6 +63,34 @@ func runInstall(dockerCli *client.DockerCli, name string, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: pass acceptAllPermissions and noEnable flag
|
||||
return dockerCli.Client().PluginInstall(ctx, ref.String(), encodedAuth, false, false, dockerCli.In(), dockerCli.Out())
|
||||
|
||||
registryAuthFunc := dockerCli.RegistryAuthenticationPrivilegedFunc(repoInfo.Index, "plugin install")
|
||||
|
||||
options := types.PluginInstallOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
Disabled: opts.disable,
|
||||
AcceptAllPermissions: opts.grantPerms,
|
||||
AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name),
|
||||
// TODO: Rename PrivilegeFunc, it has nothing to do with privileges
|
||||
PrivilegeFunc: registryAuthFunc,
|
||||
}
|
||||
|
||||
return dockerCli.Client().PluginInstall(ctx, ref.String(), options)
|
||||
}
|
||||
|
||||
func acceptPrivileges(dockerCli *client.DockerCli, name string) func(privileges types.PluginPrivileges) (bool, error) {
|
||||
return func(privileges types.PluginPrivileges) (bool, error) {
|
||||
fmt.Fprintf(dockerCli.Out(), "Plugin %q is requesting the following privileges:\n", name)
|
||||
for _, privilege := range privileges {
|
||||
fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value)
|
||||
}
|
||||
|
||||
fmt.Fprint(dockerCli.Out(), "Do you grant the above permissions? [y/N] ")
|
||||
reader := bufio.NewReader(dockerCli.In())
|
||||
line, _, err := reader.ReadLine()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return strings.ToLower(string(line)) == "y", nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import (
|
|||
|
||||
func newPushCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "push",
|
||||
Use: "push PLUGIN",
|
||||
Short: "Push a plugin",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
@ -31,7 +31,9 @@ func runPush(dockerCli *client.DockerCli, name string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
named = reference.WithDefaultTag(named)
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
|
|
|
@ -7,13 +7,14 @@ import (
|
|||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm",
|
||||
Use: "rm PLUGIN",
|
||||
Short: "Remove a plugin",
|
||||
Aliases: []string{"remove"},
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
|
@ -28,8 +29,19 @@ func newRemoveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
func runRemove(dockerCli *client.DockerCli, names []string) error {
|
||||
var errs cli.Errors
|
||||
for _, name := range names {
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
// TODO: pass names to api instead of making multiple api calls
|
||||
if err := dockerCli.Client().PluginRemove(context.Background(), name); err != nil {
|
||||
if err := dockerCli.Client().PluginRemove(context.Background(), ref.String()); err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -3,16 +3,19 @@
|
|||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/reference"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func newSetCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "set",
|
||||
Use: "set PLUGIN key1=value1 [key2=value2...]",
|
||||
Short: "Change settings for a plugin",
|
||||
Args: cli.RequiresMinArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
@ -24,5 +27,16 @@ func newSetCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
}
|
||||
|
||||
func runSet(dockerCli *client.DockerCli, name string, args []string) error {
|
||||
return dockerCli.Client().PluginSet(context.Background(), name, args)
|
||||
named, err := reference.ParseNamed(name) // FIXME: validate
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if reference.IsNameOnly(named) {
|
||||
named = reference.WithDefaultTag(named)
|
||||
}
|
||||
ref, ok := named.(reference.NamedTagged)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid name: %s", named.String())
|
||||
}
|
||||
return dockerCli.Client().PluginSet(context.Background(), ref.String(), args)
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/docker/docker/pkg/ioutils"
|
||||
apiclient "github.com/docker/engine-api/client"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -50,7 +51,7 @@ func runInspect(dockerCli *client.DockerCli, opts inspectOptions) error {
|
|||
ctx := context.Background()
|
||||
|
||||
getRef := func(ref string) (interface{}, []byte, error) {
|
||||
service, err := client.ServiceInspect(ctx, ref)
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, ref)
|
||||
if err == nil || !apiclient.IsErrServiceNotFound(err) {
|
||||
return service, nil, err
|
||||
}
|
||||
|
@ -93,22 +94,61 @@ func printService(out io.Writer, service swarm.Service) {
|
|||
}
|
||||
|
||||
if service.Spec.Mode.Global != nil {
|
||||
fmt.Fprintln(out, "Mode:\t\tGLOBAL")
|
||||
fmt.Fprintln(out, "Mode:\t\tGlobal")
|
||||
} else {
|
||||
fmt.Fprintln(out, "Mode:\t\tREPLICATED")
|
||||
fmt.Fprintln(out, "Mode:\t\tReplicated")
|
||||
if service.Spec.Mode.Replicated.Replicas != nil {
|
||||
fmt.Fprintf(out, " Replicas:\t\t%d\n", *service.Spec.Mode.Replicated.Replicas)
|
||||
fmt.Fprintf(out, " Replicas:\t%d\n", *service.Spec.Mode.Replicated.Replicas)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(out, "Placement:")
|
||||
fmt.Fprintln(out, " Strategy:\tSPREAD")
|
||||
fmt.Fprintf(out, "UpateConfig:\n")
|
||||
fmt.Fprintln(out, " Strategy:\tSpread")
|
||||
if service.Spec.TaskTemplate.Placement != nil && len(service.Spec.TaskTemplate.Placement.Constraints) > 0 {
|
||||
ioutils.FprintfIfNotEmpty(out, " Constraints\t: %s\n", strings.Join(service.Spec.TaskTemplate.Placement.Constraints, ", "))
|
||||
}
|
||||
fmt.Fprintf(out, "UpdateConfig:\n")
|
||||
fmt.Fprintf(out, " Parallelism:\t%d\n", service.Spec.UpdateConfig.Parallelism)
|
||||
if service.Spec.UpdateConfig.Delay.Nanoseconds() > 0 {
|
||||
fmt.Fprintf(out, " Delay:\t\t%s\n", service.Spec.UpdateConfig.Delay)
|
||||
}
|
||||
fmt.Fprintf(out, "ContainerSpec:\n")
|
||||
printContainerSpec(out, service.Spec.TaskTemplate.ContainerSpec)
|
||||
|
||||
if service.Spec.TaskTemplate.Resources != nil {
|
||||
fmt.Fprintln(out, "Resources:")
|
||||
printResources := func(out io.Writer, r *swarm.Resources) {
|
||||
if r.NanoCPUs != 0 {
|
||||
fmt.Fprintf(out, " CPU:\t\t%g\n", float64(r.NanoCPUs)/1e9)
|
||||
}
|
||||
if r.MemoryBytes != 0 {
|
||||
fmt.Fprintf(out, " Memory:\t\t%s\n", units.BytesSize(float64(r.MemoryBytes)))
|
||||
}
|
||||
}
|
||||
if service.Spec.TaskTemplate.Resources.Reservations != nil {
|
||||
fmt.Fprintln(out, "Reservations:")
|
||||
printResources(out, service.Spec.TaskTemplate.Resources.Reservations)
|
||||
}
|
||||
if service.Spec.TaskTemplate.Resources.Limits != nil {
|
||||
fmt.Fprintln(out, "Limits:")
|
||||
printResources(out, service.Spec.TaskTemplate.Resources.Limits)
|
||||
}
|
||||
}
|
||||
if len(service.Spec.Networks) > 0 {
|
||||
fmt.Fprintf(out, "Networks:")
|
||||
for _, n := range service.Spec.Networks {
|
||||
fmt.Fprintf(out, " %s", n.Target)
|
||||
}
|
||||
}
|
||||
|
||||
if len(service.Endpoint.Ports) > 0 {
|
||||
fmt.Fprintln(out, "Ports:")
|
||||
for _, port := range service.Endpoint.Ports {
|
||||
fmt.Fprintf(out, " Name = %s\n", port.Name)
|
||||
fmt.Fprintf(out, " Protocol = %s\n", port.Protocol)
|
||||
fmt.Fprintf(out, " TargetPort = %d\n", port.TargetPort)
|
||||
fmt.Fprintf(out, " PublishedPort = %d\n", port.PublishedPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
|
||||
|
@ -117,11 +157,20 @@ func printContainerSpec(out io.Writer, containerSpec swarm.ContainerSpec) {
|
|||
fmt.Fprintf(out, " Command:\t%s\n", strings.Join(containerSpec.Command, " "))
|
||||
}
|
||||
if len(containerSpec.Args) > 0 {
|
||||
fmt.Fprintf(out, " Args:\t%s\n", strings.Join(containerSpec.Args, " "))
|
||||
fmt.Fprintf(out, " Args:\t\t%s\n", strings.Join(containerSpec.Args, " "))
|
||||
}
|
||||
if len(containerSpec.Env) > 0 {
|
||||
fmt.Fprintf(out, " Env:\t\t%s\n", strings.Join(containerSpec.Env, " "))
|
||||
}
|
||||
ioutils.FprintfIfNotEmpty(out, " Dir\t\t%s\n", containerSpec.Dir)
|
||||
ioutils.FprintfIfNotEmpty(out, " User\t\t%s\n", containerSpec.User)
|
||||
if len(containerSpec.Mounts) > 0 {
|
||||
fmt.Fprintln(out, " Mounts:")
|
||||
for _, v := range containerSpec.Mounts {
|
||||
fmt.Fprintf(out, " Target = %s\n", v.Target)
|
||||
fmt.Fprintf(out, " Source = %s\n", v.Source)
|
||||
fmt.Fprintf(out, " Writable = %v\n", v.Writable)
|
||||
fmt.Fprintf(out, " Type = %v\n", v.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,15 @@ import (
|
|||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/docker/docker/api/client"
|
||||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/engine-api/types/filters"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -47,11 +47,10 @@ func newListCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
}
|
||||
|
||||
func runList(dockerCli *client.DockerCli, opts listOptions) error {
|
||||
ctx := context.Background()
|
||||
client := dockerCli.Client()
|
||||
|
||||
services, err := client.ServiceList(
|
||||
context.Background(),
|
||||
types.ServiceListOptions{Filter: opts.filter.Value()})
|
||||
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filter: opts.filter.Value()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -60,31 +59,48 @@ func runList(dockerCli *client.DockerCli, opts listOptions) error {
|
|||
if opts.quiet {
|
||||
printQuiet(out, services)
|
||||
} else {
|
||||
printTable(out, services)
|
||||
taskFilter := filters.NewArgs()
|
||||
for _, service := range services {
|
||||
taskFilter.Add("service", service.ID)
|
||||
}
|
||||
|
||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filter: taskFilter})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
running := map[string]int{}
|
||||
for _, task := range tasks {
|
||||
if task.Status.State == "running" {
|
||||
running[task.ServiceID]++
|
||||
}
|
||||
}
|
||||
|
||||
printTable(out, services, running)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printTable(out io.Writer, services []swarm.Service) {
|
||||
func printTable(out io.Writer, services []swarm.Service, running map[string]int) {
|
||||
writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
|
||||
|
||||
// Ignore flushing errors
|
||||
defer writer.Flush()
|
||||
|
||||
fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "SCALE", "IMAGE", "COMMAND")
|
||||
fmt.Fprintf(writer, listItemFmt, "ID", "NAME", "REPLICAS", "IMAGE", "COMMAND")
|
||||
for _, service := range services {
|
||||
scale := ""
|
||||
replicas := ""
|
||||
if service.Spec.Mode.Replicated != nil && service.Spec.Mode.Replicated.Replicas != nil {
|
||||
scale = fmt.Sprintf("%d", *service.Spec.Mode.Replicated.Replicas)
|
||||
replicas = fmt.Sprintf("%d/%d", running[service.ID], *service.Spec.Mode.Replicated.Replicas)
|
||||
} else if service.Spec.Mode.Global != nil {
|
||||
scale = "global"
|
||||
replicas = "global"
|
||||
}
|
||||
fmt.Fprintf(
|
||||
writer,
|
||||
listItemFmt,
|
||||
stringid.TruncateID(service.ID),
|
||||
service.Spec.Name,
|
||||
scale,
|
||||
replicas,
|
||||
service.Spec.TaskTemplate.ContainerSpec.Image,
|
||||
strings.Join(service.Spec.TaskTemplate.ContainerSpec.Args, " "))
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ type int64Value interface {
|
|||
type memBytes int64
|
||||
|
||||
func (m *memBytes) String() string {
|
||||
return strconv.FormatInt(m.Value(), 10)
|
||||
return units.BytesSize(float64(m.Value()))
|
||||
}
|
||||
|
||||
func (m *memBytes) Set(value string) error {
|
||||
|
@ -48,7 +48,7 @@ func (m *memBytes) Value() int64 {
|
|||
type nanoCPUs int64
|
||||
|
||||
func (c *nanoCPUs) String() string {
|
||||
return strconv.FormatInt(c.Value(), 10)
|
||||
return big.NewRat(c.Value(), 1e9).FloatString(3)
|
||||
}
|
||||
|
||||
func (c *nanoCPUs) Set(value string) error {
|
||||
|
@ -177,7 +177,7 @@ func (m *MountOpt) Set(value string) error {
|
|||
}
|
||||
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invald field '%s' must be a key=value pair", field)
|
||||
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
||||
}
|
||||
|
||||
key, value := parts[0], parts[1]
|
||||
|
@ -191,14 +191,14 @@ func (m *MountOpt) Set(value string) error {
|
|||
case "writable":
|
||||
mount.Writable, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invald value for writable: %s", err.Error())
|
||||
return fmt.Errorf("invalid value for writable: %s", value)
|
||||
}
|
||||
case "bind-propagation":
|
||||
mount.BindOptions.Propagation = swarm.MountPropagation(strings.ToUpper(value))
|
||||
case "volume-populate":
|
||||
volumeOptions().Populate, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invald value for populate: %s", err.Error())
|
||||
return fmt.Errorf("invalid value for populate: %s", value)
|
||||
}
|
||||
case "volume-label":
|
||||
setValueOnMap(volumeOptions().Labels, value)
|
||||
|
@ -235,7 +235,8 @@ func (m *MountOpt) Type() string {
|
|||
func (m *MountOpt) String() string {
|
||||
mounts := []string{}
|
||||
for _, mount := range m.values {
|
||||
mounts = append(mounts, fmt.Sprintf("%v", mount))
|
||||
repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
|
||||
mounts = append(mounts, repr)
|
||||
}
|
||||
return strings.Join(mounts, ", ")
|
||||
}
|
||||
|
@ -456,7 +457,7 @@ func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
|
|||
|
||||
flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints")
|
||||
|
||||
flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously")
|
||||
flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 0, "Maximum number of tasks updated simultaneously")
|
||||
flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates")
|
||||
|
||||
flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
|
||||
|
|
115
api/client/service/opts_test.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/pkg/testutil/assert"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
)
|
||||
|
||||
func TestMemBytesString(t *testing.T) {
|
||||
var mem memBytes = 1048576
|
||||
assert.Equal(t, mem.String(), "1 MiB")
|
||||
}
|
||||
|
||||
func TestMemBytesSetAndValue(t *testing.T) {
|
||||
var mem memBytes
|
||||
assert.NilError(t, mem.Set("5kb"))
|
||||
assert.Equal(t, mem.Value(), int64(5120))
|
||||
}
|
||||
|
||||
func TestNanoCPUsString(t *testing.T) {
|
||||
var cpus nanoCPUs = 6100000000
|
||||
assert.Equal(t, cpus.String(), "6.100")
|
||||
}
|
||||
|
||||
func TestNanoCPUsSetAndValue(t *testing.T) {
|
||||
var cpus nanoCPUs
|
||||
assert.NilError(t, cpus.Set("0.35"))
|
||||
assert.Equal(t, cpus.Value(), int64(350000000))
|
||||
}
|
||||
|
||||
func TestDurationOptString(t *testing.T) {
|
||||
dur := time.Duration(300 * 10e8)
|
||||
duration := DurationOpt{value: &dur}
|
||||
assert.Equal(t, duration.String(), "5m0s")
|
||||
}
|
||||
|
||||
func TestDurationOptSetAndValue(t *testing.T) {
|
||||
var duration DurationOpt
|
||||
assert.NilError(t, duration.Set("300s"))
|
||||
assert.Equal(t, *duration.Value(), time.Duration(300*10e8))
|
||||
}
|
||||
|
||||
func TestUint64OptString(t *testing.T) {
|
||||
value := uint64(2345678)
|
||||
opt := Uint64Opt{value: &value}
|
||||
assert.Equal(t, opt.String(), "2345678")
|
||||
|
||||
opt = Uint64Opt{}
|
||||
assert.Equal(t, opt.String(), "none")
|
||||
}
|
||||
|
||||
func TestUint64OptSetAndValue(t *testing.T) {
|
||||
var opt Uint64Opt
|
||||
assert.NilError(t, opt.Set("14445"))
|
||||
assert.Equal(t, *opt.Value(), uint64(14445))
|
||||
}
|
||||
|
||||
func TestMountOptString(t *testing.T) {
|
||||
mount := MountOpt{
|
||||
values: []swarm.Mount{
|
||||
{
|
||||
Type: swarm.MountType("BIND"),
|
||||
Source: "/home/path",
|
||||
Target: "/target",
|
||||
},
|
||||
{
|
||||
Type: swarm.MountType("VOLUME"),
|
||||
Source: "foo",
|
||||
Target: "/target/foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
expected := "BIND /home/path /target, VOLUME foo /target/foo"
|
||||
assert.Equal(t, mount.String(), expected)
|
||||
}
|
||||
|
||||
func TestMountOptSetNoError(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.NilError(t, mount.Set("type=bind,target=/target,source=/foo"))
|
||||
|
||||
mounts := mount.Value()
|
||||
assert.Equal(t, len(mounts), 1)
|
||||
assert.Equal(t, mounts[0], swarm.Mount{
|
||||
Type: swarm.MountType("BIND"),
|
||||
Source: "/foo",
|
||||
Target: "/target",
|
||||
})
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorNoType(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("target=/target,source=/foo"), "type is required")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorNoTarget(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("type=VOLUME,source=/foo"), "target is required")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidKey(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("type=VOLUME,bogus=foo"), "unexpected key 'bogus'")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidField(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("type=VOLUME,bogus"), "invalid field 'bogus'")
|
||||
}
|
||||
|
||||
func TestMountOptSetErrorInvalidWritable(t *testing.T) {
|
||||
var mount MountOpt
|
||||
assert.Error(t, mount.Set("type=VOLUME,writable=yes"), "invalid value for writable: yes")
|
||||
}
|
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
func newScaleCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "scale SERVICE=SCALE [SERVICE=SCALE...]",
|
||||
Use: "scale SERVICE=REPLICAS [SERVICE=REPLICAS...]",
|
||||
Short: "Scale one or multiple services",
|
||||
Args: scaleArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
@ -61,7 +61,8 @@ func runServiceScale(dockerCli *client.DockerCli, serviceID string, scale string
|
|||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
service, err := client.ServiceInspect(ctx, serviceID)
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ func runTasks(dockerCli *client.DockerCli, opts tasksOptions) error {
|
|||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
service, err := client.ServiceInspect(ctx, opts.serviceID)
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, opts.serviceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, serviceID stri
|
|||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
service, err := client.ServiceInspect(ctx, serviceID)
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -31,7 +31,12 @@ func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefil
|
|||
}
|
||||
|
||||
fmt.Fprintf(stderr, "Loading bundle from %s\n", path)
|
||||
bundle, err := bundlefile.LoadFile(path)
|
||||
reader, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bundle, err := bundlefile.LoadFile(reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error reading %s: %v\n", path, err)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/docker/docker/cli"
|
||||
"github.com/docker/engine-api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type initOptions struct {
|
||||
|
@ -19,6 +20,7 @@ type initOptions struct {
|
|||
}
|
||||
|
||||
func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
var flags *pflag.FlagSet
|
||||
opts := initOptions{
|
||||
listenAddr: NewNodeAddrOption(),
|
||||
autoAccept: NewAutoAcceptOption(),
|
||||
|
@ -26,14 +28,14 @@ func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize a Swarm.",
|
||||
Short: "Initialize a Swarm",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runInit(dockerCli, opts)
|
||||
return runInit(dockerCli, flags, opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags = cmd.Flags()
|
||||
flags.Var(&opts.listenAddr, "listen-addr", "Listen address")
|
||||
flags.Var(&opts.autoAccept, "auto-accept", "Auto acceptance policy (worker, manager, or none)")
|
||||
flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster")
|
||||
|
@ -41,7 +43,7 @@ func newInitCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
return cmd
|
||||
}
|
||||
|
||||
func runInit(dockerCli *client.DockerCli, opts initOptions) error {
|
||||
func runInit(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts initOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
@ -50,8 +52,11 @@ func runInit(dockerCli *client.DockerCli, opts initOptions) error {
|
|||
ForceNewCluster: opts.forceNewCluster,
|
||||
}
|
||||
|
||||
req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(opts.secret)
|
||||
|
||||
if flags.Changed("secret") {
|
||||
req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(&opts.secret)
|
||||
} else {
|
||||
req.Spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(nil)
|
||||
}
|
||||
nodeID, err := client.SwarmInit(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -25,7 +25,7 @@ func newJoinCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
|
||||
cmd := &cobra.Command{
|
||||
Use: "join [OPTIONS] HOST:PORT",
|
||||
Short: "Join a Swarm as a node and/or manager.",
|
||||
Short: "Join a Swarm as a node and/or manager",
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.remote = args[0]
|
||||
|
|
|
@ -19,7 +19,7 @@ func newLeaveCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
|
||||
cmd := &cobra.Command{
|
||||
Use: "leave",
|
||||
Short: "Leave a Swarm.",
|
||||
Short: "Leave a Swarm",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runLeave(dockerCli, opts)
|
||||
|
|
|
@ -35,12 +35,12 @@ func (a *NodeAddrOption) String() string {
|
|||
// Set the value for this flag
|
||||
func (a *NodeAddrOption) Set(value string) error {
|
||||
if !strings.Contains(value, ":") {
|
||||
return fmt.Errorf("Invalud url, a host and port are required")
|
||||
return fmt.Errorf("Invalid url, a host and port are required")
|
||||
}
|
||||
|
||||
parts := strings.Split(value, ":")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("Invalud url, too many colons")
|
||||
return fmt.Errorf("Invalid url, too many colons")
|
||||
}
|
||||
|
||||
a.addr = value
|
||||
|
@ -102,7 +102,7 @@ func (o *AutoAcceptOption) Type() string {
|
|||
}
|
||||
|
||||
// Policies returns a representation of this option for the api
|
||||
func (o *AutoAcceptOption) Policies(secret string) []swarm.Policy {
|
||||
func (o *AutoAcceptOption) Policies(secret *string) []swarm.Policy {
|
||||
policies := []swarm.Policy{}
|
||||
for _, p := range defaultPolicies {
|
||||
if len(o.values) != 0 {
|
||||
|
|
|
@ -18,6 +18,7 @@ type updateOptions struct {
|
|||
secret string
|
||||
taskHistoryLimit int64
|
||||
dispatcherHeartbeat time.Duration
|
||||
nodeCertExpiry time.Duration
|
||||
}
|
||||
|
||||
func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
||||
|
@ -26,7 +27,7 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
|
||||
cmd := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "update the Swarm.",
|
||||
Short: "Update the Swarm",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runUpdate(dockerCli, flags, opts)
|
||||
|
@ -38,6 +39,7 @@ func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
|
|||
flags.StringVar(&opts.secret, "secret", "", "Set secret value needed to accept nodes into cluster")
|
||||
flags.Int64Var(&opts.taskHistoryLimit, "task-history-limit", 10, "Task history retention limit")
|
||||
flags.DurationVar(&opts.dispatcherHeartbeat, "dispatcher-heartbeat", time.Duration(5*time.Second), "Dispatcher heartbeat period")
|
||||
flags.DurationVar(&opts.nodeCertExpiry, "cert-expiry", time.Duration(90*24*time.Hour), "Validity period for node certificates")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -54,6 +56,7 @@ func runUpdate(dockerCli *client.DockerCli, flags *pflag.FlagSet, opts updateOpt
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = client.SwarmUpdate(ctx, swarm.Version, swarm.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -68,18 +71,17 @@ func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error {
|
|||
|
||||
if flags.Changed("auto-accept") {
|
||||
value := flags.Lookup("auto-accept").Value.(*AutoAcceptOption)
|
||||
if len(spec.AcceptancePolicy.Policies) > 0 {
|
||||
spec.AcceptancePolicy.Policies = value.Policies(spec.AcceptancePolicy.Policies[0].Secret)
|
||||
} else {
|
||||
spec.AcceptancePolicy.Policies = value.Policies("")
|
||||
}
|
||||
spec.AcceptancePolicy.Policies = value.Policies(nil)
|
||||
}
|
||||
|
||||
var psecret *string
|
||||
if flags.Changed("secret") {
|
||||
secret, _ := flags.GetString("secret")
|
||||
for _, policy := range spec.AcceptancePolicy.Policies {
|
||||
policy.Secret = secret
|
||||
}
|
||||
psecret = &secret
|
||||
}
|
||||
|
||||
for i := range spec.AcceptancePolicy.Policies {
|
||||
spec.AcceptancePolicy.Policies[i].Secret = psecret
|
||||
}
|
||||
|
||||
if flags.Changed("task-history-limit") {
|
||||
|
@ -92,5 +94,11 @@ func mergeSwarm(swarm *swarm.Swarm, flags *pflag.FlagSet) error {
|
|||
}
|
||||
}
|
||||
|
||||
if flags.Changed("cert-expiry") {
|
||||
if v, err := flags.GetDuration("cert-expiry"); err == nil {
|
||||
spec.CAConfig.NodeCertExpiry = v
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
17
cli/error.go
|
@ -1,21 +1,20 @@
|
|||
package cli
|
||||
|
||||
import "bytes"
|
||||
import "strings"
|
||||
|
||||
// Errors is a list of errors.
|
||||
// Useful in a loop if you don't want to return the error right away and you want to display after the loop,
|
||||
// all the errors that happened during the loop.
|
||||
type Errors []error
|
||||
|
||||
func (errs Errors) Error() string {
|
||||
if len(errs) < 1 {
|
||||
func (errList Errors) Error() string {
|
||||
if len(errList) < 1 {
|
||||
return ""
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(errs[0].Error())
|
||||
for _, err := range errs[1:] {
|
||||
buf.WriteString(", ")
|
||||
buf.WriteString(err.Error())
|
||||
|
||||
out := make([]string, len(errList))
|
||||
for i := range errList {
|
||||
out[i] = errList[i].Error()
|
||||
}
|
||||
return buf.String()
|
||||
return strings.Join(out, ", ")
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@ import (
|
|||
)
|
||||
|
||||
func pluginInit(config *daemon.Config, remote libcontainerd.Remote, rs registry.Service) error {
|
||||
return plugin.Init(config.Root, config.ExecRoot, remote, rs)
|
||||
return plugin.Init(config.Root, config.ExecRoot, remote, rs, config.LiveRestore)
|
||||
}
|
||||
|
|
|
@ -823,7 +823,7 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
|
|||
})
|
||||
}
|
||||
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionService(svcCfg.Name, svcCfg.ID, net.ParseIP(vip), portConfigs))
|
||||
createOptions = append(createOptions, libnetwork.CreateOptionService(svcCfg.Name, svcCfg.ID, net.ParseIP(vip), portConfigs, svcCfg.Aliases[n.ID()]))
|
||||
}
|
||||
|
||||
if !containertypes.NetworkMode(n.Name()).IsUserDefined() {
|
||||
|
|
|
@ -54,7 +54,8 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun
|
|||
|
||||
// TmpfsMounts returns the list of tmpfs mounts
|
||||
func (container *Container) TmpfsMounts() []Mount {
|
||||
return nil
|
||||
var mounts []Mount
|
||||
return mounts
|
||||
}
|
||||
|
||||
// UpdateContainer updates configuration of a container
|
||||
|
|
|
@ -1639,9 +1639,15 @@ _docker_swarm_join() {
|
|||
}
|
||||
|
||||
_docker_swarm_update() {
|
||||
case "$prev" in
|
||||
--auto-accept|--cert-expiry|--dispatcher-heartbeat|--secret|--task-history-limit)
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
case "$cur" in
|
||||
-*)
|
||||
COMPREPLY=( $( compgen -W "--auto-accept --dispatcher-heartbeat --help --secret --task-history-limit" -- "$cur" ) )
|
||||
COMPREPLY=( $( compgen -W "--auto-accept --cert-expiry --dispatcher-heartbeat --help --secret --task-history-limit" -- "$cur" ) )
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#compdef docker
|
||||
#compdef docker dockerd
|
||||
#
|
||||
# zsh completion for docker (http://docker.com)
|
||||
#
|
||||
|
@ -1410,6 +1410,13 @@ _docker() {
|
|||
return ret
|
||||
}
|
||||
|
||||
_dockerd() {
|
||||
integer ret=1
|
||||
words[1]='daemon'
|
||||
__docker_subcommand && ret=0
|
||||
return ret
|
||||
}
|
||||
|
||||
_docker "$@"
|
||||
|
||||
# Local Variables:
|
||||
|
|
|
@ -20,6 +20,8 @@ LimitCORE=infinity
|
|||
TimeoutStartSec=0
|
||||
# set delegate yes so that systemd does not reset the cgroups of docker containers
|
||||
Delegate=yes
|
||||
# kill only the docker process, not all processes in the cgroup
|
||||
KillMode=process
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -28,25 +28,28 @@ import (
|
|||
|
||||
const swarmDirName = "swarm"
|
||||
const controlSocket = "control.sock"
|
||||
const swarmConnectTimeout = 10 * time.Second
|
||||
const swarmConnectTimeout = 20 * time.Second
|
||||
const stateFile = "docker-state.json"
|
||||
|
||||
const (
|
||||
initialReconnectDelay = 100 * time.Millisecond
|
||||
maxReconnectDelay = 10 * time.Second
|
||||
maxReconnectDelay = 30 * time.Second
|
||||
)
|
||||
|
||||
// ErrNoManager is returned then a manager-only function is called on non-manager
|
||||
var ErrNoManager = fmt.Errorf("this node is not participating as a Swarm manager")
|
||||
var ErrNoManager = fmt.Errorf("This node is not participating as a Swarm manager")
|
||||
|
||||
// ErrNoSwarm is returned on leaving a cluster that was never initialized
|
||||
var ErrNoSwarm = fmt.Errorf("this node is not part of Swarm")
|
||||
var ErrNoSwarm = fmt.Errorf("This node is not part of Swarm")
|
||||
|
||||
// ErrSwarmExists is returned on initialize or join request for a cluster that has already been activated
|
||||
var ErrSwarmExists = fmt.Errorf("this node is already part of a Swarm")
|
||||
var ErrSwarmExists = fmt.Errorf("This node is already part of a Swarm cluster. Use \"docker swarm leave\" to leave this cluster and join another one.")
|
||||
|
||||
// ErrPendingSwarmExists is returned on initialize or join request for a cluster that is already processing a similar request but has not succeeded yet.
|
||||
var ErrPendingSwarmExists = fmt.Errorf("This node is processing an existing join request that has not succeeded yet. Use \"docker swarm leave\" to cancel the current request.")
|
||||
|
||||
// ErrSwarmJoinTimeoutReached is returned when cluster join could not complete before timeout was reached.
|
||||
var ErrSwarmJoinTimeoutReached = fmt.Errorf("timeout reached before node was joined")
|
||||
var ErrSwarmJoinTimeoutReached = fmt.Errorf("Timeout was reached before node was joined. Attempt to join the cluster will continue in the background. Use \"docker info\" command to see the current Swarm status of your node.")
|
||||
|
||||
type state struct {
|
||||
ListenAddr string
|
||||
|
@ -111,7 +114,7 @@ func New(config Config) (*Cluster, error) {
|
|||
select {
|
||||
case <-time.After(swarmConnectTimeout):
|
||||
logrus.Errorf("swarm component could not be started before timeout was reached")
|
||||
case <-n.Ready(context.Background()):
|
||||
case <-n.Ready():
|
||||
case <-ctx.Done():
|
||||
}
|
||||
if ctx.Err() != nil {
|
||||
|
@ -213,7 +216,7 @@ func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secre
|
|||
|
||||
go func() {
|
||||
select {
|
||||
case <-node.Ready(context.Background()):
|
||||
case <-node.Ready():
|
||||
c.Lock()
|
||||
c.reconnectDelay = initialReconnectDelay
|
||||
c.Unlock()
|
||||
|
@ -249,13 +252,14 @@ func (c *Cluster) startNewNode(forceNewCluster bool, listenAddr, joinAddr, secre
|
|||
// Init initializes new cluster from user provided request.
|
||||
func (c *Cluster) Init(req types.InitRequest) (string, error) {
|
||||
c.Lock()
|
||||
if c.node != nil {
|
||||
if node := c.node; node != nil {
|
||||
c.Unlock()
|
||||
if !req.ForceNewCluster {
|
||||
return "", ErrSwarmExists
|
||||
return "", errSwarmExists(node)
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
c.cancelReconnect()
|
||||
if err := c.node.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") {
|
||||
return "", err
|
||||
}
|
||||
|
@ -273,7 +277,7 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
|
|||
c.Unlock()
|
||||
|
||||
select {
|
||||
case <-n.Ready(context.Background()):
|
||||
case <-n.Ready():
|
||||
if err := initAcceptancePolicy(n, req.Spec.AcceptancePolicy); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -297,9 +301,9 @@ func (c *Cluster) Init(req types.InitRequest) (string, error) {
|
|||
// Join makes current Cluster part of an existing swarm cluster.
|
||||
func (c *Cluster) Join(req types.JoinRequest) error {
|
||||
c.Lock()
|
||||
if c.node != nil {
|
||||
if node := c.node; node != nil {
|
||||
c.Unlock()
|
||||
return ErrSwarmExists
|
||||
return errSwarmExists(node)
|
||||
}
|
||||
// todo: check current state existing
|
||||
if len(req.RemoteAddrs) == 0 {
|
||||
|
@ -312,23 +316,29 @@ func (c *Cluster) Join(req types.JoinRequest) error {
|
|||
}
|
||||
c.Unlock()
|
||||
|
||||
select {
|
||||
case <-time.After(swarmConnectTimeout):
|
||||
go c.reconnectOnFailure(ctx)
|
||||
if nodeid := n.NodeID(); nodeid != "" {
|
||||
return fmt.Errorf("Timeout reached before node was joined. Your cluster settings may be preventing this node from automatically joining. To accept this node into cluster run `docker node accept %v` in an existing cluster manager", nodeid)
|
||||
certificateRequested := n.CertificateRequested()
|
||||
for {
|
||||
select {
|
||||
case <-certificateRequested:
|
||||
if n.NodeMembership() == swarmapi.NodeMembershipPending {
|
||||
return fmt.Errorf("Your node is in the process of joining the cluster but needs to be accepted by existing cluster member.\nTo accept this node into cluster run \"docker node accept %v\" in an existing cluster manager. Use \"docker info\" command to see the current Swarm status of your node.", n.NodeID())
|
||||
}
|
||||
certificateRequested = nil
|
||||
case <-time.After(swarmConnectTimeout):
|
||||
// attempt to connect will continue in background, also reconnecting
|
||||
go c.reconnectOnFailure(ctx)
|
||||
return ErrSwarmJoinTimeoutReached
|
||||
case <-n.Ready():
|
||||
go c.reconnectOnFailure(ctx)
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if c.err != nil {
|
||||
return c.err
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
return ErrSwarmJoinTimeoutReached
|
||||
case <-n.Ready(context.Background()):
|
||||
go c.reconnectOnFailure(ctx)
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if c.err != nil {
|
||||
return c.err
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,10 +389,11 @@ func (c *Cluster) Leave(force bool) error {
|
|||
if err := node.Stop(ctx); err != nil && !strings.Contains(err.Error(), "context canceled") {
|
||||
return err
|
||||
}
|
||||
nodeID := node.NodeID()
|
||||
for _, id := range c.config.Backend.ListContainersForNode(nodeID) {
|
||||
if err := c.config.Backend.ContainerRm(id, &apitypes.ContainerRmConfig{ForceRemove: true}); err != nil {
|
||||
logrus.Errorf("error removing %v: %v", id, err)
|
||||
if nodeID := node.NodeID(); nodeID != "" {
|
||||
for _, id := range c.config.Backend.ListContainersForNode(nodeID) {
|
||||
if err := c.config.Backend.ContainerRm(id, &apitypes.ContainerRmConfig{ForceRemove: true}); err != nil {
|
||||
logrus.Errorf("error removing %v: %v", id, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Lock()
|
||||
|
@ -444,12 +455,12 @@ func (c *Cluster) Update(version uint64, spec types.Spec) error {
|
|||
return ErrNoManager
|
||||
}
|
||||
|
||||
swarmSpec, err := convert.SwarmSpecToGRPC(spec)
|
||||
swarm, err := getSwarm(c.getRequestContext(), c.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
swarm, err := getSwarm(c.getRequestContext(), c.client)
|
||||
swarmSpec, err := convert.SwarmSpecToGRPCandMerge(spec, &swarm.Spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -918,7 +929,7 @@ func populateNetworkID(ctx context.Context, c swarmapi.ControlClient, s *types.S
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Networks[i] = types.NetworkAttachmentConfig{Target: apiNetwork.ID}
|
||||
s.Networks[i].Target = apiNetwork.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1004,6 +1015,13 @@ func (c *Cluster) managerStats() (current bool, reachable int, unreachable int,
|
|||
return
|
||||
}
|
||||
|
||||
func errSwarmExists(node *swarmagent.Node) error {
|
||||
if node.NodeMembership() != swarmapi.NodeMembershipAccepted {
|
||||
return ErrPendingSwarmExists
|
||||
}
|
||||
return ErrSwarmExists
|
||||
}
|
||||
|
||||
func initAcceptancePolicy(node *swarmagent.Node, acceptancePolicy types.AcceptancePolicy) error {
|
||||
ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
for conn := range node.ListenControlSocket(ctx) {
|
||||
|
@ -1030,7 +1048,7 @@ func initAcceptancePolicy(node *swarmagent.Node, acceptancePolicy types.Acceptan
|
|||
}
|
||||
spec := &cluster.Spec
|
||||
|
||||
if err := convert.SwarmSpecUpdateAcceptancePolicy(spec, acceptancePolicy); err != nil {
|
||||
if err := convert.SwarmSpecUpdateAcceptancePolicy(spec, acceptancePolicy, nil); err != nil {
|
||||
return fmt.Errorf("error updating cluster settings: %v", err)
|
||||
}
|
||||
_, err := client.UpdateCluster(ctx, &swarmapi.UpdateClusterRequest{
|
||||
|
|
|
@ -148,17 +148,22 @@ func BasicNetworkFromGRPC(n swarmapi.Network) basictypes.NetworkResource {
|
|||
}
|
||||
}
|
||||
|
||||
return basictypes.NetworkResource{
|
||||
nr := basictypes.NetworkResource{
|
||||
ID: n.ID,
|
||||
Name: n.Spec.Annotations.Name,
|
||||
Scope: "swarm",
|
||||
Driver: n.DriverState.Name,
|
||||
EnableIPv6: spec.Ipv6Enabled,
|
||||
IPAM: ipam,
|
||||
Internal: spec.Internal,
|
||||
Options: n.DriverState.Options,
|
||||
Labels: n.Spec.Annotations.Labels,
|
||||
}
|
||||
|
||||
if n.DriverState != nil {
|
||||
nr.Driver = n.DriverState.Name
|
||||
nr.Options = n.DriverState.Options
|
||||
}
|
||||
|
||||
return nr
|
||||
}
|
||||
|
||||
// BasicNetworkCreateToGRPC converts a NetworkCreateRequest to a grpc NetworkSpec.
|
||||
|
|
|
@ -49,7 +49,8 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
|
|||
Autoaccept: policy.Autoaccept,
|
||||
}
|
||||
if policy.Secret != nil {
|
||||
p.Secret = string(policy.Secret.Data)
|
||||
secret := string(policy.Secret.Data)
|
||||
p.Secret = &secret
|
||||
}
|
||||
swarm.Spec.AcceptancePolicy.Policies = append(swarm.Spec.AcceptancePolicy.Policies, p)
|
||||
}
|
||||
|
@ -57,8 +58,8 @@ func SwarmFromGRPC(c swarmapi.Cluster) types.Swarm {
|
|||
return swarm
|
||||
}
|
||||
|
||||
// SwarmSpecToGRPC converts a Spec to a grpc ClusterSpec.
|
||||
func SwarmSpecToGRPC(s types.Spec) (swarmapi.ClusterSpec, error) {
|
||||
// SwarmSpecToGRPCandMerge converts a Spec to a grpc ClusterSpec and merge AcceptancePolicy from an existing grpc ClusterSpec if provided.
|
||||
func SwarmSpecToGRPCandMerge(s types.Spec, existingSpec *swarmapi.ClusterSpec) (swarmapi.ClusterSpec, error) {
|
||||
spec := swarmapi.ClusterSpec{
|
||||
Annotations: swarmapi.Annotations{
|
||||
Name: s.Name,
|
||||
|
@ -82,15 +83,18 @@ func SwarmSpecToGRPC(s types.Spec) (swarmapi.ClusterSpec, error) {
|
|||
},
|
||||
}
|
||||
|
||||
if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy); err != nil {
|
||||
if err := SwarmSpecUpdateAcceptancePolicy(&spec, s.AcceptancePolicy, existingSpec); err != nil {
|
||||
return swarmapi.ClusterSpec{}, err
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
// SwarmSpecUpdateAcceptancePolicy updates a grpc ClusterSpec using AcceptancePolicy.
|
||||
func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy) error {
|
||||
func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolicy types.AcceptancePolicy, oldSpec *swarmapi.ClusterSpec) error {
|
||||
spec.AcceptancePolicy.Policies = nil
|
||||
hashs := make(map[string][]byte)
|
||||
|
||||
for _, p := range acceptancePolicy.Policies {
|
||||
role, ok := swarmapi.NodeRole_value[strings.ToUpper(string(p.Role))]
|
||||
if !ok {
|
||||
|
@ -102,11 +106,24 @@ func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolic
|
|||
Autoaccept: p.Autoaccept,
|
||||
}
|
||||
|
||||
if p.Secret != "" {
|
||||
hashPwd, _ := bcrypt.GenerateFromPassword([]byte(p.Secret), 0)
|
||||
if p.Secret != nil {
|
||||
if *p.Secret == "" { // if provided secret is empty, it means erase previous secret.
|
||||
policy.Secret = nil
|
||||
} else { // if provided secret is not empty, we generate a new one.
|
||||
hashPwd, ok := hashs[*p.Secret]
|
||||
if !ok {
|
||||
hashPwd, _ = bcrypt.GenerateFromPassword([]byte(*p.Secret), 0)
|
||||
hashs[*p.Secret] = hashPwd
|
||||
}
|
||||
policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret{
|
||||
Data: hashPwd,
|
||||
Alg: "bcrypt",
|
||||
}
|
||||
}
|
||||
} else if oldSecret := getOldSecret(oldSpec, policy.Role); oldSecret != nil { // else use the old one.
|
||||
policy.Secret = &swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret{
|
||||
Data: hashPwd,
|
||||
Alg: "bcrypt",
|
||||
Data: oldSecret.Data,
|
||||
Alg: oldSecret.Alg,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,3 +131,15 @@ func SwarmSpecUpdateAcceptancePolicy(spec *swarmapi.ClusterSpec, acceptancePolic
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getOldSecret(oldSpec *swarmapi.ClusterSpec, role swarmapi.NodeRole) *swarmapi.AcceptancePolicy_RoleAdmissionPolicy_HashedSecret {
|
||||
if oldSpec == nil {
|
||||
return nil
|
||||
}
|
||||
for _, p := range oldSpec.AcceptancePolicy.Policies {
|
||||
if p.Role == role {
|
||||
return p.Secret
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func newContainerAdapter(b executorpkg.Backend, task *api.Task) (*containerAdapt
|
|||
|
||||
func (c *containerAdapter) pullImage(ctx context.Context) error {
|
||||
// if the image needs to be pulled, the auth config will be retrieved and updated
|
||||
encodedAuthConfig := c.container.task.ServiceAnnotations.Labels[fmt.Sprintf("%v.registryauth", systemLabelPrefix)]
|
||||
encodedAuthConfig := c.container.spec().RegistryAuth
|
||||
|
||||
authConfig := &types.AuthConfig{}
|
||||
if encodedAuthConfig != "" {
|
||||
|
@ -126,7 +126,6 @@ func (c *containerAdapter) create(ctx context.Context, backend executorpkg.Backe
|
|||
|
||||
if nc != nil {
|
||||
for n, ep := range nc.EndpointsConfig {
|
||||
logrus.Errorf("CONNECT %s : %v", n, ep.IPAMConfig.IPv4Address)
|
||||
if err := backend.ConnectContainerToNetwork(cr.ID, n, ep); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -348,6 +348,7 @@ func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig {
|
|||
log.Printf("Creating service config in agent for t = %+v", c.task)
|
||||
svcCfg := &clustertypes.ServiceConfig{
|
||||
Name: c.task.ServiceAnnotations.Name,
|
||||
Aliases: make(map[string][]string),
|
||||
ID: c.task.ServiceID,
|
||||
VirtualAddresses: make(map[string]*clustertypes.VirtualAddress),
|
||||
}
|
||||
|
@ -357,6 +358,9 @@ func (c *containerConfig) serviceConfig() *clustertypes.ServiceConfig {
|
|||
// We support only IPv4 virtual IP for now.
|
||||
IPv4: c.virtualIP(na.Network.ID),
|
||||
}
|
||||
if len(na.Aliases) > 0 {
|
||||
svcCfg.Aliases[na.Network.ID] = na.Aliases
|
||||
}
|
||||
}
|
||||
|
||||
if c.task.Endpoint != nil {
|
||||
|
|
|
@ -2,13 +2,13 @@ package container
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
executorpkg "github.com/docker/docker/daemon/cluster/executor"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/swarmkit/agent/exec"
|
||||
"github.com/docker/swarmkit/api"
|
||||
"github.com/docker/swarmkit/log"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
|
@ -84,31 +84,32 @@ func (r *controller) Prepare(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
if err := r.checkClosed(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.adapter.create(ctx, r.backend); err != nil {
|
||||
if isContainerCreateNameConflict(err) {
|
||||
if _, err := r.adapter.inspect(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.adapter.pullImage(ctx); err != nil {
|
||||
// NOTE(stevvooe): We always try to pull the image to make sure we have
|
||||
// the most up to date version. This will return an error, but we only
|
||||
// log it. If the image truly doesn't exist, the create below will
|
||||
// error out.
|
||||
//
|
||||
// This gives us some nice behavior where we use up to date versions of
|
||||
// mutable tags, but will still run if the old image is available but a
|
||||
// registry is down.
|
||||
//
|
||||
// If you don't want this behavior, lock down your image to an
|
||||
// immutable tag or digest.
|
||||
log.G(ctx).WithError(err).Error("pulling image failed")
|
||||
}
|
||||
|
||||
// container is already created. success!
|
||||
return exec.ErrTaskPrepared
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), "No such image") { // todo: better error detection
|
||||
return err
|
||||
}
|
||||
if err := r.adapter.pullImage(ctx); err != nil {
|
||||
if err := r.adapter.create(ctx, r.backend); err != nil {
|
||||
if isContainerCreateNameConflict(err) {
|
||||
if _, err := r.adapter.inspect(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue // retry to create the container
|
||||
// container is already created. success!
|
||||
return exec.ErrTaskPrepared
|
||||
}
|
||||
|
||||
break
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -135,7 +136,7 @@ func (r *controller) Start(ctx context.Context) error {
|
|||
}
|
||||
|
||||
if err := r.adapter.start(ctx); err != nil {
|
||||
return err
|
||||
return errors.Wrap(err, "starting container failed")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -31,6 +31,7 @@ type PortConfig struct {
|
|||
type ServiceConfig struct {
|
||||
ID string
|
||||
Name string
|
||||
Aliases map[string][]string
|
||||
VirtualAddresses map[string]*VirtualAddress
|
||||
ExposedPorts []*PortConfig
|
||||
}
|
||||
|
|
|
@ -179,7 +179,7 @@ func (daemon *Daemon) restore() error {
|
|||
logrus.Errorf("Failed to restore with containerd: %q", err)
|
||||
return
|
||||
}
|
||||
if !c.HostConfig.NetworkMode.IsContainer() {
|
||||
if !c.HostConfig.NetworkMode.IsContainer() && c.IsRunning() {
|
||||
options, err := daemon.buildSandboxOptions(c)
|
||||
if err != nil {
|
||||
logrus.Warnf("Failed build sandbox option to restore container %s: %v", c.ID, err)
|
||||
|
|
|
@ -114,19 +114,10 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
|
|||
backingFs = fsName
|
||||
}
|
||||
|
||||
// check if they are running over btrfs, aufs, zfs or overlay
|
||||
// check if they are running over btrfs, aufs, zfs, overlay, or ecryptfs
|
||||
switch fsMagic {
|
||||
case graphdriver.FsMagicBtrfs:
|
||||
logrus.Error("'overlay' is not supported over btrfs.")
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
case graphdriver.FsMagicAufs:
|
||||
logrus.Error("'overlay' is not supported over aufs.")
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
case graphdriver.FsMagicZfs:
|
||||
logrus.Error("'overlay' is not supported over zfs.")
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
case graphdriver.FsMagicOverlay:
|
||||
logrus.Error("'overlay' is not supported over overlay.")
|
||||
case graphdriver.FsMagicBtrfs, graphdriver.FsMagicAufs, graphdriver.FsMagicZfs, graphdriver.FsMagicOverlay, graphdriver.FsMagicEcryptfs:
|
||||
logrus.Errorf("'overlay2' is not supported over %s", backingFs)
|
||||
return nil, graphdriver.ErrIncompatibleFS
|
||||
}
|
||||
|
||||
|
|
|
@ -465,6 +465,7 @@ func (daemon *Daemon) transformContainer(container *container.Container, ctx *li
|
|||
GlobalIPv6Address: network.GlobalIPv6Address,
|
||||
GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen,
|
||||
MacAddress: network.MacAddress,
|
||||
NetworkID: network.NetworkID,
|
||||
}
|
||||
if network.IPAMConfig != nil {
|
||||
networks[name].IPAMConfig = &networktypes.EndpointIPAMConfig{
|
||||
|
|
|
@ -480,9 +480,10 @@ func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []c
|
|||
}
|
||||
|
||||
if m.Source == "tmpfs" {
|
||||
data := c.HostConfig.Tmpfs[m.Destination]
|
||||
options := []string{"noexec", "nosuid", "nodev", volume.DefaultPropagationMode}
|
||||
if m.Data != "" {
|
||||
options = append(options, strings.Split(m.Data, ",")...)
|
||||
if data != "" {
|
||||
options = append(options, strings.Split(data, ",")...)
|
||||
}
|
||||
|
||||
merged, err := mount.MergeTmpfsOptions(options)
|
||||
|
|
|
@ -129,7 +129,8 @@ func (daemon *Daemon) registerMountPoints(container *container.Container, hostCo
|
|||
return err
|
||||
}
|
||||
|
||||
if binds[bind.Destination] {
|
||||
_, tmpfsExists := hostConfig.Tmpfs[bind.Destination]
|
||||
if binds[bind.Destination] || tmpfsExists {
|
||||
return fmt.Errorf("Duplicate mount point '%s'", bind.Destination)
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,15 @@ import (
|
|||
// /etc/resolv.conf, and if it is not, appends it to the array of mounts.
|
||||
func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) {
|
||||
var mounts []container.Mount
|
||||
// TODO: tmpfs mounts should be part of Mountpoints
|
||||
tmpfsMounts := make(map[string]bool)
|
||||
for _, m := range c.TmpfsMounts() {
|
||||
tmpfsMounts[m.Destination] = true
|
||||
}
|
||||
for _, m := range c.MountPoints {
|
||||
if tmpfsMounts[m.Destination] {
|
||||
continue
|
||||
}
|
||||
if err := daemon.lazyInitializeVolume(c.ID, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
42
docs/getstarted/index.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
aliases = [
|
||||
"/mac/started/",
|
||||
"/windows/started/",
|
||||
"/linux/started/",
|
||||
]
|
||||
title = "Get Started with Docker"
|
||||
description = "Getting started with Docker"
|
||||
keywords = ["beginner, getting started, Docker"]
|
||||
[menu.main]
|
||||
identifier = "getstart_all"
|
||||
parent = "engine_use"
|
||||
weight="-80"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
||||
# Get Started with Docker
|
||||
|
||||
This tutorial is a for non-technical users who are interested in learning more about Docker. By following these steps, you'll learn fundamental Docker features while working through some simple tasks. You'll learn how to:
|
||||
|
||||
* install Docker software for your platform
|
||||
* run a software image in a container
|
||||
* browse for an image on Docker Hub
|
||||
* create your own image and run it in a container
|
||||
* create a Docker Hub account and an image repository
|
||||
* create an image of your own
|
||||
* push your image to Docker Hub for others to use
|
||||
|
||||
The getting started was user tested to reduce the chance of users having problems. For the best chance of success, follow the steps as written the first time before exploring on your own. It takes approximately 45 minutes to complete.
|
||||
|
||||
|
||||
### Make sure you understand...
|
||||
|
||||
This getting started uses Docker Engine CLI commands entered on the command line of a terminal window. You don't need to be a wizard at the command line, but you should be familiar with how to open your favorite shell or terminal, and run basic commands in that environment. It helps (but isn't required) to know how to navigate a directory tree, manipulate files, list running process, and so forth.
|
||||
|
||||
|
||||
Go to [the next page to install](step_one.md).
|
||||
|
||||
|
||||
|
70
docs/getstarted/last_page.md
Normal file
|
@ -0,0 +1,70 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
aliases = [
|
||||
"/mac/last_page/",
|
||||
"/windows/last_page/",
|
||||
"/linux/last_page/",
|
||||
]
|
||||
title = "Learning more"
|
||||
description = "Getting started with Docker"
|
||||
keywords = ["beginner, getting started, Docker"]
|
||||
[menu.main]
|
||||
identifier = "getstart_learn_more"
|
||||
parent = "getstart_all"
|
||||
weight = 7
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# Learning more
|
||||
|
||||
This getting started provided very basic essentials for using Docker on Mac, Windows, and Linux. If you want to learn more with regard to end-to-end development, start with the full install instructions and feature overviews, then follow up with more advanced tutorials and user guides.
|
||||
|
||||
Depending on your interest, the Docker documentation contains a wealth of information. Here are some places to start:
|
||||
|
||||
<style type="text/css">
|
||||
</style>
|
||||
<table class="tutorial">
|
||||
<tr>
|
||||
<th class="tg-031e">If you are looking for</th>
|
||||
<th class="tg-031e">Where to find it</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tg-031e">More about Docker for Mac, features, examples, FAQs, relationship to Docker Machine and Docker Toolbox, and how this fits in the Docker ecosystem</td>
|
||||
<td class="tg-031e">[Getting Started with Docker for Mac](/docker-for-mac/index.md)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tg-031e">More about Docker for Windows, More about Docker for Windows, features, examples, FAQs, relationship to Docker Machine and Docker Toolbox, and how this fits in the Docker ecosystem</td>
|
||||
<td class="tg-031e">[Getting Started with Docker for Windows](/docker-for-mac/index.md)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tg-031e">More about Docker Toolbox</td>
|
||||
<td class="tg-031e">[Docker Toolbox Overview](/toolbox/overview.md)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tg-031e">More about Docker for Linux distributions</td>
|
||||
<td class="tg-031e">[Install Docker Engine on Linux](/engine/installation/linux/index.md)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tg-031e">More advanced tutorials on running containers, building your own images, networking containers, managing data for containers, and storing images on Docker Hub</td>
|
||||
<td class="tg-031e"> [Learn by example](/engine/tutorials/index.md)</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tg-031e">Information about the Docker product line</td>
|
||||
<td class="tg-031e"><a href="http://www.docker.com/products/">The product explainer is a good place to start.</a></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td class="tg-031e">How to set up an automated build on Docker Hub</td>
|
||||
<td class="tg-031e"><a href="https://docs.docker.com/docker-hub/">Docker Hub documentation</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="tg-031e">How to run a multi-container application with Compose</td>
|
||||
<td class="tg-031e"> [Docker Compose documentation](/compose/overview.md)
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
|
||||
|
44
docs/getstarted/linux_install_help.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
aliases = ["/mac/started/"]
|
||||
title = "Install Docker and run hello-world"
|
||||
description = "Getting started with Docker"
|
||||
keywords = ["beginner, getting started, Docker, install"]
|
||||
identifier = "getstart_linux_install"
|
||||
parent = "getstart_all"
|
||||
weight="-80"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# Example: Install Docker on Ubuntu Linux
|
||||
|
||||
This installation procedure for users who are unfamiliar with package
|
||||
managers, and just want to try out the Getting Started tutorial while running Docker on Linux. If you are comfortable with package managers, prefer not to use
|
||||
`curl`, or have problems installing and want to troubleshoot, please use our
|
||||
`apt` and `yum` <a href="https://docs.docker.com/engine/installation/"
|
||||
target="_blank">repositories instead for your installation</a>.
|
||||
|
||||
1. Log into your Ubuntu installation as a user with `sudo` privileges.
|
||||
|
||||
2. Verify that you have `curl` installed.
|
||||
|
||||
$ which curl
|
||||
|
||||
If `curl` isn't installed, install it after updating your manager:
|
||||
|
||||
$ sudo apt-get update
|
||||
$ sudo apt-get install curl
|
||||
|
||||
3. Get the latest Docker package.
|
||||
|
||||
$ curl -fsSL https://get.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 -fsSL https://get.docker.com/gpg | sudo apt-key add -
|
78
docs/getstarted/step_five.md
Normal file
|
@ -0,0 +1,78 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
aliases = [
|
||||
"/mac/step_five/",
|
||||
"/windows/step_five/",
|
||||
"/linux/step_five/",
|
||||
]
|
||||
title = "Create a Docker Hub account & repository"
|
||||
description = "Getting started with Docker"
|
||||
keywords = ["beginner, getting started, Docker"]
|
||||
[menu.main]
|
||||
identifier = "getstart_docker_hub"
|
||||
parent = "getstart_all"
|
||||
weight = 5
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# Create a Docker Hub account & repository
|
||||
|
||||
You've built something really cool, you should share it. In this next section,
|
||||
you'll do just that. You'll need a Docker Hub account. Then, you'll push your
|
||||
image up to it so other people with Docker Engine can run it.
|
||||
|
||||
|
||||
## Step 1: Sign up for an account
|
||||
|
||||
1. Use your browser to navigate to <a href="https://hub.docker.com/?utm_source=getting_started_guide&utm_medium=embedded_MacOSX&utm_campaign=create_docker_hub_account" target="_blank">the Docker Hub signup page</a>.
|
||||
|
||||
Your browser displays the page.
|
||||
|
||||
![Docker Hub signup](tutimg/hub_signup.png)
|
||||
|
||||
2. Fill out the form on the signup page.
|
||||
|
||||
Docker Hub is free. Docker does need a name, password, and email address.
|
||||
|
||||
3. Press **Signup**.
|
||||
|
||||
The browser displays the welcome to Docker Hub page.
|
||||
|
||||
## Step 2: Verify your email and add a repository
|
||||
|
||||
Before you can share anything on the hub, you need to verify your email address.
|
||||
|
||||
1. Open your email inbox.
|
||||
|
||||
2. Look for the email titled `Please confirm email for your Docker Hub account`.
|
||||
|
||||
If you don't see the email, check your Spam folder or wait a moment for the email to arrive.
|
||||
|
||||
2. Open the email and click the **Confirm Your Email** button.
|
||||
|
||||
The browser opens Docker Hub to your profile page.
|
||||
|
||||
4. Choose **Create Repository**.
|
||||
|
||||
The browser opens the **Create Repository** page.
|
||||
|
||||
5. Provide a Repository Name and Short Description.
|
||||
|
||||
6. Make sure Visibility is set to **Public**.
|
||||
|
||||
When you are done, your form should look similar to the following:
|
||||
|
||||
![Add repo](tutimg/add_repository.png)
|
||||
|
||||
6. Press **Create** when you are done.
|
||||
|
||||
Docker Hub creates your new repository.
|
||||
|
||||
## Where to go next
|
||||
|
||||
On this page, you opened an account on Docker Hub and created a new repository.
|
||||
In the next section, you populate the repository [by tagging and pushing the
|
||||
image you created earlier](step_six.md).
|
||||
|
||||
|
||||
|
225
docs/getstarted/step_four.md
Normal file
|
@ -0,0 +1,225 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
aliases = [
|
||||
"/mac/step_four/",
|
||||
"/windows/step_four/",
|
||||
"/linux/step_four/",
|
||||
]
|
||||
title = "Build your own image"
|
||||
description = "Getting started with Docker"
|
||||
keywords = ["beginner, getting started, Docker"]
|
||||
[menu.main]
|
||||
identifier = "getstart_build_image"
|
||||
parent = "getstart_all"
|
||||
weight = 4
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# Build your own image
|
||||
|
||||
The `whalesay` image could be improved. It would be nice if you didn't have to
|
||||
think of something to say. And you type a lot to get `whalesay` to talk.
|
||||
|
||||
docker run docker/whalesay cowsay boo-boo
|
||||
|
||||
In this next section, you will improve the `whalesay` image by building a new version that "talks on its own" and requires fewer words to run.
|
||||
|
||||
## Step 1: Write a Dockerfile
|
||||
|
||||
In this step, you use your favorite text editor to write a short Dockerfile. A
|
||||
Dockerfile describes the software that is "baked" into an image. It isn't just
|
||||
ingredients tho, it can tell the software what environment to use or what
|
||||
commands to run. Your recipe is going to be very short.
|
||||
|
||||
1. Go back to your command terminal window.
|
||||
|
||||
2. Make a new directory by typing `mkdir mydockerbuild` and pressing RETURN.
|
||||
|
||||
$ mkdir mydockerbuild
|
||||
|
||||
This directory serves as the "context" for your build. The context just means it contains all the things you need to build your image.
|
||||
|
||||
3. Change to your new directory.
|
||||
|
||||
$ cd mydockerbuild
|
||||
|
||||
Right now the directory is empty.
|
||||
|
||||
4. Create a Dockerfile in the directory by typing `touch Dockerfile` and pressing RETURN.
|
||||
|
||||
$ touch Dockerfile
|
||||
|
||||
The command appears to do nothing but it actually creates the Dockerfile in the current directory. Just type `ls Dockerfile` to see it.
|
||||
|
||||
$ ls Dockerfile
|
||||
Dockerfile
|
||||
|
||||
5. Open the `Dockerfile` in a visual text editor like <a href="https://atom.io/" target="_blank">Atom</a> or <a href="https://www.sublimetext.com/" target="_blank">Sublime</a>, or a text based editor like `vi`, or `nano` (https://www.nano-editor.org/).
|
||||
|
||||
6. Add a line to the file like this:
|
||||
|
||||
FROM docker/whalesay:latest
|
||||
|
||||
The `FROM` keyword tells Docker which image your image is based on. Whalesay is cute and has the `cowsay` program already, so we'll start there.
|
||||
|
||||
7. Now, add the `fortunes` program to the image.
|
||||
|
||||
RUN apt-get -y update && apt-get install -y fortunes
|
||||
|
||||
The `fortunes` program has a command that prints out wise sayings for our whale to say. So, the first step is to install it. This line installs the software into the image.
|
||||
|
||||
8. Once the image has the software it needs, you instruct the software to run
|
||||
when the image is loaded.
|
||||
|
||||
CMD /usr/games/fortune -a | cowsay
|
||||
|
||||
This line tells the `fortune` program to pass a nifty quote to the `cowsay` program.
|
||||
|
||||
9. Check your work, your file should look like this:
|
||||
|
||||
FROM docker/whalesay:latest
|
||||
RUN apt-get -y update && apt-get install -y fortunes
|
||||
CMD /usr/games/fortune -a | cowsay
|
||||
|
||||
10. Save and close your Dockerfile.
|
||||
|
||||
At this point, you have all your software ingredients and behaviors described in a Dockerfile. You are ready to build a new image.
|
||||
|
||||
## Step 2: Build an image from your Dockerfile
|
||||
|
||||
1. At the command line, make sure the Dockerfile is in the current directory by typing `cat Dockerfile`
|
||||
|
||||
$ cat Dockerfile
|
||||
FROM docker/whalesay:latest
|
||||
|
||||
RUN apt-get -y update && apt-get install -y fortunes
|
||||
|
||||
CMD /usr/games/fortune -a | cowsay
|
||||
|
||||
2. Now, build your new image by typing the `docker build -t docker-whale .` command in your terminal (don't forget the . period).
|
||||
|
||||
$ docker build -t docker-whale .
|
||||
Sending build context to Docker daemon 158.8 MB
|
||||
...snip...
|
||||
Removing intermediate container a8e6faa88df3
|
||||
Successfully built 7d9495d03763
|
||||
|
||||
The command takes several seconds to run and reports its outcome. Before
|
||||
you do anything with the new image, take a minute to learn about the
|
||||
Dockerfile build process.
|
||||
|
||||
## Step 3: Learn about the build process
|
||||
|
||||
The `docker build -t docker-whale .` command takes the `Dockerfile` in the
|
||||
current directory, and builds an image called `docker-whale` on your local
|
||||
machine. The command takes about a minute and its output looks really long and
|
||||
complex. In this section, you learn what each message means.
|
||||
|
||||
First Docker checks to make sure it has everything it needs to build.
|
||||
|
||||
Sending build context to Docker daemon 158.8 MB
|
||||
|
||||
Then, Docker loads with the `whalesay` image. It already has this image
|
||||
locally as you might recall from the last page. So, Docker doesn't need to
|
||||
download it.
|
||||
|
||||
Step 0 : FROM docker/whalesay:latest
|
||||
---> fb434121fc77
|
||||
|
||||
Docker moves onto the next step which is to update the `apt-get` package
|
||||
manager. This takes a lot of lines, no need to list them all again here.
|
||||
|
||||
Step 1 : RUN apt-get -y update && apt-get install -y fortunes
|
||||
---> Running in 27d224dfa5b2
|
||||
Ign http://archive.ubuntu.com trusty InRelease
|
||||
Ign http://archive.ubuntu.com trusty-updates InRelease
|
||||
Ign http://archive.ubuntu.com trusty-security InRelease
|
||||
Hit http://archive.ubuntu.com trusty Release.gpg
|
||||
....snip...
|
||||
Get:15 http://archive.ubuntu.com trusty-security/restricted amd64 Packages [14.8 kB]
|
||||
Get:16 http://archive.ubuntu.com trusty-security/universe amd64 Packages [134 kB]
|
||||
Reading package lists...
|
||||
---> eb06e47a01d2
|
||||
|
||||
Then, Docker installs the new `fortunes` software.
|
||||
|
||||
Removing intermediate container e2a84b5f390f
|
||||
Step 2 : RUN apt-get install -y fortunes
|
||||
---> Running in 23aa52c1897c
|
||||
Reading package lists...
|
||||
Building dependency tree...
|
||||
Reading state information...
|
||||
The following extra packages will be installed:
|
||||
fortune-mod fortunes-min librecode0
|
||||
Suggested packages:
|
||||
x11-utils bsdmainutils
|
||||
The following NEW packages will be installed:
|
||||
fortune-mod fortunes fortunes-min librecode0
|
||||
0 upgraded, 4 newly installed, 0 to remove and 3 not upgraded.
|
||||
Need to get 1961 kB of archives.
|
||||
After this operation, 4817 kB of additional disk space will be used.
|
||||
Get:1 http://archive.ubuntu.com/ubuntu/ trusty/main librecode0 amd64 3.6-21 [771 kB]
|
||||
...snip......
|
||||
Setting up fortunes (1:1.99.1-7) ...
|
||||
Processing triggers for libc-bin (2.19-0ubuntu6.6) ...
|
||||
---> c81071adeeb5
|
||||
Removing intermediate container 23aa52c1897c
|
||||
|
||||
Finally, Docker finishes the build and reports its outcome.
|
||||
|
||||
Step 3 : CMD /usr/games/fortune -a | cowsay
|
||||
---> Running in a8e6faa88df3
|
||||
---> 7d9495d03763
|
||||
Removing intermediate container a8e6faa88df3
|
||||
Successfully built 7d9495d03763
|
||||
|
||||
|
||||
## Step 4: Run your new docker-whale
|
||||
|
||||
In this step, you verify the new images is on your computer and then you run your new image.
|
||||
|
||||
1. Open a command line terminal.
|
||||
|
||||
2. Type `docker images` and press RETURN.
|
||||
|
||||
This command, you might remember, lists the images you have locally.
|
||||
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
|
||||
docker-whale latest 7d9495d03763 4 minutes ago 273.7 MB
|
||||
docker/whalesay latest fb434121fc77 4 hours ago 247 MB
|
||||
hello-world latest 91c95931e552 5 weeks ago 910 B
|
||||
|
||||
3. Run your new image by typing `docker run docker-whale` and pressing RETURN.
|
||||
|
||||
$ docker run docker-whale
|
||||
_________________________________________
|
||||
/ "He was a modest, good-humored boy. It \
|
||||
\ was Oxford that made him insufferable." /
|
||||
-----------------------------------------
|
||||
\
|
||||
\
|
||||
\
|
||||
## .
|
||||
## ## ## ==
|
||||
## ## ## ## ===
|
||||
/""""""""""""""""___/ ===
|
||||
~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
|
||||
\______ o __/
|
||||
\ \ __/
|
||||
\____\______/
|
||||
|
||||
As you can see, you've made the whale a lot smarter. It finds its own
|
||||
things to say and the command line is a lot shorter! You may also notice
|
||||
that Docker didn't have to download anything. That is because the image was
|
||||
built locally and is already available.
|
||||
|
||||
## Where to go next
|
||||
|
||||
On this page, you learned to build an image by writing your own Dockerfile.
|
||||
You ran your image in a container. You also just used Linux from your Mac yet
|
||||
again. In the next section, you take the first step in sharing your image by
|
||||
[creating a Docker Hub account](step_five.md).
|
||||
|
||||
|
||||
|
140
docs/getstarted/step_one.md
Normal file
|
@ -0,0 +1,140 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
aliases = [
|
||||
"/mac/step_one/",
|
||||
"/windows/step_one/",
|
||||
"/linux/step_one/",
|
||||
]
|
||||
title = "Install Docker and run hello-world"
|
||||
description = "Getting started with Docker"
|
||||
keywords = ["beginner, getting started, Docker, install"]
|
||||
[menu.main]
|
||||
identifier = "getstart_all_install"
|
||||
parent = "getstart_all"
|
||||
weight = 1
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# Install Docker
|
||||
|
||||
## Step 1: Get Docker
|
||||
|
||||
### Docker for Mac
|
||||
|
||||
Docker for Mac is our newest offering for the Mac. It runs as a native Mac application and uses <a href="https://github.com/mist64/xhyve/" target="_blank">xhyve</a> to virutalize the Docker Engine environment and Linux kernel-specific features for the Docker daemon.
|
||||
|
||||
<a class="button" href="https://download.docker.com/mac/beta/Docker.dmg">Get Docker for Mac</a>
|
||||
|
||||
#### Install Prequisites
|
||||
|
||||
- Mac must be a 2010 or newer model, with Intel's hardware support for memory management unit (MMU) virtualization; i.e., Extended Page Tables (EPT)
|
||||
|
||||
- OS X 10.10.3 Yosemite or newer
|
||||
|
||||
- At least 4GB of RAM
|
||||
|
||||
- VirtualBox prior to version 4.3.30 must NOT be installed (it is incompatible with Docker for Mac). Docker for Mac will error out on install in this case. Uninstall the older version of VirtualBox and re-try the install.
|
||||
|
||||
#### Docker Toolbox for the Mac
|
||||
|
||||
If you have an earlier Mac that doesn't meet the Docker for Mac prerequisites, <a href="https://www.docker.com/products/docker-toolbox" target="_blank">get Docker Toolbox</a> for the Mac.
|
||||
|
||||
See [Docker Toolbox Overview](/toolbox/overview.md) for help on installing Docker with Toolbox.
|
||||
|
||||
### Docker for Windows
|
||||
|
||||
Docker for Windows is our newest offering for PCs. It runs as a native Windows application and uses Hyper-V to virutalize the Docker Engine environment and Linux kernel-specific features for the Docker daemon.
|
||||
|
||||
<a class="button" href="https://download.docker.com/win/beta/InstallDocker.msi">Get Docker for Windows</a>
|
||||
|
||||
#### Install Prequisites
|
||||
|
||||
* 64bit Windows 10 Pro, Enterprise and Education (1511 November update, Build 10586 or later). In the future we will support more versions of Windows 10.
|
||||
|
||||
* The Hyper-V package must be enabled. The Docker for Windows installer will enable it for you, if needed. (This requires a reboot).
|
||||
|
||||
#### Docker Toolbox for Windows
|
||||
|
||||
If you have an earlier Windows system that doesn't meet the Docker for Windows prerequisites, <a href="https://www.docker.com/products/docker-toolbox" target="_blank">get Docker Toolbox</a>.
|
||||
|
||||
See [Docker Toolbox Overview](/toolbox/overview.md) for help on installing Docker with Toolbox.
|
||||
|
||||
### Docker for Linux
|
||||
Docker Engine runs navitvely on Linux distributions.
|
||||
|
||||
For full instructions on getting Docker for various Linux distributions, see [Install Docker Engine](/engine/installation/index.md).
|
||||
|
||||
## Step 2: Install Docker
|
||||
|
||||
* For install instructions for Docker for Mac, see [Getting Started with Docker for Mac](/docker-for-mac/index.md).
|
||||
|
||||
* For install instructions for Docker for Windows, see [Getting Started with Docker for Windows](/docker-for-windows/index.md).
|
||||
|
||||
* For install instructions for Docker Toolbox, see [Docker Toolbox Overview](/toolbox/overview.md).
|
||||
|
||||
* For a simple example of installing Docker on Ubuntu Linux so that you can work through this tutorial, see [Installing Docker on Ubuntu Linux (Example)](linux_install_help.md).
|
||||
|
||||
For full install instructions for Docker on Linux, see [Install Docker Engine](/engine/installation/index.md) and select the flavor of Linux you want to use.
|
||||
|
||||
## Step 3: Verify your installation
|
||||
|
||||
1. Open a command-line terminal, and run some Docker commands to verify that Docker is working as expected.
|
||||
|
||||
Some good commands to try are `docker version` to check that you have the latest release installed and `docker ps` to see if you have any running containers. (Probably not, since you just started.)
|
||||
|
||||
2. Type the `docker run hello-world` command and press RETURN.
|
||||
|
||||
The command does some work for you, if everything runs well, the command's
|
||||
output looks like this:
|
||||
|
||||
$ docker run hello-world
|
||||
Unable to find image 'hello-world:latest' locally
|
||||
latest: Pulling from library/hello-world
|
||||
535020c3e8ad: Pull complete
|
||||
af340544ed62: Pull complete
|
||||
Digest: sha256:a68868bfe696c00866942e8f5ca39e3e31b79c1e50feaee4ce5e28df2f051d5c
|
||||
Status: Downloaded newer image for hello-world:latest
|
||||
|
||||
Hello from Docker.
|
||||
This message shows that your installation appears to be working correctly.
|
||||
|
||||
To generate this message, Docker took the following steps:
|
||||
1. The Docker Engine CLI client contacted the Docker Engine daemon.
|
||||
2. The Docker Engine daemon pulled the "hello-world" image from the Docker Hub.
|
||||
3. The Docker Engine daemon created a new container from that image which runs the
|
||||
executable that produces the output you are currently reading.
|
||||
4. The Docker Engine daemon streamed that output to the Docker Engine CLI client, which sent it
|
||||
to your terminal.
|
||||
|
||||
To try something more ambitious, you can run an Ubuntu container with:
|
||||
$ docker run -it ubuntu bash
|
||||
|
||||
Share images, automate workflows, and more with a free Docker Hub account:
|
||||
https://hub.docker.com
|
||||
|
||||
For more examples and ideas, visit:
|
||||
https://docs.docker.com/userguide/
|
||||
|
||||
3. Run `docker ps -a` to show all containers on the system.
|
||||
|
||||
$ docker ps -a
|
||||
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
592376ff3eb8 hello-world "/hello" 25 seconds ago Exited (0) 24 seconds ago prickly_wozniak
|
||||
|
||||
You should see your `hello-world` container listed in the output for the `docker ps -a` command.
|
||||
|
||||
The command `docker ps` shows only currently running containers. Since `hello-world` already ran and exited, it wouldn't show up with a `docker ps`.
|
||||
|
||||
## Looking for troubleshooting help?
|
||||
|
||||
Typically, the above steps work out-of-the-box, but some scenarios can cause problems. If your `docker run hello-world` didn't work and resulted in errors, check out [Troubleshooting](/toolbox/faqs/troubleshoot.md) for quick fixes to common problems.
|
||||
|
||||
## Where to go next
|
||||
|
||||
At this point, you have successfully installed the Docker software. Leave the
|
||||
Docker Quickstart Terminal window open. Now, go to the next page to [read a very
|
||||
short introduction Docker images and containers](step_two.md).
|
||||
|
||||
|
||||
|
205
docs/getstarted/step_six.md
Normal file
|
@ -0,0 +1,205 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
aliases = [
|
||||
"/mac/step_six/",
|
||||
"/windows/step_six/",
|
||||
"/linux/step_six/",
|
||||
]
|
||||
title = "Tag, push, & pull your image"
|
||||
description = "Getting started with Docker"
|
||||
keywords = ["beginner, getting started, Docker"]
|
||||
[menu.main]
|
||||
identifier = "getstart_tag_push_pull"
|
||||
parent = "getstart_all"
|
||||
weight = 6
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
||||
# Tag, push, and pull your image
|
||||
|
||||
In this section, you tag and push your `docker-whale` image to your newly
|
||||
created repository. When you are done, you test the repository by pulling your
|
||||
new image.
|
||||
|
||||
## Step 1: Tag and push the image
|
||||
|
||||
If you don't already have a terminal open, open one now:
|
||||
|
||||
1. Go back to your command line terminal.
|
||||
|
||||
2. At the prompt, type `docker images` to list the images you currently have:
|
||||
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
|
||||
docker-whale latest 7d9495d03763 38 minutes ago 273.7 MB
|
||||
<none> <none> 5dac217f722c 45 minutes ago 273.7 MB
|
||||
docker/whalesay latest fb434121fc77 4 hours ago 247 MB
|
||||
hello-world latest 91c95931e552 5 weeks ago 910 B
|
||||
|
||||
5. Find the `IMAGE ID` for your `docker-whale` image.
|
||||
|
||||
In this example, the id is `7d9495d03763`.
|
||||
|
||||
Notice that currently, the `REPOSITORY` shows the repo name `docker-whale`
|
||||
but not the namespace. You need to include the `namespace` for Docker Hub to
|
||||
associate it with your account. The `namespace` is the same as your Docker
|
||||
Hub account name. You need to rename the image to
|
||||
`YOUR_DOCKERHUB_NAME/docker-whale`.
|
||||
|
||||
6. Use `IMAGE ID` and the `docker tag` command to tag your `docker-whale` image.
|
||||
|
||||
The command you type looks like this:
|
||||
|
||||
![Docker tag command](tutimg/tagger.png)
|
||||
|
||||
Of course, your account name will be your own. So, you type the command with
|
||||
your image's ID and your account name and press RETURN.
|
||||
|
||||
$ docker tag 7d9495d03763 maryatdocker/docker-whale:latest
|
||||
|
||||
7. Type the `docker images` command again to see your newly tagged image.
|
||||
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
|
||||
maryatdocker/docker-whale latest 7d9495d03763 5 minutes ago 273.7 MB
|
||||
docker-whale latest 7d9495d03763 2 hours ago 273.7 MB
|
||||
<none> <none> 5dac217f722c 5 hours ago 273.7 MB
|
||||
docker/whalesay latest fb434121fc77 5 hours ago 247 MB
|
||||
hello-world latest 91c95931e552 5 weeks ago 910 B
|
||||
|
||||
8. Use the `docker login` command to log into the Docker Hub from the command line.
|
||||
|
||||
The format for the login command is:
|
||||
|
||||
docker login --username=yourhubusername --email=youremail@company.com
|
||||
|
||||
When prompted, enter your password and press enter. So, for example:
|
||||
|
||||
$ docker login --username=maryatdocker --email=mary@docker.com
|
||||
Password:
|
||||
WARNING: login credentials saved in C:\Users\sven\.docker\config.json
|
||||
Login Succeeded
|
||||
|
||||
9. Type the `docker push` command to push your image to your new repository.
|
||||
|
||||
$ docker push maryatdocker/docker-whale
|
||||
The push refers to a repository [maryatdocker/docker-whale] (len: 1)
|
||||
7d9495d03763: Image already exists
|
||||
c81071adeeb5: Image successfully pushed
|
||||
eb06e47a01d2: Image successfully pushed
|
||||
fb434121fc77: Image successfully pushed
|
||||
5d5bd9951e26: Image successfully pushed
|
||||
99da72cfe067: Image successfully pushed
|
||||
1722f41ddcb5: Image successfully pushed
|
||||
5b74edbcaa5b: Image successfully pushed
|
||||
676c4a1897e6: Image successfully pushed
|
||||
07f8e8c5e660: Image successfully pushed
|
||||
37bea4ee0c81: Image successfully pushed
|
||||
a82efea989f9: Image successfully pushed
|
||||
e9e06b06e14c: Image successfully pushed
|
||||
Digest: sha256:ad89e88beb7dc73bf55d456e2c600e0a39dd6c9500d7cd8d1025626c4b985011
|
||||
|
||||
10. Return to your profile on Docker Hub to see your new image.
|
||||
|
||||
![Docker tag command](tutimg/new_image.png)
|
||||
|
||||
## Step 2: Pull your new image
|
||||
|
||||
In this last section, you'll pull the image you just pushed to hub. Before you
|
||||
do that though, you'll need to remove the original image from your local
|
||||
machine. If you left the original image on your machine. Docker would not pull
|
||||
from the hub — why would it? The two images are identical.
|
||||
|
||||
1. Make sure Docker is running, and open a command line terminal.
|
||||
|
||||
2. At the prompt, type `docker images` to list the images you currently have on your local machine.
|
||||
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
|
||||
maryatdocker/docker-whale latest 7d9495d03763 5 minutes ago 273.7 MB
|
||||
docker-whale latest 7d9495d03763 2 hours ago 273.7 MB
|
||||
<none> <none> 5dac217f722c 5 hours ago 273.7 MB
|
||||
docker/whalesay latest fb434121fc77 5 hours ago 247 MB
|
||||
hello-world latest 91c95931e552 5 weeks ago 910 B
|
||||
|
||||
To make a good test, you need to remove the `maryatdocker/docker-whale` and
|
||||
`docker-whale` images from your local system. Removing them forces the next
|
||||
`docker pull` to get the image from your repository.
|
||||
|
||||
3. Use the `docker rmi` to remove the `maryatdocker/docker-whale` and `docker-whale`
|
||||
images.
|
||||
|
||||
You can use an ID or the name to remove an image.
|
||||
|
||||
$ docker rmi -f 7d9495d03763
|
||||
$ docker rmi -f docker-whale
|
||||
|
||||
4. Pull and load a new image from your repository using the `docker run` command.
|
||||
|
||||
The command you type should include your username from Docker Hub.
|
||||
|
||||
docker run yourusername/docker-whale
|
||||
|
||||
Since the image is no longer available on your local system, Docker downloads it.
|
||||
|
||||
$ docker run maryatdocker/docker-whale
|
||||
Unable to find image 'maryatdocker/docker-whale:latest' locally
|
||||
latest: Pulling from maryatdocker/docker-whale
|
||||
eb06e47a01d2: Pull complete
|
||||
c81071adeeb5: Pull complete
|
||||
7d9495d03763: Already exists
|
||||
e9e06b06e14c: Already exists
|
||||
a82efea989f9: Already exists
|
||||
37bea4ee0c81: Already exists
|
||||
07f8e8c5e660: Already exists
|
||||
676c4a1897e6: Already exists
|
||||
5b74edbcaa5b: Already exists
|
||||
1722f41ddcb5: Already exists
|
||||
99da72cfe067: Already exists
|
||||
5d5bd9951e26: Already exists
|
||||
fb434121fc77: Already exists
|
||||
Digest: sha256:ad89e88beb7dc73bf55d456e2c600e0a39dd6c9500d7cd8d1025626c4b985011
|
||||
Status: Downloaded newer image for maryatdocker/docker-whale:latest
|
||||
________________________________________
|
||||
/ Having wandered helplessly into a \
|
||||
| blinding snowstorm Sam was greatly |
|
||||
| relieved to see a sturdy Saint Bernard |
|
||||
| dog bounding toward him with the |
|
||||
| traditional keg of brandy strapped to |
|
||||
| his collar. |
|
||||
| |
|
||||
| "At last," cried Sam, "man's best |
|
||||
\ friend -- and a great big dog, too!" /
|
||||
----------------------------------------
|
||||
\
|
||||
\
|
||||
\
|
||||
## .
|
||||
## ## ## ==
|
||||
## ## ## ## ===
|
||||
/""""""""""""""""___/ ===
|
||||
~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
|
||||
\______ o __/
|
||||
\ \ __/
|
||||
\____\______/
|
||||
|
||||
## Where to go next
|
||||
|
||||
You've done a lot, you've done all of the following fundamental Docker tasks.
|
||||
|
||||
* installed Docker
|
||||
* run a software image in a container
|
||||
* located an interesting image on Docker Hub
|
||||
* run the image on your own machine
|
||||
* modified an image to create your own and run it
|
||||
* create a Docker Hub account and repository
|
||||
* pushed your image to Docker Hub for others to share
|
||||
|
||||
<a href="https://twitter.com/intent/tweet?button_hashtag=dockerdocs&text=Just%20ran%20a%20container%20with%20an%20image%20I%20built.%20Find%20it%20on%20%23dockerhub.%20Build%20your%20own%3A%20http%3A%2F%2Fgoo.gl%2FMUi7cA" class="twitter-hashtag-button" data-size="large" data-related="docker" target="_blank">Tweet your accomplishment!</a>
|
||||
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
|
||||
|
||||
You've only scratched the surface of what Docker can do. Go to the next page to [learn more](last_page.md).
|
||||
|
||||
|
||||
|
143
docs/getstarted/step_three.md
Normal file
|
@ -0,0 +1,143 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
aliases = [
|
||||
"/mac/step_three/",
|
||||
"/windows/step_three/",
|
||||
"/linux/step_three/",
|
||||
]
|
||||
title = "Find & run the whalesay image"
|
||||
description = "Getting started with Docker"
|
||||
keywords = ["beginner, getting started, Docker"]
|
||||
[menu.main]
|
||||
identifier = "getstart_locate"
|
||||
parent = "getstart_all"
|
||||
weight = 3
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# Find and run the whalesay image
|
||||
|
||||
People all over the world create Docker images. You can find these images by
|
||||
browsing the Docker Hub. In this next section, you'll search for and find the
|
||||
image you'll use in the rest of this getting started.
|
||||
|
||||
## Step 1: Locate the whalesay image
|
||||
|
||||
1. Open your browser and <a href="https://hub.docker.com/?utm_source=getting_started_guide&utm_medium=embedded_MacOSX&utm_campaign=find_whalesay" target=_blank> browse to the Docker Hub</a>.
|
||||
|
||||
![Browse Docker Hub](tutimg/browse_and_search.png)
|
||||
|
||||
The Docker Hub contains images from individuals like you and official images
|
||||
from organizations like RedHat, IBM, Google, and a whole lot more.
|
||||
|
||||
2. Click **Browse & Search**.
|
||||
|
||||
The browser opens the search page.
|
||||
|
||||
3. Enter the word `whalesay` in the search bar.
|
||||
|
||||
![Browse Docker Hub](tutimg/image_found.png)
|
||||
|
||||
4. Click on the **docker/whalesay** image in the results.
|
||||
|
||||
The browser displays the repository for the **whalesay** image.
|
||||
|
||||
![Browse Docker Hub](tutimg/whale_repo.png)
|
||||
|
||||
Each image repository contains information about an image. It should
|
||||
include information such as what kind of software the image contains and
|
||||
how to use it. You may notice that the **whalesay** image is based on a
|
||||
Linux distribution called Ubuntu. In the next step, you run the **whalesay** image on your machine.
|
||||
|
||||
## Step 2: Run the whalesay image
|
||||
|
||||
Make sure Docker is running. On Docker for Mac and Docker for Windows, this is indicated by the Docker whale showing in the status bar.
|
||||
|
||||
1. Open a command-line terminal.
|
||||
|
||||
2. Type the `docker run docker/whalesay cowsay boo` command and press RETURN.
|
||||
|
||||
This command runs the **whalesay** image in a container. Your terminal should look like the following:
|
||||
|
||||
$ docker run docker/whalesay cowsay boo
|
||||
Unable to find image 'docker/whalesay:latest' locally
|
||||
latest: Pulling from docker/whalesay
|
||||
e9e06b06e14c: Pull complete
|
||||
a82efea989f9: Pull complete
|
||||
37bea4ee0c81: Pull complete
|
||||
07f8e8c5e660: Pull complete
|
||||
676c4a1897e6: Pull complete
|
||||
5b74edbcaa5b: Pull complete
|
||||
1722f41ddcb5: Pull complete
|
||||
99da72cfe067: Pull complete
|
||||
5d5bd9951e26: Pull complete
|
||||
fb434121fc77: Already exists
|
||||
Digest: sha256:d6ee73f978a366cf97974115abe9c4099ed59c6f75c23d03c64446bb9cd49163
|
||||
Status: Downloaded newer image for docker/whalesay:latest
|
||||
_____
|
||||
< boo >
|
||||
-----
|
||||
\
|
||||
\
|
||||
\
|
||||
## .
|
||||
## ## ## ==
|
||||
## ## ## ## ===
|
||||
/""""""""""""""""___/ ===
|
||||
~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
|
||||
\______ o __/
|
||||
\ \ __/
|
||||
\____\______/
|
||||
|
||||
The first time you run a software image, the `docker` command looks for it
|
||||
on your local system. If the image isn't there, then `docker` gets it from
|
||||
the hub.
|
||||
|
||||
5. While still in the command line terminal, type `docker images` command and press RETURN.
|
||||
|
||||
The command lists all the images on your local system. You should see
|
||||
`docker/whalesay` in the list.
|
||||
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
|
||||
docker/whalesay latest fb434121fc77 3 hours ago 247 MB
|
||||
hello-world latest 91c95931e552 5 weeks ago 910 B
|
||||
|
||||
When you run an image in a container, Docker downloads the image to your
|
||||
computer. This local copy of the image saves you time. Docker only
|
||||
downloads the image again if the image's source changes on the hub. You
|
||||
can, of course, delete the image yourself. You'll learn more about that
|
||||
later. Let's leave the image there for now because we are going to use it
|
||||
later.
|
||||
|
||||
6. Take a moment to play with the **whalesay** container a bit.
|
||||
|
||||
Try running the `whalesay` image again with a word or phrase. Try a long or
|
||||
short phrase. Can you break the cow?
|
||||
|
||||
$ docker run docker/whalesay cowsay boo-boo
|
||||
_________
|
||||
< boo-boo >
|
||||
---------
|
||||
\
|
||||
\
|
||||
\
|
||||
## .
|
||||
## ## ## ==
|
||||
## ## ## ## ===
|
||||
/""""""""""""""""___/ ===
|
||||
~~~ {~~ ~~~~ ~~~ ~~~~ ~~ ~ / ===- ~~~
|
||||
\______ o __/
|
||||
\ \ __/
|
||||
\____\______/
|
||||
|
||||
## Where to go next
|
||||
|
||||
On this page, you learned to search for images on Docker Hub. You used your
|
||||
command line to run an image. Think about it, effectively you ran a piece of
|
||||
Linux software on your Mac computer. You learned that running an image copies
|
||||
it on your computer. Now, you are ready to create your own Docker image.
|
||||
Go on to the next part [to build your own image](step_four.md).
|
||||
|
||||
|
||||
|
46
docs/getstarted/step_two.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
<!--[metadata]>
|
||||
+++
|
||||
aliases = [
|
||||
"/mac/step_two/",
|
||||
"/windows/step_two/",
|
||||
"/linux/step_two/",
|
||||
]
|
||||
title = "Understand images & containers"
|
||||
description = "Getting started with Docker"
|
||||
keywords = ["beginner, getting started, Docker"]
|
||||
[menu.main]
|
||||
identifier = "getstart_understand"
|
||||
parent = "getstart_all"
|
||||
weight = 2
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
# Learn about images & containers
|
||||
|
||||
Docker Engine provides the core Docker technology that enables images and
|
||||
containers. As the last step in your installation, you ran the
|
||||
`docker run hello-world` command. The command you ran had three parts.
|
||||
|
||||
![Container Explainer](tutimg/container_explainer.png)
|
||||
|
||||
An *image* is a filesystem and parameters to use at runtime. It doesn't have
|
||||
state and never changes. A *container* is a running instance of an image.
|
||||
When you ran the command, Docker Engine:
|
||||
|
||||
* checked to see if you had the `hello-world` software image
|
||||
* downloaded the image from the Docker Hub (more about the hub later)
|
||||
* loaded the image into the container and "ran" it
|
||||
|
||||
Depending on how it was built, an image might run a simple, single command and then exit. This is what `Hello-World` did.
|
||||
|
||||
A Docker image, though, is capable of much more. An image can start software as complex as a database, wait for you (or someone else) to add data, store the data for later use, and then wait for the next person.
|
||||
|
||||
Who built the `hello-world` software image though? In this case, Docker did but anyone can. Docker Engine lets people (or companies) create and share software through Docker images. Using Docker Engine, you don't have to worry about whether your computer can run the software in a Docker image — a Docker container *can always run it*.
|
||||
|
||||
## Where to go next
|
||||
|
||||
See, that was quick wasn't it? Now, you are ready to do some really fun stuff with Docker.
|
||||
Go on to the next part [to find and run the whalesay image](step_three.md).
|
||||
|
||||
|
||||
|
BIN
docs/getstarted/tutimg/add_repository.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
docs/getstarted/tutimg/browse_and_search.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
docs/getstarted/tutimg/container_explainer.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
docs/getstarted/tutimg/hub_signup.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
docs/getstarted/tutimg/image_found.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
docs/getstarted/tutimg/line_one.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
docs/getstarted/tutimg/new_image.png
Normal file
After Width: | Height: | Size: 69 KiB |
BIN
docs/getstarted/tutimg/tagger.png
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
docs/getstarted/tutimg/whale_repo.png
Normal file
After Width: | Height: | Size: 136 KiB |
|
@ -57,7 +57,7 @@ package manager.
|
|||
$ sudo tee /etc/yum.repos.d/docker.repo <<-'EOF'
|
||||
[dockerrepo]
|
||||
name=Docker Repository
|
||||
baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/
|
||||
baseurl=https://yum.dockerproject.org/repo/main/centos/7/
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
gpgkey=https://yum.dockerproject.org/gpg
|
||||
|
|
|
@ -45,19 +45,22 @@ storage driver and a node that is part of a 2 node Swarm cluster:
|
|||
Paused: 1
|
||||
Stopped: 10
|
||||
Images: 52
|
||||
Server Version: 1.11.1
|
||||
Server Version: 1.12.0-dev
|
||||
Storage Driver: overlay
|
||||
Backing Filesystem: extfs
|
||||
Logging Driver: json-file
|
||||
Cgroup Driver: cgroupfs
|
||||
Plugins:
|
||||
Volume: local
|
||||
Network: bridge null host
|
||||
Network: bridge null host overlay
|
||||
Swarm:
|
||||
NodeID: 0gac67oclbxq7
|
||||
IsManager: YES
|
||||
Managers: 2
|
||||
Nodes: 2
|
||||
Runtimes: default
|
||||
Default Runtime: default
|
||||
Security Options: apparmor seccomp
|
||||
Kernel Version: 4.4.0-21-generic
|
||||
Operating System: Ubuntu 16.04 LTS
|
||||
OSType: linux
|
||||
|
|
|
@ -30,17 +30,17 @@ Example output:
|
|||
|
||||
$ docker node inspect swarm-manager
|
||||
[
|
||||
{
|
||||
"ID": "0gac67oclbxq7",
|
||||
{
|
||||
"ID": "e216jshn25ckzbvmwlnh5jr3g",
|
||||
"Version": {
|
||||
"Index": 2028
|
||||
"Index": 10
|
||||
},
|
||||
"CreatedAt": "2016-06-06T20:49:32.720047494Z",
|
||||
"UpdatedAt": "2016-06-07T00:23:31.207632893Z",
|
||||
"CreatedAt": "2016-06-16T22:52:44.9910662Z",
|
||||
"UpdatedAt": "2016-06-16T22:52:45.230878043Z",
|
||||
"Spec": {
|
||||
"Role": "MANAGER",
|
||||
"Membership": "ACCEPTED",
|
||||
"Availability": "ACTIVE"
|
||||
"Role": "manager",
|
||||
"Membership": "accepted",
|
||||
"Availability": "active"
|
||||
},
|
||||
"Description": {
|
||||
"Hostname": "swarm-manager",
|
||||
|
@ -50,38 +50,55 @@ Example output:
|
|||
},
|
||||
"Resources": {
|
||||
"NanoCPUs": 1000000000,
|
||||
"MemoryBytes": 1044250624
|
||||
"MemoryBytes": 1039843328
|
||||
},
|
||||
"Engine": {
|
||||
"EngineVersion": "1.12.0",
|
||||
"Labels": {
|
||||
"provider": "virtualbox"
|
||||
}
|
||||
"Plugins": [
|
||||
{
|
||||
"Type": "Volume",
|
||||
"Name": "local"
|
||||
},
|
||||
{
|
||||
"Type": "Network",
|
||||
"Name": "overlay"
|
||||
},
|
||||
{
|
||||
"Type": "Network",
|
||||
"Name": "null"
|
||||
},
|
||||
{
|
||||
"Type": "Network",
|
||||
"Name": "host"
|
||||
},
|
||||
{
|
||||
"Type": "Network",
|
||||
"Name": "bridge"
|
||||
},
|
||||
{
|
||||
"Type": "Network",
|
||||
"Name": "overlay"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"Status": {
|
||||
"State": "READY"
|
||||
"State": "ready"
|
||||
},
|
||||
"Manager": {
|
||||
"Raft": {
|
||||
"RaftID": 2143745093569717375,
|
||||
"Addr": "192.168.99.118:4500",
|
||||
"Status": {
|
||||
"Leader": true,
|
||||
"Reachability": "REACHABLE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Attachment": {},
|
||||
}
|
||||
"ManagerStatus": {
|
||||
"Leader": true,
|
||||
"Reachability": "reachable",
|
||||
"Addr": "168.0.32.137:2377"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
$ docker node inspect --format '{{ .Manager.Raft.Status.Leader }}' self
|
||||
$ docker node inspect --format '{{ .ManagerStatus.Leader }}' self
|
||||
false
|
||||
|
||||
$ docker node inspect --pretty self
|
||||
ID: 2otfhz83efcc7
|
||||
Hostname: ad960a848573
|
||||
ID: e216jshn25ckzbvmwlnh5jr3g
|
||||
Hostname: swarm-manager
|
||||
Status:
|
||||
State: Ready
|
||||
Availability: Active
|
||||
|
|
|
@ -29,10 +29,10 @@ Lists all the nodes that the Docker Swarm manager knows about. You can filter us
|
|||
Example output:
|
||||
|
||||
$ docker node ls
|
||||
ID NAME STATUS AVAILABILITY MANAGER STATUS LEADER
|
||||
0gac67oclbxq swarm-master Ready Active Reachable Yes
|
||||
0pwvm3ve66q7 swarm-node-02 Ready Active
|
||||
15xwihgw71aw * swarm-node-01 Ready Active Reachable
|
||||
ID NAME MEMBERSHIP STATUS AVAILABILITY MANAGER STATUS LEADER
|
||||
1bcef6utixb0l0ca7gxuivsj0 swarm-worker2 Accepted Ready Active
|
||||
38ciaotwjuritcdtn9npbnkuz swarm-worker1 Accepted Ready Active
|
||||
e216jshn25ckzbvmwlnh5jr3g * swarm-manager1 Accepted Ready Active Reachable Yes
|
||||
|
||||
|
||||
## Filtering
|
||||
|
@ -49,22 +49,21 @@ The currently supported filters are:
|
|||
|
||||
### name
|
||||
|
||||
The `name` filter matches on all or part of a tasks's name.
|
||||
The `name` filter matches on all or part of a node name.
|
||||
|
||||
The following filter matches the node with a name equal to `swarm-master` string.
|
||||
|
||||
$ docker node ls -f name=swarm-master
|
||||
ID NAME STATUS AVAILABILITY MANAGER STATUS LEADER
|
||||
0gac67oclbxq * swarm-master Ready Active Reachable Yes
|
||||
$ docker node ls -f name=swarm-manager1
|
||||
ID NAME MEMBERSHIP STATUS AVAILABILITY MANAGER STATUS LEADER
|
||||
e216jshn25ckzbvmwlnh5jr3g * swarm-manager1 Accepted Ready Active Reachable Yes
|
||||
|
||||
### id
|
||||
|
||||
The `id` filter matches all or part of a node's id.
|
||||
|
||||
$ docker node ls -f id=0
|
||||
ID NAME STATUS AVAILABILITY MANAGER STATUS LEADER
|
||||
0gac67oclbxq * swarm-master Ready Active Reachable Yes
|
||||
0pwvm3ve66q7 swarm-node-02 Ready Active
|
||||
$ docker node ls -f id=1
|
||||
ID NAME MEMBERSHIP STATUS AVAILABILITY MANAGER STATUS LEADER
|
||||
1bcef6utixb0l0ca7gxuivsj0 swarm-worker2 Accepted Ready Active
|
||||
|
||||
|
||||
#### label
|
||||
|
@ -76,8 +75,8 @@ The following filter matches nodes with the `usage` label regardless of its valu
|
|||
|
||||
```bash
|
||||
$ docker node ls -f "label=foo"
|
||||
ID NAME STATUS AVAILABILITY MANAGER STATUS LEADER
|
||||
15xwihgw71aw * swarm-node-01 Ready Active Reachable
|
||||
ID NAME MEMBERSHIP STATUS AVAILABILITY MANAGER STATUS LEADER
|
||||
1bcef6utixb0l0ca7gxuivsj0 swarm-worker2 Accepted Ready Active
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -26,13 +26,13 @@ Lists all the tasks on a Node that Docker knows about. You can filter using the
|
|||
|
||||
Example output:
|
||||
|
||||
$ docker node tasks swarm-master
|
||||
ID NAME SERVICE IMAGE DESIRED STATE LAST STATE NODE
|
||||
dx2g0fe3zsdb6y6q453f8dqw2 redis.1 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
f33pcf8lwhs4c1t4kq8szwzta redis.4 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
5v26yzixl3one3ptjyqqbd0ro redis.5 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
adcaphlhsfr30d47lby6walg6 redis.8 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
chancjvk9tex6768uzzacslq2 redis.9 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
$ docker node tasks swarm-manager1
|
||||
ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE
|
||||
7q92v0nr1hcgts2amcjyqg3pq redis.1 redis redis:3.0.6 Running 5 hours Running swarm-manager1
|
||||
b465edgho06e318egmgjbqo4o redis.6 redis redis:3.0.6 Running 29 seconds Running swarm-manager1
|
||||
bg8c07zzg87di2mufeq51a2qp redis.7 redis redis:3.0.6 Running 5 seconds Running swarm-manager1
|
||||
dkkual96p4bb3s6b10r7coxxt redis.9 redis redis:3.0.6 Running 5 seconds Running swarm-manager1
|
||||
0tgctg8h8cech4w0k0gwrmr23 redis.10 redis redis:3.0.6 Running 5 seconds Running swarm-manager1
|
||||
|
||||
|
||||
## Filtering
|
||||
|
@ -53,22 +53,22 @@ The `name` filter matches on all or part of a task's name.
|
|||
|
||||
The following filter matches all tasks with a name containing the `redis` string.
|
||||
|
||||
$ docker node tasks -f name=redis swarm-master
|
||||
ID NAME SERVICE IMAGE DESIRED STATE LAST STATE NODE
|
||||
dx2g0fe3zsdb6y6q453f8dqw2 redis.1 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
f33pcf8lwhs4c1t4kq8szwzta redis.4 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
5v26yzixl3one3ptjyqqbd0ro redis.5 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
adcaphlhsfr30d47lby6walg6 redis.8 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
chancjvk9tex6768uzzacslq2 redis.9 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
$ docker node tasks -f name=redis swarm-manager1
|
||||
ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE
|
||||
7q92v0nr1hcgts2amcjyqg3pq redis.1 redis redis:3.0.6 Running 5 hours Running swarm-manager1
|
||||
b465edgho06e318egmgjbqo4o redis.6 redis redis:3.0.6 Running 29 seconds Running swarm-manager1
|
||||
bg8c07zzg87di2mufeq51a2qp redis.7 redis redis:3.0.6 Running 5 seconds Running swarm-manager1
|
||||
dkkual96p4bb3s6b10r7coxxt redis.9 redis redis:3.0.6 Running 5 seconds Running swarm-manager1
|
||||
0tgctg8h8cech4w0k0gwrmr23 redis.10 redis redis:3.0.6 Running 5 seconds Running swarm-manager1
|
||||
|
||||
|
||||
### id
|
||||
|
||||
The `id` filter matches a task's id.
|
||||
|
||||
$ docker node tasks -f id=f33pcf8lwhs4c1t4kq8szwzta swarm-master
|
||||
ID NAME SERVICE IMAGE DESIRED STATE LAST STATE NODE
|
||||
f33pcf8lwhs4c1t4kq8szwzta redis.4 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
$ docker node tasks -f id=bg8c07zzg87di2mufeq51a2qp swarm-manager1
|
||||
ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE
|
||||
bg8c07zzg87di2mufeq51a2qp redis.7 redis redis:3.0.6 Running 5 seconds Running swarm-manager1
|
||||
|
||||
|
||||
#### label
|
||||
|
@ -80,9 +80,9 @@ The following filter matches tasks with the `usage` label regardless of its valu
|
|||
|
||||
```bash
|
||||
$ docker node tasks -f "label=usage"
|
||||
ID NAME SERVICE IMAGE DESIRED STATE LAST STATE NODE
|
||||
dx2g0fe3zsdb6y6q453f8dqw2 redis.1 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
f33pcf8lwhs4c1t4kq8szwzta redis.4 redis redis:3.0.6 RUNNING RUNNING 2 hours swarm-master
|
||||
ID NAME SERVICE IMAGE LAST STATE DESIRED STATE NODE
|
||||
b465edgho06e318egmgjbqo4o redis.6 redis redis:3.0.6 Running 10 minutes Running swarm-manager1
|
||||
bg8c07zzg87di2mufeq51a2qp redis.7 redis redis:3.0.6 Running 9 minutes Running swarm-manager1
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -16,6 +16,12 @@ parent = "smn_cli"
|
|||
|
||||
Update a node
|
||||
|
||||
Options:
|
||||
--availability string Availability of the node (active/pause/drain)
|
||||
--help Print usage
|
||||
--membership string Membership of the node (accepted/rejected)
|
||||
--role string Role of the node (worker/manager)
|
||||
|
||||
|
||||
|
||||
## Related information
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
title = "plugin disable"
|
||||
description = "the plugin disable command description and usage"
|
||||
keywords = ["plugin, disable"]
|
||||
advisory = "experimental"
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
advisory = "experimental"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
title = "plugin enable"
|
||||
description = "the plugin enable command description and usage"
|
||||
keywords = ["plugin, enable"]
|
||||
advisory = "experimental"
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
advisory = "experimental"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
title = "plugin inspect"
|
||||
description = "The plugin inspect command description and usage"
|
||||
keywords = ["plugin, inspect"]
|
||||
advisory = "experimental"
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
advisory = "experimental"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
title = "plugin install"
|
||||
description = "the plugin install command description and usage"
|
||||
keywords = ["plugin, install"]
|
||||
advisory = "experimental"
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
advisory = "experimental"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
title = "plugin ls"
|
||||
description = "The plugin ls command description and usage"
|
||||
keywords = ["plugin, list"]
|
||||
advisory = "experimental"
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
advisory = "experimental"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
title = "plugin rm"
|
||||
description = "the plugin rm command description and usage"
|
||||
keywords = ["plugin, rm"]
|
||||
advisory = "experimental"
|
||||
[menu.main]
|
||||
parent = "smn_cli"
|
||||
advisory = "experimental"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ parent = "smn_cli"
|
|||
--help Print usage
|
||||
--secret string Set secret value needed to accept nodes into cluster
|
||||
--task-history-limit int Task history retention limit (default 10)
|
||||
--cert-expiry duration Validity period for node certificates (default 2160h0m0s)
|
||||
|
||||
Updates a Swarm cluster with new parameter values. This command must target a manager node.
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Swarm overview"
|
||||
description = "Docker Swarm overview"
|
||||
keywords = ["docker, container, cluster, swarm"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="swarm_overview"
|
||||
parent="engine_swarm"
|
||||
weight="1"
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
# Docker Swarm overview
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Swarm key concepts"
|
||||
description = "Introducing key concepts for Docker Swarm"
|
||||
keywords = ["docker, container, cluster, swarm"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="swarm-concepts"
|
||||
parent="engine_swarm"
|
||||
weight="2"
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
# Docker Swarm key concepts
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Manage a Swarm (1.12 RC)"
|
||||
description = "How to use Docker Swarm to create and manage Docker Engine clusters"
|
||||
keywords = [" docker, documentation, developer, "]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier = "engine_swarm"
|
||||
parent = "engine_use"
|
||||
weight = 0
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Add nodes to the Swarm"
|
||||
description = "Add nodes to the Swarm"
|
||||
keywords = ["tutorial, cluster management, swarm"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="add-nodes"
|
||||
parent="swarm-tutorial"
|
||||
weight=13
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Create a Swarm"
|
||||
description = "Initialize the Swarm"
|
||||
keywords = ["tutorial, cluster management, swarm"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="initialize-swarm"
|
||||
parent="swarm-tutorial"
|
||||
weight=12
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Delete the service"
|
||||
description = "Remove the service on the Swarm"
|
||||
keywords = ["tutorial, cluster management, swarm, service"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="swarm-tutorial-delete-service"
|
||||
parent="swarm-tutorial"
|
||||
weight=19
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Deploy a service"
|
||||
description = "Deploy the application"
|
||||
keywords = ["tutorial, cluster management, swarm"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="deploy-application"
|
||||
parent="swarm-tutorial"
|
||||
weight=16
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
@ -39,8 +39,8 @@ example, the tutorial uses a machine named `manager1`.
|
|||
```
|
||||
$ docker service ls
|
||||
|
||||
ID NAME SCALE IMAGE COMMAND
|
||||
2zs4helqu64f helloworld 1 alpine ping docker.com
|
||||
ID NAME REPLICAS IMAGE COMMAND
|
||||
2zs4helqu64f helloworld 1 alpine ping docker.com
|
||||
```
|
||||
|
||||
## What's next?
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
title = "Drain a node"
|
||||
description = "Drain nodes on the Swarm"
|
||||
keywords = ["tutorial, cluster management, swarm, service, drain"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="swarm-tutorial-drain-node"
|
||||
parent="swarm-tutorial"
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Set up for the tutorial"
|
||||
description = "Getting Started tutorial for Docker Swarm"
|
||||
keywords = ["tutorial, cluster management, swarm"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="tutorial-setup"
|
||||
parent="swarm-tutorial"
|
||||
weight=11
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Inspect the service"
|
||||
description = "Inspect the application"
|
||||
keywords = ["tutorial, cluster management, swarm"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="inspect-application"
|
||||
parent="swarm-tutorial"
|
||||
weight=17
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Get started with Swarm"
|
||||
description = "Getting started tutorial for Docker Swarm"
|
||||
keywords = ["cluster, swarm, tutorial"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="swarm-tutorial"
|
||||
parent="engine_swarm"
|
||||
weight=10
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Apply rolling updates"
|
||||
description = "Apply rolling updates to a service on the Swarm"
|
||||
keywords = ["tutorial, cluster management, swarm, service, rolling-update"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="swarm-tutorial-rolling-update"
|
||||
parent="swarm-tutorial"
|
||||
weight=20
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
title = "Scale the service"
|
||||
description = "Scale the service running in the Swarm"
|
||||
keywords = ["tutorial, cluster management, swarm, scale"]
|
||||
advisory = "rc"
|
||||
[menu.main]
|
||||
identifier="swarm-tutorial-scale-service"
|
||||
parent="swarm-tutorial"
|
||||
weight=18
|
||||
advisory = "rc"
|
||||
+++
|
||||
<![end-metadata]-->
|
||||
|
||||
|
|
|
@ -49,9 +49,9 @@ refined or entirely removed.
|
|||
To download the latest experimental `docker` binary for Linux,
|
||||
use the following URLs:
|
||||
|
||||
https://experimental.docker.com/builds/Linux/i386/docker-latest
|
||||
https://experimental.docker.com/builds/Linux/i386/docker-latest.tgz
|
||||
|
||||
https://experimental.docker.com/builds/Linux/x86_64/docker-latest
|
||||
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/installation/binaries/#get-the-docker-binary) to run the `docker` daemon.
|
||||
|
|