diff --git a/Makefile b/Makefile index 406355c1205..341304f463b 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ BUILDFLAGS := -tags "$(AUTOTAGS) $(TAGS)" all: buildah docs -buildah: *.go cmd/buildah/*.go +buildah: *.go imagebuildah/*.go cmd/buildah/*.go go build -o buildah $(BUILDFLAGS) ./cmd/buildah .PHONY: clean diff --git a/README.md b/README.md index e8479970e98..090088b9dfc 100644 --- a/README.md +++ b/README.md @@ -56,4 +56,4 @@ Then to install buildah follow the steps in this example: **Future goals include:** * docs * more CI tests -* additional CLI commands (build?) +* additional CLI commands (?) diff --git a/cmd/buildah/bud.go b/cmd/buildah/bud.go new file mode 100644 index 00000000000..d963a6ebcb2 --- /dev/null +++ b/cmd/buildah/bud.go @@ -0,0 +1,176 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/containers/storage/pkg/archive" + "github.com/projectatomic/buildah/imagebuildah" + "github.com/urfave/cli" +) + +var ( + budFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "quiet, q", + Usage: "refrain from announcing build instructions", + }, + cli.StringFlag{ + Name: "registry", + Usage: "prefix to prepend to the image name in order to pull the image", + Value: DefaultRegistry, + }, + cli.BoolTFlag{ + Name: "pull", + Usage: "pull the image if not present", + }, + cli.BoolFlag{ + Name: "pull-always", + Usage: "pull the image, even if a version is present", + }, + cli.StringFlag{ + Name: "signature-policy", + Usage: "`pathname` of signature policy file (not usually used)", + }, + cli.StringSliceFlag{ + Name: "build-arg", + Usage: "`argument=value` to supply to the builder", + }, + cli.StringFlag{ + Name: "tag, t", + Usage: "`tag` to apply to the built image", + }, + cli.StringSliceFlag{ + Name: "file, f", + Usage: "`pathname or URL` of a Dockerfile", + }, + } + budDescription = "Builds an OCI image using instructions in one or more Dockerfiles." + budCommand = cli.Command{ + Name: "build-using-dockerfile", + Aliases: []string{"bud"}, + Usage: "Build an image using instructions in a Dockerfile", + Description: budDescription, + Flags: budFlags, + Action: budCmd, + ArgsUsage: "CONTEXT-DIRECTORY | URL", + } +) + +func budCmd(c *cli.Context) error { + output := "" + if c.IsSet("tag") { + output = c.String("tag") + } + registry := DefaultRegistry + if c.IsSet("registry") { + registry = c.String("registry") + } + pull := true + if c.IsSet("pull") { + pull = c.BoolT("pull") + } + pullAlways := false + if c.IsSet("pull-always") { + pull = c.Bool("pull-always") + } + signaturePolicy := "" + if c.IsSet("signature-policy") { + signaturePolicy = c.String("signature-policy") + } + args := make(map[string]string) + if c.IsSet("build-arg") { + for _, arg := range c.StringSlice("build-arg") { + av := strings.SplitN(arg, "=", 2) + if len(av) > 1 { + args[av[0]] = av[1] + } else { + delete(args, av[0]) + } + } + } + quiet := false + if c.IsSet("quiet") { + quiet = c.Bool("quiet") + } + dockerfiles := []string{} + if c.IsSet("file") || c.IsSet("f") { + dockerfiles = c.StringSlice("file") + } + contextDir := "" + cliArgs := c.Args() + if len(cliArgs) > 0 { + // The context directory could be a URL. Try to handle that. + tempDir, subDir, err := imagebuildah.TempDirForURL("", "buildah", cliArgs[0]) + if err != nil { + return fmt.Errorf("error prepping temporary context directory: %v", err) + } + if tempDir != "" { + // We had to download it to a temporary directory. + // Delete it later. + defer func() { + if err = os.RemoveAll(tempDir); err != nil { + logrus.Errorf("error removing temporary directory %q: %v", contextDir, err) + } + }() + contextDir = filepath.Join(tempDir, subDir) + } else { + // Nope, it was local. Use it as is. + absDir, err := filepath.Abs(cliArgs[0]) + if err != nil { + return fmt.Errorf("error determining path to directory %q: %v", cliArgs[0], err) + } + contextDir = absDir + } + cliArgs = cliArgs.Tail() + } else { + // No context directory or URL was specified. Try to use the + // home of the first locally-available Dockerfile. + for i := range dockerfiles { + if strings.HasPrefix(dockerfiles[i], "http://") || + strings.HasPrefix(dockerfiles[i], "https://") || + strings.HasPrefix(dockerfiles[i], "git://") || + strings.HasPrefix(dockerfiles[i], "github.com/") { + continue + } + absFile, err := filepath.Abs(dockerfiles[i]) + if err != nil { + return fmt.Errorf("error determining path to file %q: %v", dockerfiles[i], err) + } + contextDir = filepath.Dir(absFile) + dockerfiles[i], err = filepath.Rel(contextDir, absFile) + if err != nil { + return fmt.Errorf("error determining path to file %q: %v", dockerfiles[i], err) + } + break + } + } + if contextDir == "" { + return fmt.Errorf("no context directory specified, and no dockerfile specified") + } + if len(dockerfiles) == 0 { + dockerfiles = append(dockerfiles, filepath.Join(contextDir, "Dockerfile")) + } + + store, err := getStore(c) + if err != nil { + return err + } + + options := imagebuildah.BuildOptions{ + ContextDirectory: contextDir, + PullIfMissing: pull, + PullAlways: pullAlways, + Registry: registry, + Compression: archive.Gzip, + Quiet: quiet, + SignaturePolicyPath: signaturePolicy, + Args: args, + Output: output, + } + + return imagebuildah.BuildDockerfiles(store, options, dockerfiles...) +} diff --git a/cmd/buildah/config.go b/cmd/buildah/config.go index 401d193ac74..7395f0de9ad 100644 --- a/cmd/buildah/config.go +++ b/cmd/buildah/config.go @@ -99,7 +99,7 @@ func updateConfig(builder *buildah.Builder, c *cli.Context) { if c.IsSet("user") { builder.User = c.String("user") } - if c.IsSet("port") { + if c.IsSet("port") || c.IsSet("p") { if builder.Expose == nil { builder.Expose = make(map[string]interface{}) } @@ -133,7 +133,7 @@ func updateConfig(builder *buildah.Builder, c *cli.Context) { builder.Volumes = append(builder.Volumes, volSpec...) } } - if c.IsSet("label") { + if c.IsSet("label") || c.IsSet("l") { if builder.Labels == nil { builder.Labels = make(map[string]string) } @@ -149,7 +149,7 @@ func updateConfig(builder *buildah.Builder, c *cli.Context) { if c.IsSet("workingdir") { builder.Workdir = c.String("workingdir") } - if c.IsSet("annotation") { + if c.IsSet("annotation") || c.IsSet("a") { if builder.Annotations == nil { builder.Annotations = make(map[string]string) } diff --git a/cmd/buildah/main.go b/cmd/buildah/main.go index f9f773197c0..6f1929f4978 100644 --- a/cmd/buildah/main.go +++ b/cmd/buildah/main.go @@ -81,6 +81,7 @@ func main() { umountCommand, imagesCommand, rmiCommand, + budCommand, } err := app.Run(os.Args) if err != nil { diff --git a/config.go b/config.go index d26335d7eda..c799b939f8a 100644 --- a/config.go +++ b/config.go @@ -133,3 +133,15 @@ func (b *Builder) updatedConfig() []byte { } return updatedImageConfig } + +// UpdatedEnv returns the environment list from the source image, with the +// builder's own list appended to it. +func (b *Builder) UpdatedEnv() []string { + config := b.updatedConfig() + image := ociv1.Image{} + if err := json.Unmarshal(config, &image); err != nil { + logrus.Errorf("error parsing updated image information") + return []string{} + } + return append(image.Config.Env, b.Env...) +} diff --git a/contrib/completions/bash/buildah b/contrib/completions/bash/buildah index 4062117ff95..c3224021206 100644 --- a/contrib/completions/bash/buildah +++ b/contrib/completions/bash/buildah @@ -323,6 +323,47 @@ return 1 esac } + _buildah_bud() { + local boolean_options=" + --help + -h + --pull + --pull-always + " + + local options_with_args=" + --registry + --signature-policy + --runtime + --runtime-flag + --tag + -t + --file + -f + --build-arg + " + + local all_options="$options_with_args $boolean_options" + + case "$prev" in + --runtime) + COMPREPLY=($(compgen -W 'runc runv' -- "$cur")) + ;; + $(__buildah_to_extglob "$options_with_args")) + return + ;; + esac + + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + esac +} + + _buildah_build_using_dockerfile() { + _buildah_bud "$@" +} _buildah_run() { local boolean_options=" @@ -437,7 +478,7 @@ return 1 -q --noheading -n - --no-truncate + --notruncate " local options_with_args=" @@ -460,7 +501,7 @@ return 1 -q --noheading -n - --no-truncate + --notruncate " local options_with_args=" @@ -507,6 +548,8 @@ return 1 local commands=( add + bud + build-using-dockerfile commit config containers @@ -561,7 +604,7 @@ done local binary="${words[0]}" - local completions_func=_buildah_${command} + local completions_func=_buildah_${command/-/_} declare -F $completions_func >/dev/null && $completions_func eval "$previous_extglob_setting" diff --git a/docs/buildah-bud.md b/docs/buildah-bud.md new file mode 100644 index 00000000000..bed7f1fc4f8 --- /dev/null +++ b/docs/buildah-bud.md @@ -0,0 +1,79 @@ +## buildah-bud "1" "April 2017" "buildah" + +## NAME +buildah bud - Build an image using instructions from Dockerfiles. + +## SYNOPSIS +**buildah** **bud | build-using-dockerfile** [*options* [...]] [**context**] + +## DESCRIPTION +Builds an image using instructions from one or more Dockerfiles and a specified +build context directory. The build context directory can be specified as the +**http** or **https** URL of an archive which will be retrieved and extracted +to a temporary location. + +## OPTIONS + +**-f, --file** *Dockerfile* + +Specifies a Dockerfile which contains instructions for building the image, +either a local file or an **http** or **https** URL. If more than one +Dockerfile is specified, *FROM* instructions will only be accepted from the +first specified file. + +If a build context is not specified, and at least one Dockerfile is a +local file, the directory in which it resides will be used as the build +context. + +**--pull** + +Pull the image if it is not present. If this flag is disabled (with +*--pull=false*) and the image is not present, the image will not be pulled. +Defaults to *true*. + +**--pull-always** + +Pull the image even if a version of the image is already present. + +**--registry** *registry* + +A prefix to prepend to the image name in order to pull the image. Default +value is "docker://" + +**--signature-policy** *signaturepolicy* + +Pathname of a signature policy file to use. It is not recommended that this +option be used, as the default behavior of using the system-wide default policy +(frequently */etc/containers/policy.json*) is most often preferred. + +**--build-arg** *arg=value* + +Specifies a build argument and its value, which will be interpolated in +instructions read from the Dockerfiles in the same way that environment +variables are, but which will not be added to environment variable list in the +resulting image's configuration. + +**--runtime** *path* + +The *path* to an alternate runtime, which will be used to run commands +specified by the **RUN** instruction. + +**--runtime-flag** *flag* + +Adds global flags for the container rutime. + +**-t, --tag** *imageName* + +Specifies the name which will be assigned to the resulting image if the build +process completes successfully. + +## EXAMPLE + +buildah bud . +buildah bud -f Dockerfile.simple . +buildah bud -f Dockerfile.simple -f Dockerfile.notsosimple +buildah bud -t imageName . +buildah bud -t imageName -f Dockerfile.simple + +## SEE ALSO +buildah(1) diff --git a/docs/buildah.md b/docs/buildah.md index 6261e2a0aac..b3124ddec11 100644 --- a/docs/buildah.md +++ b/docs/buildah.md @@ -17,6 +17,7 @@ The buildah package provides a command line tool which can be used to: | Command | Description | | --------------------- | --------------------------------------------------- | | buildah-add(1) | Add the contents of a file, URL, or a directory to the container. | +| buildah-bud(1) | Build an image using instructions from Dockerfiles. | | buildah-commit(1) | Create an image from a working container. | | buildah-config(1) | Update image configuration settings. | | buildah-containers(1) | List the working containers and their base images. | diff --git a/imagebuildah/build.go b/imagebuildah/build.go new file mode 100644 index 00000000000..fe3207321e7 --- /dev/null +++ b/imagebuildah/build.go @@ -0,0 +1,575 @@ +package imagebuildah + +import ( + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/Sirupsen/logrus" + is "github.com/containers/image/storage" + "github.com/containers/image/transports" + "github.com/containers/image/transports/alltransports" + "github.com/containers/image/types" + "github.com/containers/storage/pkg/archive" + "github.com/containers/storage/pkg/stringid" + "github.com/containers/storage/storage" + "github.com/docker/docker/builder/dockerfile/parser" + docker "github.com/fsouza/go-dockerclient" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/openshift/imagebuilder" + "github.com/projectatomic/buildah" +) + +// BuildOptions can be used to alter how an image is built. +type BuildOptions struct { + // ContextDirectory is the default source location for COPY and ADD + // commands. + ContextDirectory string + // PullIfMissing signals to NewBuilder() that it should pull the image + // if it is not present in the local Store. + PullIfMissing bool + // PullAlways signals to NewBuilder() that it should pull the source + // image before creating the container. + PullAlways bool + // Registry is a value which is prepended to the image's name, if it + // needs to be pulled and the image name alone can not be resolved to a + // reference to a source image. + Registry string + // IgnoreUnrecognizedInstructions tells us to just log instructions we + // don't recognize, and try to keep going. + IgnoreUnrecognizedInstructions bool + // Quiet tells us whether or not to announce steps as we go through them. + Quiet bool + // Runtime is the name of the command to run for RUN instructions. It + // should accept the same arguments and flags that runc does. + Runtime string + // RuntimeArgs adds global arguments for the runtime. + RuntimeArgs []string + // TransientMounts is a list of mounts that won't be kept in the image. + TransientMounts []specs.Mount + // Compression specifies the type of compression which is applied to + // layer blobs. The default is to not use compression, but + // archive.Gzip is recommended. + Compression archive.Compression + // Arguments which can be interpolated into Dockerfiles + Args map[string]string + // Name of the image to write to. + Output string + // SignaturePolicyPath specifies an override location for the signature + // policy which should be used for verifying the new image as it is + // being written. Except in specific circumstances, no value should be + // specified, indicating that the shared, system-wide default policy + // should be used. + SignaturePolicyPath string +} + +// Executor is a buildah-based implementation of the imagebuilder.Executor +// interface. +type Executor struct { + store storage.Store + contextDir string + builder *buildah.Builder + pullIfMissing bool + pullAlways bool + registry string + ignoreUnrecognizedInstructions bool + counter int + quiet bool + runtime string + runtimeArgs []string + transientMounts []specs.Mount + compression archive.Compression + output string + signaturePolicyPath string + systemContext *types.SystemContext + mountPoint string + preserved int + volumes imagebuilder.VolumeSet + volumeCache map[string]string + volumeCacheInfo map[string]os.FileInfo +} + +func makeSystemContext(signaturePolicyPath string) *types.SystemContext { + sc := &types.SystemContext{} + if signaturePolicyPath != "" { + sc.SignaturePolicyPath = signaturePolicyPath + } + return sc +} + +// Preserve informs the executor that from this point on, it needs to ensure +// that only COPY and ADD instructions can modify the contents of this +// directory or anything below it. +// The Executor handles this by caching the contents of directories which have +// been marked this way before executing a RUN instruction, invalidating that +// cache when an ADD or COPY instruction sets any location under the directory +// as the destination, and using the cache to reset the contents of the +// directory tree after processing each RUN instruction. +// It would be simpler if we could just mark the directory as a read-only bind +// mount of itself during Run(), but the directory is expected to be remain +// writeable, even if any changes within it are ultimately discarded. +func (b *Executor) Preserve(path string) error { + logrus.Debugf("PRESERVE %q", path) + if b.volumes.Covers(path) { + // This path is already a subdirectory of a volume path that + // we're already preserving, so there's nothing new to be done. + return nil + } + // Figure out where the cache for this volume would be stored. + b.preserved++ + cacheDir, err := b.store.GetContainerDirectory(b.builder.ContainerID) + if err != nil { + return fmt.Errorf("unable to locate temporary directory for container") + } + cacheFile := filepath.Join(cacheDir, fmt.Sprintf("volume%d.tar", b.preserved)) + // Save info about the top level of the location that we'll be archiving. + archivedPath := filepath.Join(b.mountPoint, path) + st, err := os.Stat(archivedPath) + if err != nil { + logrus.Debugf("error reading info about %q: %v", archivedPath, err) + return err + } + b.volumeCacheInfo[path] = st + if !b.volumes.Add(path) { + // This path is not a subdirectory of a volume path that we're + // already preserving, so adding it to the list should work. + return fmt.Errorf("error adding %q to the volume cache", path) + } + b.volumeCache[path] = cacheFile + // Now prune cache files for volumes that are now supplanted by this one. + removed := []string{} + for cachedPath := range b.volumeCache { + // Walk our list of cached volumes, and check that they're + // still in the list of locations that we need to cache. + found := false + for _, volume := range b.volumes { + if volume == cachedPath { + // We need to keep this volume's cache. + found = true + break + } + } + if !found { + // We don't need to keep this volume's cache. Make a + // note to remove it. + removed = append(removed, cachedPath) + } + } + // Actually remove the caches that we decided to remove. + for _, cachedPath := range removed { + archivedPath := filepath.Join(b.mountPoint, cachedPath) + logrus.Debugf("no longer need cache of %q in %q", archivedPath, b.volumeCache[cachedPath]) + if err := os.Remove(b.volumeCache[cachedPath]); err != nil { + return fmt.Errorf("error removing %q: %v", b.volumeCache[cachedPath], err) + } + delete(b.volumeCache, cachedPath) + } + return nil +} + +// Remove any volume cache item which will need to be re-saved because we're +// writing to part of it. +func (b *Executor) volumeCacheInvalidate(path string) error { + invalidated := []string{} + for cachedPath := range b.volumeCache { + if strings.HasPrefix(path, cachedPath+string(os.PathSeparator)) { + invalidated = append(invalidated, cachedPath) + } + } + for _, cachedPath := range invalidated { + if err := os.Remove(b.volumeCache[cachedPath]); err != nil { + return fmt.Errorf("error removing volume cache %q: %v", b.volumeCache[cachedPath], err) + } + archivedPath := filepath.Join(b.mountPoint, cachedPath) + logrus.Debugf("invalidated volume cache for %q from %q", archivedPath, b.volumeCache[cachedPath]) + delete(b.volumeCache, cachedPath) + } + return nil +} + +// Save the contents of each of the executor's list of volumes for which we +// don't already have a cache file. +func (b *Executor) volumeCacheSave() error { + for cachedPath, cacheFile := range b.volumeCache { + archivedPath := filepath.Join(b.mountPoint, cachedPath) + _, err := os.Stat(cacheFile) + if err == nil { + logrus.Debugf("contents of volume %q are already cached in %q", archivedPath, cacheFile) + continue + } + if !os.IsNotExist(err) { + return fmt.Errorf("error checking for cache of %q in %q: %v", archivedPath, cacheFile, err) + } + logrus.Debugf("caching contents of volume %q in %q", archivedPath, cacheFile) + cache, err := os.Create(cacheFile) + if err != nil { + return fmt.Errorf("error creating archive at %q: %v", cacheFile, err) + } + defer cache.Close() + rc, err := archive.Tar(archivedPath, archive.Uncompressed) + if err != nil { + return fmt.Errorf("error archiving %q: %v", archivedPath, err) + } + defer rc.Close() + _, err = io.Copy(cache, rc) + if err != nil { + return fmt.Errorf("error archiving %q to %q: %v", archivedPath, cacheFile, err) + } + } + return nil +} + +// Restore the contents of each of the executor's list of volumes. +func (b *Executor) volumeCacheRestore() error { + for cachedPath, cacheFile := range b.volumeCache { + archivedPath := filepath.Join(b.mountPoint, cachedPath) + logrus.Debugf("restoring contents of volume %q from %q", archivedPath, cacheFile) + cache, err := os.Open(cacheFile) + if err != nil { + return fmt.Errorf("error opening archive at %q: %v", cacheFile, err) + } + defer cache.Close() + if err := os.RemoveAll(archivedPath); err != nil { + return fmt.Errorf("error clearing volume path %q: %v", archivedPath, err) + } + if err := os.MkdirAll(archivedPath, 0700); err != nil { + return fmt.Errorf("error recreating volume path %q: %v", archivedPath, err) + } + err = archive.Untar(cache, archivedPath, nil) + if err != nil { + return fmt.Errorf("error extracting archive at %q: %v", archivedPath, err) + } + if st, ok := b.volumeCacheInfo[cachedPath]; ok { + if err := os.Chmod(archivedPath, st.Mode()); err != nil { + return fmt.Errorf("error restoring permissions on %q: %v", archivedPath, err) + } + if err := os.Chown(archivedPath, 0, 0); err != nil { + return fmt.Errorf("error setting ownership on %q: %v", archivedPath, err) + } + if err := os.Chtimes(archivedPath, st.ModTime(), st.ModTime()); err != nil { + return fmt.Errorf("error restoring datestamps on %q: %v", archivedPath, err) + } + } + } + return nil +} + +// Copy copies data into the working tree. The "Download" field is how +// imagebuilder tells us the instruction was "ADD" and not "COPY". +func (b *Executor) Copy(excludes []string, copies ...imagebuilder.Copy) error { + for _, copy := range copies { + logrus.Debugf("COPY %#v, %#v", excludes, copy) + if err := b.volumeCacheInvalidate(copy.Dest); err != nil { + return err + } + sources := []string{} + for _, src := range copy.Src { + if strings.HasPrefix(src, "http://") || strings.HasPrefix(src, "https://") { + sources = append(sources, src) + } else { + sources = append(sources, filepath.Join(b.contextDir, src)) + } + } + if err := b.builder.Add(copy.Dest, copy.Download, sources...); err != nil { + return err + } + } + return nil +} + +// Run executes a RUN instruction using the working container as a root +// directory. +func (b *Executor) Run(run imagebuilder.Run, config docker.Config) error { + logrus.Debugf("RUN %#v, %#v", run, config) + if b.builder == nil { + return fmt.Errorf("no build container available") + } + options := buildah.RunOptions{ + Hostname: config.Hostname, + Runtime: b.runtime, + Args: b.runtimeArgs, + Mounts: b.transientMounts, + Env: config.Env, + User: config.User, + WorkingDir: config.WorkingDir, + Entrypoint: config.Entrypoint, + Cmd: config.Cmd, + NetworkDisabled: config.NetworkDisabled, + } + + args := run.Args + if run.Shell { + args = append([]string{"/bin/sh", "-c"}, args...) + } + if err := b.volumeCacheSave(); err != nil { + return err + } + err := b.builder.Run(args, options) + if err2 := b.volumeCacheRestore(); err2 != nil { + if err == nil { + return err2 + } + } + return err +} + +// UnrecognizedInstruction is called when we encounter an instruction that the +// imagebuilder parser didn't understand. +func (b *Executor) UnrecognizedInstruction(step *imagebuilder.Step) error { + if !b.ignoreUnrecognizedInstructions { + logrus.Debugf("+(UNIMPLEMENTED?) %#v", step) + return nil + } + logrus.Errorf("+(UNIMPLEMENTED?) %#v", step) + return fmt.Errorf("Unrecognized instruction: %#v", step) +} + +// NewExecutor creates a new instance of the imagebuilder.Executor interface. +func NewExecutor(store storage.Store, options BuildOptions) (*Executor, error) { + exec := Executor{ + store: store, + contextDir: options.ContextDirectory, + pullIfMissing: options.PullIfMissing, + pullAlways: options.PullAlways, + registry: options.Registry, + ignoreUnrecognizedInstructions: options.IgnoreUnrecognizedInstructions, + quiet: options.Quiet, + runtime: options.Runtime, + runtimeArgs: options.RuntimeArgs, + transientMounts: options.TransientMounts, + compression: options.Compression, + output: options.Output, + signaturePolicyPath: options.SignaturePolicyPath, + systemContext: makeSystemContext(options.SignaturePolicyPath), + volumeCache: make(map[string]string), + volumeCacheInfo: make(map[string]os.FileInfo), + } + return &exec, nil +} + +// Prepare creates a working container based on the first FROM instruction we +// can find in the parsed tree. +func (b *Executor) Prepare(ib *imagebuilder.Builder, node *parser.Node) error { + from, err := ib.From(node) + if err != nil { + logrus.Debugf("Prepare(node.Children=%#v)", node.Children) + return fmt.Errorf("error determining starting point for build: %v", err) + } + logrus.Debugf("FROM %#v", from) + b.counter = 1 + if !b.quiet { + fmt.Fprintf(os.Stderr, "STEP %d: FROM %s\n", b.counter, from) + } + b.counter++ + builderOptions := buildah.BuilderOptions{ + FromImage: from, + PullIfMissing: b.pullIfMissing, + PullAlways: b.pullAlways, + Registry: b.registry, + SignaturePolicyPath: b.signaturePolicyPath, + } + builder, err := buildah.NewBuilder(b.store, builderOptions) + if err != nil { + return fmt.Errorf("error creating build container: %v", err) + } + dImage := docker.Image{ + Config: &docker.Config{ + Env: builder.UpdatedEnv(), + Image: from, + }, + } + err = ib.FromImage(&dImage, node) + if err != nil { + if err2 := builder.Delete(); err2 != nil { + logrus.Debugf("error deleting container which we failed to update: %v", err2) + } + return fmt.Errorf("error updating build context: %v", err) + } + mountPoint, err := builder.Mount("") + if err != nil { + if err2 := builder.Delete(); err2 != nil { + logrus.Debugf("error deleting container which we failed to mount: %v", err2) + } + return fmt.Errorf("error mounting new container: %v", err) + } + b.mountPoint = mountPoint + b.builder = builder + return nil +} + +// Delete deletes the working container, if we have one. The Executor object +// should not be used to build another image, as the name of the output image +// isn't resettable. +func (b *Executor) Delete() (err error) { + if b.builder != nil { + err = b.builder.Delete() + b.builder = nil + } + return err +} + +// Execute runs each of the steps in the parsed tree, in turn. +func (b *Executor) Execute(ib *imagebuilder.Builder, node *parser.Node) error { + for i, node := range node.Children { + step := ib.Step() + if err := step.Resolve(node); err != nil { + return fmt.Errorf("error resolving step %+v: %v", *node, err) + } + logrus.Debugf("Parsed Step: %+v", *step) + if !b.quiet { + fmt.Fprintf(os.Stderr, "STEP %d: %s\n", b.counter, step.Original) + } + b.counter++ + requiresStart := false + if i < len(node.Children)-1 { + requiresStart = ib.RequiresStart(&parser.Node{Children: node.Children[i+1:]}) + } + err := ib.Run(step, b, requiresStart) + if err != nil { + return fmt.Errorf("error building at step %+v: %v", *step, err) + } + } + return nil +} + +// Commit writes the container's contents to an image, using a passed-in tag as +// the name if there is one, generating a unique ID-based one otherwise. +func (b *Executor) Commit(ib *imagebuilder.Builder, node *parser.Node) (err error) { + var imageRef types.ImageReference + if b.output != "" { + imageRef, err = alltransports.ParseImageName(b.output) + if err != nil { + imageRef2, err2 := is.Transport.ParseStoreReference(b.store, b.output) + if err2 == nil { + imageRef = imageRef2 + err = nil + } + } + } else { + imageRef, err = is.Transport.ParseStoreReference(b.store, "@"+stringid.GenerateRandomID()) + } + if imageRef != nil { + logName := transports.ImageName(imageRef) + logrus.Debugf("COMMIT %q", logName) + if !b.quiet { + fmt.Fprintf(os.Stderr, "STEP %d: COMMIT %s\n", b.counter, logName) + } + } else { + logrus.Debugf("COMMIT") + if !b.quiet { + fmt.Fprintf(os.Stderr, "STEP %d: COMMIT\n", b.counter) + } + } + b.counter++ + if err != nil { + return err + } + options := buildah.CommitOptions{ + Compression: b.compression, + SignaturePolicyPath: b.signaturePolicyPath, + } + return b.builder.Commit(imageRef, options) +} + +// Build takes care of the details of running Prepare/Execute/Commit/Delete +// over each of the one or more parsed Dockerfiles. +func (b *Executor) Build(ib *imagebuilder.Builder, node []*parser.Node) (err error) { + if len(node) == 0 { + return fmt.Errorf("error building: no build instructions") + } + first := node[0] + if err = b.Prepare(ib, first); err != nil { + return err + } + defer b.Delete() + for _, this := range node { + if err = b.Execute(ib, this); err != nil { + return err + } + } + if err = b.Commit(ib, first); err != nil { + return err + } + return nil +} + +// BuildReadClosers parses a set of one or more already-opened Dockerfiles, +// creates a new Executor, and then runs Prepare/Execute/Commit/Delete over the +// entire set of instructions. +func BuildReadClosers(store storage.Store, options BuildOptions, dockerfile ...io.ReadCloser) error { + mainFile := dockerfile[0] + extraFiles := dockerfile[1:] + for _, dfile := range dockerfile { + defer dfile.Close() + } + builder, parsed, err := imagebuilder.NewBuilderForReader(mainFile, options.Args) + if err != nil { + return fmt.Errorf("error creating builder: %v", err) + } + exec, err := NewExecutor(store, options) + if err != nil { + return fmt.Errorf("error creating build executor: %v", err) + } + nodes := []*parser.Node{parsed} + for _, extra := range extraFiles { + _, parsed, err := imagebuilder.NewBuilderForReader(extra, options.Args) + if err != nil { + return fmt.Errorf("error parsing dockerfile: %v", err) + } + nodes = append(nodes, parsed) + } + return exec.Build(builder, nodes) +} + +// BuildDockerfiles parses a set of one or more Dockerfiles (which may be +// URLs), creates a new Executor, and then runs Prepare/Execute/Commit/Delete +// over the entire set of instructions. +func BuildDockerfiles(store storage.Store, options BuildOptions, dockerfile ...string) error { + var dockerfiles []io.ReadCloser + if len(dockerfile) == 0 { + return fmt.Errorf("error building: no dockerfiles specified") + } + for _, dfile := range dockerfile { + var rc io.ReadCloser + if strings.HasPrefix(dfile, "http://") || strings.HasPrefix(dfile, "https://") { + logrus.Debugf("reading remote Dockerfile %q", dfile) + resp, err := http.Get(dfile) + if err != nil { + return fmt.Errorf("error getting %q: %v", dfile, err) + } + if resp.ContentLength == 0 { + resp.Body.Close() + return fmt.Errorf("no contents in %q", dfile) + } + rc = resp.Body + } else { + if !filepath.IsAbs(dfile) { + logrus.Debugf("resolving local Dockerfile %q", dfile) + dfile = filepath.Join(options.ContextDirectory, dfile) + } + logrus.Debugf("reading local Dockerfile %q", dfile) + contents, err := os.Open(dfile) + if err != nil { + return fmt.Errorf("error reading %q: %v", dfile, err) + } + dinfo, err := contents.Stat() + if err != nil { + contents.Close() + return fmt.Errorf("error reading info about %q: %v", dfile, err) + } + if dinfo.Size() == 0 { + contents.Close() + return fmt.Errorf("no contents in %q: %v", dfile, err) + } + rc = contents + } + dockerfiles = append(dockerfiles, rc) + } + if err := BuildReadClosers(store, options, dockerfiles...); err != nil { + return fmt.Errorf("error building: %v", err) + } + return nil +} diff --git a/imagebuildah/util.go b/imagebuildah/util.go new file mode 100644 index 00000000000..479e29d09d3 --- /dev/null +++ b/imagebuildah/util.go @@ -0,0 +1,87 @@ +package imagebuildah + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + "os/exec" + "path" + "strings" + + "github.com/Sirupsen/logrus" + "github.com/containers/storage/pkg/chrootarchive" +) + +func cloneToDirectory(url, dir string) error { + if !strings.HasPrefix(url, "git://") { + url = "git://" + url + } + logrus.Debugf("cloning %q to %q", url, dir) + cmd := exec.Command("git", "clone", url, dir) + return cmd.Run() +} + +func downloadToDirectory(url, dir string) error { + logrus.Debugf("extracting %q to %q", url, dir) + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("error getting %q: %v", url, err) + } + defer resp.Body.Close() + if resp.ContentLength == 0 { + return fmt.Errorf("no contents in %q", url) + } + return chrootarchive.Untar(resp.Body, dir, nil) +} + +// TempDirForURL checks if the passed-in string looks like a URL. If it is, +// TempDirForURL creates a temporary directory, arranges for its contents to be +// the contents of that URL, and returns the temporary directory's path, along +// with the name of a subdirectory which should be used as the build context +// (which may be empty or "."). Removal of the temporary directory is the +// responsibility of the caller. If the string doesn't look like a URL, +// TempDirForURL returns empty strings and a nil error code. +func TempDirForURL(dir, prefix, url string) (name string, subdir string, err error) { + if !strings.HasPrefix(url, "http://") && + !strings.HasPrefix(url, "https://") && + !strings.HasPrefix(url, "git://") && + !strings.HasPrefix(url, "github.com/") { + return "", "", nil + } + name, err = ioutil.TempDir(dir, prefix) + if err != nil { + return "", "", fmt.Errorf("error creating temporary directory for %q: %v", url, err) + } + if strings.HasPrefix(url, "git://") { + err = cloneToDirectory(url, name) + if err != nil { + if err2 := os.Remove(name); err2 != nil { + logrus.Debugf("error removing temporary directory %q: %v", name, err2) + } + return "", "", err + } + return name, "", nil + } + if strings.HasPrefix(url, "github.com/") { + ghurl := url + url = fmt.Sprintf("https://%s/archive/master.tar.gz", ghurl) + logrus.Debugf("resolving url %q to %q", ghurl, url) + subdir = path.Base(ghurl) + "-master" + } + if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") { + err = downloadToDirectory(url, name) + if err != nil { + if err2 := os.Remove(name); err2 != nil { + logrus.Debugf("error removing temporary directory %q: %v", name, err2) + } + return "", subdir, err + } + return name, subdir, nil + } + logrus.Debugf("don't know how to retrieve %q", url) + if err2 := os.Remove(name); err2 != nil { + logrus.Debugf("error removing temporary directory %q: %v", name, err2) + } + return "", "", fmt.Errorf("unreachable code reached") +} diff --git a/new.go b/new.go index d92105f67c1..7715e2b2b97 100644 --- a/new.go +++ b/new.go @@ -7,12 +7,13 @@ import ( "github.com/Sirupsen/logrus" is "github.com/containers/image/storage" "github.com/containers/storage/storage" + "github.com/openshift/imagebuilder" ) const ( // BaseImageFakeName is the "name" of a source image which we interpret // as "no image". - BaseImageFakeName = "scratch" + BaseImageFakeName = imagebuilder.NoBaseImageSpecifier ) func newBuilder(store storage.Store, options BuilderOptions) (*Builder, error) { diff --git a/tests/bud.bats b/tests/bud.bats new file mode 100644 index 00000000000..f3ed7f2a427 --- /dev/null +++ b/tests/bud.bats @@ -0,0 +1,170 @@ +#!/usr/bin/env bats + +load helpers + +@test "bud-from-scratch" { + target=scratch-image + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} ${TESTSDIR}/bud/from-scratch + cid=$(buildah from ${target}) + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} + +@test "bud-from-multiple-files-one-from" { + target=scratch-image + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -f ${TESTSDIR}/bud/from-multiple-files/Dockerfile1.scratch -f ${TESTSDIR}/bud/from-multiple-files/Dockerfile2.nofrom + cid=$(buildah from ${target}) + root=$(buildah mount ${cid}) + cmp $root/Dockerfile1 ${TESTSDIR}/bud/from-multiple-files/Dockerfile1.scratch + cmp $root/Dockerfile2.nofrom ${TESTSDIR}/bud/from-multiple-files/Dockerfile2.nofrom + run test -s $root/etc/passwd + [ "$status" -ne 0 ] + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] + + target=alpine-image + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -f ${TESTSDIR}/bud/from-multiple-files/Dockerfile1.alpine -f ${TESTSDIR}/bud/from-multiple-files/Dockerfile2.nofrom + cid=$(buildah from ${target}) + root=$(buildah mount ${cid}) + cmp $root/Dockerfile1 ${TESTSDIR}/bud/from-multiple-files/Dockerfile1.alpine + cmp $root/Dockerfile2.nofrom ${TESTSDIR}/bud/from-multiple-files/Dockerfile2.nofrom + run test -s $root/etc/passwd + [ "$status" -eq 0 ] + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} + +@test "bud-from-multiple-files-two-froms" { + target=scratch-image + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -f ${TESTSDIR}/bud/from-multiple-files/Dockerfile1.scratch -f ${TESTSDIR}/bud/from-multiple-files/Dockerfile2.withfrom + cid=$(buildah from ${target}) + root=$(buildah mount ${cid}) + cmp $root/Dockerfile1 ${TESTSDIR}/bud/from-multiple-files/Dockerfile1.scratch + cmp $root/Dockerfile2.withfrom ${TESTSDIR}/bud/from-multiple-files/Dockerfile2.withfrom + run test -s $root/etc/passwd + [ "$status" -ne 0 ] + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] + + target=alpine-image + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -f ${TESTSDIR}/bud/from-multiple-files/Dockerfile1.alpine -f ${TESTSDIR}/bud/from-multiple-files/Dockerfile2.withfrom + cid=$(buildah from ${target}) + root=$(buildah mount ${cid}) + cmp $root/Dockerfile1 ${TESTSDIR}/bud/from-multiple-files/Dockerfile1.alpine + cmp $root/Dockerfile2.withfrom ${TESTSDIR}/bud/from-multiple-files/Dockerfile2.withfrom + run test -s $root/etc/passwd + [ "$status" -eq 0 ] + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} + +@test "bud-preserve-subvolumes" { + # This Dockerfile needs us to be able to handle a working RUN instruction. + if ! which runc ; then + skip + fi + target=volume-image + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} ${TESTSDIR}/bud/preserve-volumes + cid=$(buildah from ${target}) + root=$(buildah mount ${cid}) + test -s $root/vol/subvol/subsubvol/subsubvolfile + run test -s $root/vol/subvol/subvolfile + [ "$status" -ne 0 ] + test -s $root/vol/volfile + run test -s $root/vol/anothervolfile + [ "$status" -ne 0 ] + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} + +@test "bud-http-Dockerfile" { + starthttpd ${TESTSDIR}/bud/from-scratch + target=scratch-image + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -f http://0.0.0.0:${HTTP_SERVER_PORT}/Dockerfile . + stophttpd + cid=$(buildah from ${target}) + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} + +@test "bud-http-context-with-Dockerfile" { + starthttpd ${TESTSDIR}/bud/http-context + target=scratch-image + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} http://0.0.0.0:${HTTP_SERVER_PORT}/context.tar + stophttpd + cid=$(buildah from ${target}) + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} + +@test "bud-http-context-dir-with-Dockerfile-pre" { + starthttpd ${TESTSDIR}/bud/http-context-subdir + target=scratch-image + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} -f context/Dockerfile http://0.0.0.0:${HTTP_SERVER_PORT}/context.tar + stophttpd + cid=$(buildah from ${target}) + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} + +@test "bud-http-context-dir-with-Dockerfile-post" { + starthttpd ${TESTSDIR}/bud/http-context-subdir + target=scratch-image + buildah bud http://0.0.0.0:${HTTP_SERVER_PORT}/context.tar --signature-policy ${TESTSDIR}/policy.json -t ${target} -f context/Dockerfile + stophttpd + cid=$(buildah from ${target}) + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} + +@test "bud-git-context" { + # We need git and ssh to be around to handle cloning a repository. + if ! which git ; then + skip + fi + if ! which ssh ; then + skip + fi + target=giturl-image + # Any repo should do, but this one is small and is FROM: scratch. + gitrepo=git://github.com/projectatomic/nulecule-library + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} "${gitrepo}" + cid=$(buildah from ${target}) + buildah rm ${cid} + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} + +@test "bud-github-context" { + target=github-image + # Any repo should do, but this one is small and is FROM: scratch. + gitrepo=github.com/projectatomic/nulecule-library + buildah bud --signature-policy ${TESTSDIR}/policy.json -t ${target} "${gitrepo}" + cid=$(buildah from ${target}) + buildah rm ${cid} + buildah --debug=false images -q + buildah rmi $(buildah --debug=false images -q) + run buildah --debug=false images -q + [ "$output" = "" ] +} diff --git a/tests/bud/from-multiple-files/Dockerfile1.alpine b/tests/bud/from-multiple-files/Dockerfile1.alpine new file mode 100644 index 00000000000..c6e3fc4055b --- /dev/null +++ b/tests/bud/from-multiple-files/Dockerfile1.alpine @@ -0,0 +1,2 @@ +FROM alpine +COPY Dockerfile1.alpine /Dockerfile1 diff --git a/tests/bud/from-multiple-files/Dockerfile1.scratch b/tests/bud/from-multiple-files/Dockerfile1.scratch new file mode 100644 index 00000000000..4f9ab8a605e --- /dev/null +++ b/tests/bud/from-multiple-files/Dockerfile1.scratch @@ -0,0 +1,2 @@ +FROM scratch +COPY Dockerfile1.scratch /Dockerfile1 diff --git a/tests/bud/from-multiple-files/Dockerfile2.nofrom b/tests/bud/from-multiple-files/Dockerfile2.nofrom new file mode 100644 index 00000000000..0473c91d86a --- /dev/null +++ b/tests/bud/from-multiple-files/Dockerfile2.nofrom @@ -0,0 +1 @@ +COPY Dockerfile2.nofrom / diff --git a/tests/bud/from-multiple-files/Dockerfile2.withfrom b/tests/bud/from-multiple-files/Dockerfile2.withfrom new file mode 100644 index 00000000000..fa3b9690815 --- /dev/null +++ b/tests/bud/from-multiple-files/Dockerfile2.withfrom @@ -0,0 +1,2 @@ +FROM alpine +COPY Dockerfile2.withfrom / diff --git a/tests/bud/from-scratch/Dockerfile b/tests/bud/from-scratch/Dockerfile new file mode 100644 index 00000000000..c35f1b5f5e2 --- /dev/null +++ b/tests/bud/from-scratch/Dockerfile @@ -0,0 +1 @@ +FROM scratch diff --git a/tests/bud/http-context-subdir/context.tar b/tests/bud/http-context-subdir/context.tar new file mode 100644 index 00000000000..533ae524e2b Binary files /dev/null and b/tests/bud/http-context-subdir/context.tar differ diff --git a/tests/bud/http-context/context.tar b/tests/bud/http-context/context.tar new file mode 100644 index 00000000000..2e5f3a5156b Binary files /dev/null and b/tests/bud/http-context/context.tar differ diff --git a/tests/bud/preserve-volumes/Dockerfile b/tests/bud/preserve-volumes/Dockerfile new file mode 100644 index 00000000000..38c8844aa05 --- /dev/null +++ b/tests/bud/preserve-volumes/Dockerfile @@ -0,0 +1,16 @@ +FROM alpine +RUN mkdir -p /vol/subvol/subsubvol +RUN dd if=/dev/zero bs=512 count=1 of=/vol/subvol/subsubvol/subsubvolfile +VOLUME /vol/subvol +# At this point, the contents below /vol/subvol should be frozen. +RUN dd if=/dev/zero bs=512 count=1 of=/vol/subvol/subvolfile +# In particular, /vol/subvol/subvolfile should be wiped out. +RUN dd if=/dev/zero bs=512 count=1 of=/vol/volfile +# However, /vol/volfile should exist. +VOLUME /vol +# And this should be redundant. +VOLUME /vol/subvol +# And now we've frozen /vol. +RUN dd if=/dev/zero bs=512 count=1 of=/vol/anothervolfile +# Which means that in the image we're about to commit, /vol/anothervolfile +# shouldn't exist, either. diff --git a/tests/helpers.bash b/tests/helpers.bash index 038adb1da2a..92af896cbfc 100644 --- a/tests/helpers.bash +++ b/tests/helpers.bash @@ -12,7 +12,7 @@ function setup() { } function starthttpd() { - pushd ${2:-${BATS_TMPDIR}} > /dev/null + pushd ${2:-${TESTDIR}} > /dev/null cp ${TESTSDIR}/serve.go . go build serve.go HTTP_SERVER_PORT=$((RANDOM+32768)) diff --git a/vendor.conf b/vendor.conf index 0c3e2260c83..e070e89aa4b 100644 --- a/vendor.conf +++ b/vendor.conf @@ -1,3 +1,5 @@ +github.com/Nvveen/Gotty master +github.com/blang/semver master github.com/containers/image master github.com/containers/storage master github.com/docker/distribution master @@ -7,17 +9,18 @@ github.com/docker/engine-api master github.com/docker/go-connections master github.com/docker/go-units master github.com/docker/libtrust master +github.com/fsouza/go-dockerclient master github.com/ghodss/yaml master github.com/golang/glog master github.com/gorilla/context master github.com/gorilla/mux master +github.com/hashicorp/go-cleanhttp master github.com/imdario/mergo master github.com/mattn/go-runewidth master github.com/mattn/go-shellwords master github.com/mistifyio/go-zfs master github.com/mtrmac/gpgme master github.com/opencontainers/go-digest master -github.com/opencontainers/selinux master # Somewhere between v1.0.0-rc4 and v1.0.0-rc5 github.com/opencontainers/image-spec 2a0114e58130dbeed5c821d70eb78c8e070645a0 github.com/opencontainers/runc master @@ -25,6 +28,8 @@ github.com/opencontainers/runc master github.com/opencontainers/runtime-spec 508cd2734fe3ccad4602f10f8a68c22d0632e12c # Tied to the version of opencontainers/runtime-spec github.com/opencontainers/runtime-tools 2f0b1a0e1ac3fec9f9f6b1fd420210e9813ff195 +github.com/opencontainers/selinux master +github.com/openshift/imagebuilder master github.com/pborman/uuid master github.com/pkg/errors master github.com/Sirupsen/logrus master @@ -38,5 +43,5 @@ golang.org/x/text master gopkg.in/cheggaaa/pb.v1 master gopkg.in/yaml.v2 master k8s.io/apimachinery master https://github.com/kubernetes/apimachinery -k8s.io/kubernetes master https://github.com/kubernetes/kubernetes k8s.io/client-go master https://github.com/kubernetes/client-go +k8s.io/kubernetes master https://github.com/kubernetes/kubernetes diff --git a/vendor/github.com/Nvveen/Gotty/LICENSE b/vendor/github.com/Nvveen/Gotty/LICENSE new file mode 100644 index 00000000000..0b71c97360e --- /dev/null +++ b/vendor/github.com/Nvveen/Gotty/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2012, Neal van Veen (nealvanveen@gmail.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. diff --git a/vendor/github.com/Nvveen/Gotty/attributes.go b/vendor/github.com/Nvveen/Gotty/attributes.go new file mode 100644 index 00000000000..a4c005fae58 --- /dev/null +++ b/vendor/github.com/Nvveen/Gotty/attributes.go @@ -0,0 +1,514 @@ +// Copyright 2012 Neal van Veen. All rights reserved. +// Usage of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package gotty + +// Boolean capabilities +var BoolAttr = [...]string{ + "auto_left_margin", "bw", + "auto_right_margin", "am", + "no_esc_ctlc", "xsb", + "ceol_standout_glitch", "xhp", + "eat_newline_glitch", "xenl", + "erase_overstrike", "eo", + "generic_type", "gn", + "hard_copy", "hc", + "has_meta_key", "km", + "has_status_line", "hs", + "insert_null_glitch", "in", + "memory_above", "da", + "memory_below", "db", + "move_insert_mode", "mir", + "move_standout_mode", "msgr", + "over_strike", "os", + "status_line_esc_ok", "eslok", + "dest_tabs_magic_smso", "xt", + "tilde_glitch", "hz", + "transparent_underline", "ul", + "xon_xoff", "nxon", + "needs_xon_xoff", "nxon", + "prtr_silent", "mc5i", + "hard_cursor", "chts", + "non_rev_rmcup", "nrrmc", + "no_pad_char", "npc", + "non_dest_scroll_region", "ndscr", + "can_change", "ccc", + "back_color_erase", "bce", + "hue_lightness_saturation", "hls", + "col_addr_glitch", "xhpa", + "cr_cancels_micro_mode", "crxm", + "has_print_wheel", "daisy", + "row_addr_glitch", "xvpa", + "semi_auto_right_margin", "sam", + "cpi_changes_res", "cpix", + "lpi_changes_res", "lpix", + "backspaces_with_bs", "", + "crt_no_scrolling", "", + "no_correctly_working_cr", "", + "gnu_has_meta_key", "", + "linefeed_is_newline", "", + "has_hardware_tabs", "", + "return_does_clr_eol", "", +} + +// Numerical capabilities +var NumAttr = [...]string{ + "columns", "cols", + "init_tabs", "it", + "lines", "lines", + "lines_of_memory", "lm", + "magic_cookie_glitch", "xmc", + "padding_baud_rate", "pb", + "virtual_terminal", "vt", + "width_status_line", "wsl", + "num_labels", "nlab", + "label_height", "lh", + "label_width", "lw", + "max_attributes", "ma", + "maximum_windows", "wnum", + "max_colors", "colors", + "max_pairs", "pairs", + "no_color_video", "ncv", + "buffer_capacity", "bufsz", + "dot_vert_spacing", "spinv", + "dot_horz_spacing", "spinh", + "max_micro_address", "maddr", + "max_micro_jump", "mjump", + "micro_col_size", "mcs", + "micro_line_size", "mls", + "number_of_pins", "npins", + "output_res_char", "orc", + "output_res_line", "orl", + "output_res_horz_inch", "orhi", + "output_res_vert_inch", "orvi", + "print_rate", "cps", + "wide_char_size", "widcs", + "buttons", "btns", + "bit_image_entwining", "bitwin", + "bit_image_type", "bitype", + "magic_cookie_glitch_ul", "", + "carriage_return_delay", "", + "new_line_delay", "", + "backspace_delay", "", + "horizontal_tab_delay", "", + "number_of_function_keys", "", +} + +// String capabilities +var StrAttr = [...]string{ + "back_tab", "cbt", + "bell", "bel", + "carriage_return", "cr", + "change_scroll_region", "csr", + "clear_all_tabs", "tbc", + "clear_screen", "clear", + "clr_eol", "el", + "clr_eos", "ed", + "column_address", "hpa", + "command_character", "cmdch", + "cursor_address", "cup", + "cursor_down", "cud1", + "cursor_home", "home", + "cursor_invisible", "civis", + "cursor_left", "cub1", + "cursor_mem_address", "mrcup", + "cursor_normal", "cnorm", + "cursor_right", "cuf1", + "cursor_to_ll", "ll", + "cursor_up", "cuu1", + "cursor_visible", "cvvis", + "delete_character", "dch1", + "delete_line", "dl1", + "dis_status_line", "dsl", + "down_half_line", "hd", + "enter_alt_charset_mode", "smacs", + "enter_blink_mode", "blink", + "enter_bold_mode", "bold", + "enter_ca_mode", "smcup", + "enter_delete_mode", "smdc", + "enter_dim_mode", "dim", + "enter_insert_mode", "smir", + "enter_secure_mode", "invis", + "enter_protected_mode", "prot", + "enter_reverse_mode", "rev", + "enter_standout_mode", "smso", + "enter_underline_mode", "smul", + "erase_chars", "ech", + "exit_alt_charset_mode", "rmacs", + "exit_attribute_mode", "sgr0", + "exit_ca_mode", "rmcup", + "exit_delete_mode", "rmdc", + "exit_insert_mode", "rmir", + "exit_standout_mode", "rmso", + "exit_underline_mode", "rmul", + "flash_screen", "flash", + "form_feed", "ff", + "from_status_line", "fsl", + "init_1string", "is1", + "init_2string", "is2", + "init_3string", "is3", + "init_file", "if", + "insert_character", "ich1", + "insert_line", "il1", + "insert_padding", "ip", + "key_backspace", "kbs", + "key_catab", "ktbc", + "key_clear", "kclr", + "key_ctab", "kctab", + "key_dc", "kdch1", + "key_dl", "kdl1", + "key_down", "kcud1", + "key_eic", "krmir", + "key_eol", "kel", + "key_eos", "ked", + "key_f0", "kf0", + "key_f1", "kf1", + "key_f10", "kf10", + "key_f2", "kf2", + "key_f3", "kf3", + "key_f4", "kf4", + "key_f5", "kf5", + "key_f6", "kf6", + "key_f7", "kf7", + "key_f8", "kf8", + "key_f9", "kf9", + "key_home", "khome", + "key_ic", "kich1", + "key_il", "kil1", + "key_left", "kcub1", + "key_ll", "kll", + "key_npage", "knp", + "key_ppage", "kpp", + "key_right", "kcuf1", + "key_sf", "kind", + "key_sr", "kri", + "key_stab", "khts", + "key_up", "kcuu1", + "keypad_local", "rmkx", + "keypad_xmit", "smkx", + "lab_f0", "lf0", + "lab_f1", "lf1", + "lab_f10", "lf10", + "lab_f2", "lf2", + "lab_f3", "lf3", + "lab_f4", "lf4", + "lab_f5", "lf5", + "lab_f6", "lf6", + "lab_f7", "lf7", + "lab_f8", "lf8", + "lab_f9", "lf9", + "meta_off", "rmm", + "meta_on", "smm", + "newline", "_glitch", + "pad_char", "npc", + "parm_dch", "dch", + "parm_delete_line", "dl", + "parm_down_cursor", "cud", + "parm_ich", "ich", + "parm_index", "indn", + "parm_insert_line", "il", + "parm_left_cursor", "cub", + "parm_right_cursor", "cuf", + "parm_rindex", "rin", + "parm_up_cursor", "cuu", + "pkey_key", "pfkey", + "pkey_local", "pfloc", + "pkey_xmit", "pfx", + "print_screen", "mc0", + "prtr_off", "mc4", + "prtr_on", "mc5", + "repeat_char", "rep", + "reset_1string", "rs1", + "reset_2string", "rs2", + "reset_3string", "rs3", + "reset_file", "rf", + "restore_cursor", "rc", + "row_address", "mvpa", + "save_cursor", "row_address", + "scroll_forward", "ind", + "scroll_reverse", "ri", + "set_attributes", "sgr", + "set_tab", "hts", + "set_window", "wind", + "tab", "s_magic_smso", + "to_status_line", "tsl", + "underline_char", "uc", + "up_half_line", "hu", + "init_prog", "iprog", + "key_a1", "ka1", + "key_a3", "ka3", + "key_b2", "kb2", + "key_c1", "kc1", + "key_c3", "kc3", + "prtr_non", "mc5p", + "char_padding", "rmp", + "acs_chars", "acsc", + "plab_norm", "pln", + "key_btab", "kcbt", + "enter_xon_mode", "smxon", + "exit_xon_mode", "rmxon", + "enter_am_mode", "smam", + "exit_am_mode", "rmam", + "xon_character", "xonc", + "xoff_character", "xoffc", + "ena_acs", "enacs", + "label_on", "smln", + "label_off", "rmln", + "key_beg", "kbeg", + "key_cancel", "kcan", + "key_close", "kclo", + "key_command", "kcmd", + "key_copy", "kcpy", + "key_create", "kcrt", + "key_end", "kend", + "key_enter", "kent", + "key_exit", "kext", + "key_find", "kfnd", + "key_help", "khlp", + "key_mark", "kmrk", + "key_message", "kmsg", + "key_move", "kmov", + "key_next", "knxt", + "key_open", "kopn", + "key_options", "kopt", + "key_previous", "kprv", + "key_print", "kprt", + "key_redo", "krdo", + "key_reference", "kref", + "key_refresh", "krfr", + "key_replace", "krpl", + "key_restart", "krst", + "key_resume", "kres", + "key_save", "ksav", + "key_suspend", "kspd", + "key_undo", "kund", + "key_sbeg", "kBEG", + "key_scancel", "kCAN", + "key_scommand", "kCMD", + "key_scopy", "kCPY", + "key_screate", "kCRT", + "key_sdc", "kDC", + "key_sdl", "kDL", + "key_select", "kslt", + "key_send", "kEND", + "key_seol", "kEOL", + "key_sexit", "kEXT", + "key_sfind", "kFND", + "key_shelp", "kHLP", + "key_shome", "kHOM", + "key_sic", "kIC", + "key_sleft", "kLFT", + "key_smessage", "kMSG", + "key_smove", "kMOV", + "key_snext", "kNXT", + "key_soptions", "kOPT", + "key_sprevious", "kPRV", + "key_sprint", "kPRT", + "key_sredo", "kRDO", + "key_sreplace", "kRPL", + "key_sright", "kRIT", + "key_srsume", "kRES", + "key_ssave", "kSAV", + "key_ssuspend", "kSPD", + "key_sundo", "kUND", + "req_for_input", "rfi", + "key_f11", "kf11", + "key_f12", "kf12", + "key_f13", "kf13", + "key_f14", "kf14", + "key_f15", "kf15", + "key_f16", "kf16", + "key_f17", "kf17", + "key_f18", "kf18", + "key_f19", "kf19", + "key_f20", "kf20", + "key_f21", "kf21", + "key_f22", "kf22", + "key_f23", "kf23", + "key_f24", "kf24", + "key_f25", "kf25", + "key_f26", "kf26", + "key_f27", "kf27", + "key_f28", "kf28", + "key_f29", "kf29", + "key_f30", "kf30", + "key_f31", "kf31", + "key_f32", "kf32", + "key_f33", "kf33", + "key_f34", "kf34", + "key_f35", "kf35", + "key_f36", "kf36", + "key_f37", "kf37", + "key_f38", "kf38", + "key_f39", "kf39", + "key_f40", "kf40", + "key_f41", "kf41", + "key_f42", "kf42", + "key_f43", "kf43", + "key_f44", "kf44", + "key_f45", "kf45", + "key_f46", "kf46", + "key_f47", "kf47", + "key_f48", "kf48", + "key_f49", "kf49", + "key_f50", "kf50", + "key_f51", "kf51", + "key_f52", "kf52", + "key_f53", "kf53", + "key_f54", "kf54", + "key_f55", "kf55", + "key_f56", "kf56", + "key_f57", "kf57", + "key_f58", "kf58", + "key_f59", "kf59", + "key_f60", "kf60", + "key_f61", "kf61", + "key_f62", "kf62", + "key_f63", "kf63", + "clr_bol", "el1", + "clear_margins", "mgc", + "set_left_margin", "smgl", + "set_right_margin", "smgr", + "label_format", "fln", + "set_clock", "sclk", + "display_clock", "dclk", + "remove_clock", "rmclk", + "create_window", "cwin", + "goto_window", "wingo", + "hangup", "hup", + "dial_phone", "dial", + "quick_dial", "qdial", + "tone", "tone", + "pulse", "pulse", + "flash_hook", "hook", + "fixed_pause", "pause", + "wait_tone", "wait", + "user0", "u0", + "user1", "u1", + "user2", "u2", + "user3", "u3", + "user4", "u4", + "user5", "u5", + "user6", "u6", + "user7", "u7", + "user8", "u8", + "user9", "u9", + "orig_pair", "op", + "orig_colors", "oc", + "initialize_color", "initc", + "initialize_pair", "initp", + "set_color_pair", "scp", + "set_foreground", "setf", + "set_background", "setb", + "change_char_pitch", "cpi", + "change_line_pitch", "lpi", + "change_res_horz", "chr", + "change_res_vert", "cvr", + "define_char", "defc", + "enter_doublewide_mode", "swidm", + "enter_draft_quality", "sdrfq", + "enter_italics_mode", "sitm", + "enter_leftward_mode", "slm", + "enter_micro_mode", "smicm", + "enter_near_letter_quality", "snlq", + "enter_normal_quality", "snrmq", + "enter_shadow_mode", "sshm", + "enter_subscript_mode", "ssubm", + "enter_superscript_mode", "ssupm", + "enter_upward_mode", "sum", + "exit_doublewide_mode", "rwidm", + "exit_italics_mode", "ritm", + "exit_leftward_mode", "rlm", + "exit_micro_mode", "rmicm", + "exit_shadow_mode", "rshm", + "exit_subscript_mode", "rsubm", + "exit_superscript_mode", "rsupm", + "exit_upward_mode", "rum", + "micro_column_address", "mhpa", + "micro_down", "mcud1", + "micro_left", "mcub1", + "micro_right", "mcuf1", + "micro_row_address", "mvpa", + "micro_up", "mcuu1", + "order_of_pins", "porder", + "parm_down_micro", "mcud", + "parm_left_micro", "mcub", + "parm_right_micro", "mcuf", + "parm_up_micro", "mcuu", + "select_char_set", "scs", + "set_bottom_margin", "smgb", + "set_bottom_margin_parm", "smgbp", + "set_left_margin_parm", "smglp", + "set_right_margin_parm", "smgrp", + "set_top_margin", "smgt", + "set_top_margin_parm", "smgtp", + "start_bit_image", "sbim", + "start_char_set_def", "scsd", + "stop_bit_image", "rbim", + "stop_char_set_def", "rcsd", + "subscript_characters", "subcs", + "superscript_characters", "supcs", + "these_cause_cr", "docr", + "zero_motion", "zerom", + "char_set_names", "csnm", + "key_mouse", "kmous", + "mouse_info", "minfo", + "req_mouse_pos", "reqmp", + "get_mouse", "getm", + "set_a_foreground", "setaf", + "set_a_background", "setab", + "pkey_plab", "pfxl", + "device_type", "devt", + "code_set_init", "csin", + "set0_des_seq", "s0ds", + "set1_des_seq", "s1ds", + "set2_des_seq", "s2ds", + "set3_des_seq", "s3ds", + "set_lr_margin", "smglr", + "set_tb_margin", "smgtb", + "bit_image_repeat", "birep", + "bit_image_newline", "binel", + "bit_image_carriage_return", "bicr", + "color_names", "colornm", + "define_bit_image_region", "defbi", + "end_bit_image_region", "endbi", + "set_color_band", "setcolor", + "set_page_length", "slines", + "display_pc_char", "dispc", + "enter_pc_charset_mode", "smpch", + "exit_pc_charset_mode", "rmpch", + "enter_scancode_mode", "smsc", + "exit_scancode_mode", "rmsc", + "pc_term_options", "pctrm", + "scancode_escape", "scesc", + "alt_scancode_esc", "scesa", + "enter_horizontal_hl_mode", "ehhlm", + "enter_left_hl_mode", "elhlm", + "enter_low_hl_mode", "elohlm", + "enter_right_hl_mode", "erhlm", + "enter_top_hl_mode", "ethlm", + "enter_vertical_hl_mode", "evhlm", + "set_a_attributes", "sgr1", + "set_pglen_inch", "slength", + "termcap_init2", "", + "termcap_reset", "", + "linefeed_if_not_lf", "", + "backspace_if_not_bs", "", + "other_non_function_keys", "", + "arrow_key_map", "", + "acs_ulcorner", "", + "acs_llcorner", "", + "acs_urcorner", "", + "acs_lrcorner", "", + "acs_ltee", "", + "acs_rtee", "", + "acs_btee", "", + "acs_ttee", "", + "acs_hline", "", + "acs_vline", "", + "acs_plus", "", + "memory_lock", "", + "memory_unlock", "", + "box_chars_1", "", +} diff --git a/vendor/github.com/Nvveen/Gotty/gotty.go b/vendor/github.com/Nvveen/Gotty/gotty.go new file mode 100644 index 00000000000..093cbf37e1b --- /dev/null +++ b/vendor/github.com/Nvveen/Gotty/gotty.go @@ -0,0 +1,238 @@ +// Copyright 2012 Neal van Veen. All rights reserved. +// Usage of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Gotty is a Go-package for reading and parsing the terminfo database +package gotty + +// TODO add more concurrency to name lookup, look for more opportunities. + +import ( + "encoding/binary" + "errors" + "fmt" + "os" + "reflect" + "strings" + "sync" +) + +// Open a terminfo file by the name given and construct a TermInfo object. +// If something went wrong reading the terminfo database file, an error is +// returned. +func OpenTermInfo(termName string) (*TermInfo, error) { + var term *TermInfo + var err error + // Find the environment variables + termloc := os.Getenv("TERMINFO") + if len(termloc) == 0 { + // Search like ncurses + locations := []string{os.Getenv("HOME") + "/.terminfo/", "/etc/terminfo/", + "/lib/terminfo/", "/usr/share/terminfo/"} + var path string + for _, str := range locations { + // Construct path + path = str + string(termName[0]) + "/" + termName + // Check if path can be opened + file, _ := os.Open(path) + if file != nil { + // Path can open, fall out and use current path + file.Close() + break + } + } + if len(path) > 0 { + term, err = readTermInfo(path) + } else { + err = errors.New(fmt.Sprintf("No terminfo file(-location) found")) + } + } + return term, err +} + +// Open a terminfo file from the environment variable containing the current +// terminal name and construct a TermInfo object. If something went wrong +// reading the terminfo database file, an error is returned. +func OpenTermInfoEnv() (*TermInfo, error) { + termenv := os.Getenv("TERM") + return OpenTermInfo(termenv) +} + +// Return an attribute by the name attr provided. If none can be found, +// an error is returned. +func (term *TermInfo) GetAttribute(attr string) (stacker, error) { + // Channel to store the main value in. + var value stacker + // Add a blocking WaitGroup + var block sync.WaitGroup + // Keep track of variable being written. + written := false + // Function to put into goroutine. + f := func(ats interface{}) { + var ok bool + var v stacker + // Switch on type of map to use and assign value to it. + switch reflect.TypeOf(ats).Elem().Kind() { + case reflect.Bool: + v, ok = ats.(map[string]bool)[attr] + case reflect.Int16: + v, ok = ats.(map[string]int16)[attr] + case reflect.String: + v, ok = ats.(map[string]string)[attr] + } + // If ok, a value is found, so we can write. + if ok { + value = v + written = true + } + // Goroutine is done + block.Done() + } + block.Add(3) + // Go for all 3 attribute lists. + go f(term.boolAttributes) + go f(term.numAttributes) + go f(term.strAttributes) + // Wait until every goroutine is done. + block.Wait() + // If a value has been written, return it. + if written { + return value, nil + } + // Otherwise, error. + return nil, fmt.Errorf("Erorr finding attribute") +} + +// Return an attribute by the name attr provided. If none can be found, +// an error is returned. A name is first converted to its termcap value. +func (term *TermInfo) GetAttributeName(name string) (stacker, error) { + tc := GetTermcapName(name) + return term.GetAttribute(tc) +} + +// A utility function that finds and returns the termcap equivalent of a +// variable name. +func GetTermcapName(name string) string { + // Termcap name + var tc string + // Blocking group + var wait sync.WaitGroup + // Function to put into a goroutine + f := func(attrs []string) { + // Find the string corresponding to the name + for i, s := range attrs { + if s == name { + tc = attrs[i+1] + } + } + // Goroutine is finished + wait.Done() + } + wait.Add(3) + // Go for all 3 attribute lists + go f(BoolAttr[:]) + go f(NumAttr[:]) + go f(StrAttr[:]) + // Wait until every goroutine is done + wait.Wait() + // Return the termcap name + return tc +} + +// This function takes a path to a terminfo file and reads it in binary +// form to construct the actual TermInfo file. +func readTermInfo(path string) (*TermInfo, error) { + // Open the terminfo file + file, err := os.Open(path) + defer file.Close() + if err != nil { + return nil, err + } + + // magic, nameSize, boolSize, nrSNum, nrOffsetsStr, strSize + // Header is composed of the magic 0432 octal number, size of the name + // section, size of the boolean section, the amount of number values, + // the number of offsets of strings, and the size of the string section. + var header [6]int16 + // Byte array is used to read in byte values + var byteArray []byte + // Short array is used to read in short values + var shArray []int16 + // TermInfo object to store values + var term TermInfo + + // Read in the header + err = binary.Read(file, binary.LittleEndian, &header) + if err != nil { + return nil, err + } + // If magic number isn't there or isn't correct, we have the wrong filetype + if header[0] != 0432 { + return nil, errors.New(fmt.Sprintf("Wrong filetype")) + } + + // Read in the names + byteArray = make([]byte, header[1]) + err = binary.Read(file, binary.LittleEndian, &byteArray) + if err != nil { + return nil, err + } + term.Names = strings.Split(string(byteArray), "|") + + // Read in the booleans + byteArray = make([]byte, header[2]) + err = binary.Read(file, binary.LittleEndian, &byteArray) + if err != nil { + return nil, err + } + term.boolAttributes = make(map[string]bool) + for i, b := range byteArray { + if b == 1 { + term.boolAttributes[BoolAttr[i*2+1]] = true + } + } + // If the number of bytes read is not even, a byte for alignment is added + if len(byteArray)%2 != 0 { + err = binary.Read(file, binary.LittleEndian, make([]byte, 1)) + if err != nil { + return nil, err + } + } + + // Read in shorts + shArray = make([]int16, header[3]) + err = binary.Read(file, binary.LittleEndian, &shArray) + if err != nil { + return nil, err + } + term.numAttributes = make(map[string]int16) + for i, n := range shArray { + if n != 0377 && n > -1 { + term.numAttributes[NumAttr[i*2+1]] = n + } + } + + // Read the offsets into the short array + shArray = make([]int16, header[4]) + err = binary.Read(file, binary.LittleEndian, &shArray) + if err != nil { + return nil, err + } + // Read the actual strings in the byte array + byteArray = make([]byte, header[5]) + err = binary.Read(file, binary.LittleEndian, &byteArray) + if err != nil { + return nil, err + } + term.strAttributes = make(map[string]string) + // We get an offset, and then iterate until the string is null-terminated + for i, offset := range shArray { + if offset > -1 { + r := offset + for ; byteArray[r] != 0; r++ { + } + term.strAttributes[StrAttr[i*2+1]] = string(byteArray[offset:r]) + } + } + return &term, nil +} diff --git a/vendor/github.com/Nvveen/Gotty/parser.go b/vendor/github.com/Nvveen/Gotty/parser.go new file mode 100644 index 00000000000..a9d5d23c542 --- /dev/null +++ b/vendor/github.com/Nvveen/Gotty/parser.go @@ -0,0 +1,362 @@ +// Copyright 2012 Neal van Veen. All rights reserved. +// Usage of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package gotty + +import ( + "bytes" + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +var exp = [...]string{ + "%%", + "%c", + "%s", + "%p(\\d)", + "%P([A-z])", + "%g([A-z])", + "%'(.)'", + "%{([0-9]+)}", + "%l", + "%\\+|%-|%\\*|%/|%m", + "%&|%\\||%\\^", + "%=|%>|%<", + "%A|%O", + "%!|%~", + "%i", + "%(:[\\ #\\-\\+]{0,4})?(\\d+\\.\\d+|\\d+)?[doxXs]", + "%\\?(.*?);", +} + +var regex *regexp.Regexp +var staticVar map[byte]stacker + +// Parses the attribute that is received with name attr and parameters params. +func (term *TermInfo) Parse(attr string, params ...interface{}) (string, error) { + // Get the attribute name first. + iface, err := term.GetAttribute(attr) + str, ok := iface.(string) + if err != nil { + return "", err + } + if !ok { + return str, errors.New("Only string capabilities can be parsed.") + } + // Construct the hidden parser struct so we can use a recursive stack based + // parser. + ps := &parser{} + // Dynamic variables only exist in this context. + ps.dynamicVar = make(map[byte]stacker, 26) + ps.parameters = make([]stacker, len(params)) + // Convert the parameters to insert them into the parser struct. + for i, x := range params { + ps.parameters[i] = x + } + // Recursively walk and return. + result, err := ps.walk(str) + return result, err +} + +// Parses the attribute that is received with name attr and parameters params. +// Only works on full name of a capability that is given, which it uses to +// search for the termcap name. +func (term *TermInfo) ParseName(attr string, params ...interface{}) (string, error) { + tc := GetTermcapName(attr) + return term.Parse(tc, params) +} + +// Identify each token in a stack based manner and do the actual parsing. +func (ps *parser) walk(attr string) (string, error) { + // We use a buffer to get the modified string. + var buf bytes.Buffer + // Next, find and identify all tokens by their indices and strings. + tokens := regex.FindAllStringSubmatch(attr, -1) + if len(tokens) == 0 { + return attr, nil + } + indices := regex.FindAllStringIndex(attr, -1) + q := 0 // q counts the matches of one token + // Iterate through the string per character. + for i := 0; i < len(attr); i++ { + // If the current position is an identified token, execute the following + // steps. + if q < len(indices) && i >= indices[q][0] && i < indices[q][1] { + // Switch on token. + switch { + case tokens[q][0][:2] == "%%": + // Literal percentage character. + buf.WriteByte('%') + case tokens[q][0][:2] == "%c": + // Pop a character. + c, err := ps.st.pop() + if err != nil { + return buf.String(), err + } + buf.WriteByte(c.(byte)) + case tokens[q][0][:2] == "%s": + // Pop a string. + str, err := ps.st.pop() + if err != nil { + return buf.String(), err + } + if _, ok := str.(string); !ok { + return buf.String(), errors.New("Stack head is not a string") + } + buf.WriteString(str.(string)) + case tokens[q][0][:2] == "%p": + // Push a parameter on the stack. + index, err := strconv.ParseInt(tokens[q][1], 10, 8) + index-- + if err != nil { + return buf.String(), err + } + if int(index) >= len(ps.parameters) { + return buf.String(), errors.New("Parameters index out of bound") + } + ps.st.push(ps.parameters[index]) + case tokens[q][0][:2] == "%P": + // Pop a variable from the stack as a dynamic or static variable. + val, err := ps.st.pop() + if err != nil { + return buf.String(), err + } + index := tokens[q][2] + if len(index) > 1 { + errorStr := fmt.Sprintf("%s is not a valid dynamic variables index", + index) + return buf.String(), errors.New(errorStr) + } + // Specify either dynamic or static. + if index[0] >= 'a' && index[0] <= 'z' { + ps.dynamicVar[index[0]] = val + } else if index[0] >= 'A' && index[0] <= 'Z' { + staticVar[index[0]] = val + } + case tokens[q][0][:2] == "%g": + // Push a variable from the stack as a dynamic or static variable. + index := tokens[q][3] + if len(index) > 1 { + errorStr := fmt.Sprintf("%s is not a valid static variables index", + index) + return buf.String(), errors.New(errorStr) + } + var val stacker + if index[0] >= 'a' && index[0] <= 'z' { + val = ps.dynamicVar[index[0]] + } else if index[0] >= 'A' && index[0] <= 'Z' { + val = staticVar[index[0]] + } + ps.st.push(val) + case tokens[q][0][:2] == "%'": + // Push a character constant. + con := tokens[q][4] + if len(con) > 1 { + errorStr := fmt.Sprintf("%s is not a valid character constant", con) + return buf.String(), errors.New(errorStr) + } + ps.st.push(con[0]) + case tokens[q][0][:2] == "%{": + // Push an integer constant. + con, err := strconv.ParseInt(tokens[q][5], 10, 32) + if err != nil { + return buf.String(), err + } + ps.st.push(con) + case tokens[q][0][:2] == "%l": + // Push the length of the string that is popped from the stack. + popStr, err := ps.st.pop() + if err != nil { + return buf.String(), err + } + if _, ok := popStr.(string); !ok { + errStr := fmt.Sprintf("Stack head is not a string") + return buf.String(), errors.New(errStr) + } + ps.st.push(len(popStr.(string))) + case tokens[q][0][:2] == "%?": + // If-then-else construct. First, the whole string is identified and + // then inside this substring, we can specify which parts to switch on. + ifReg, _ := regexp.Compile("%\\?(.*)%t(.*)%e(.*);|%\\?(.*)%t(.*);") + ifTokens := ifReg.FindStringSubmatch(tokens[q][0]) + var ( + ifStr string + err error + ) + // Parse the if-part to determine if-else. + if len(ifTokens[1]) > 0 { + ifStr, err = ps.walk(ifTokens[1]) + } else { // else + ifStr, err = ps.walk(ifTokens[4]) + } + // Return any errors + if err != nil { + return buf.String(), err + } else if len(ifStr) > 0 { + // Self-defined limitation, not sure if this is correct, but didn't + // seem like it. + return buf.String(), errors.New("If-clause cannot print statements") + } + var thenStr string + // Pop the first value that is set by parsing the if-clause. + choose, err := ps.st.pop() + if err != nil { + return buf.String(), err + } + // Switch to if or else. + if choose.(int) == 0 && len(ifTokens[1]) > 0 { + thenStr, err = ps.walk(ifTokens[3]) + } else if choose.(int) != 0 { + if len(ifTokens[1]) > 0 { + thenStr, err = ps.walk(ifTokens[2]) + } else { + thenStr, err = ps.walk(ifTokens[5]) + } + } + if err != nil { + return buf.String(), err + } + buf.WriteString(thenStr) + case tokens[q][0][len(tokens[q][0])-1] == 'd': // Fallthrough for printing + fallthrough + case tokens[q][0][len(tokens[q][0])-1] == 'o': // digits. + fallthrough + case tokens[q][0][len(tokens[q][0])-1] == 'x': + fallthrough + case tokens[q][0][len(tokens[q][0])-1] == 'X': + fallthrough + case tokens[q][0][len(tokens[q][0])-1] == 's': + token := tokens[q][0] + // Remove the : that comes before a flag. + if token[1] == ':' { + token = token[:1] + token[2:] + } + digit, err := ps.st.pop() + if err != nil { + return buf.String(), err + } + // The rest is determined like the normal formatted prints. + digitStr := fmt.Sprintf(token, digit.(int)) + buf.WriteString(digitStr) + case tokens[q][0][:2] == "%i": + // Increment the parameters by one. + if len(ps.parameters) < 2 { + return buf.String(), errors.New("Not enough parameters to increment.") + } + val1, val2 := ps.parameters[0].(int), ps.parameters[1].(int) + val1++ + val2++ + ps.parameters[0], ps.parameters[1] = val1, val2 + default: + // The rest of the tokens is a special case, where two values are + // popped and then operated on by the token that comes after them. + op1, err := ps.st.pop() + if err != nil { + return buf.String(), err + } + op2, err := ps.st.pop() + if err != nil { + return buf.String(), err + } + var result stacker + switch tokens[q][0][:2] { + case "%+": + // Addition + result = op2.(int) + op1.(int) + case "%-": + // Subtraction + result = op2.(int) - op1.(int) + case "%*": + // Multiplication + result = op2.(int) * op1.(int) + case "%/": + // Division + result = op2.(int) / op1.(int) + case "%m": + // Modulo + result = op2.(int) % op1.(int) + case "%&": + // Bitwise AND + result = op2.(int) & op1.(int) + case "%|": + // Bitwise OR + result = op2.(int) | op1.(int) + case "%^": + // Bitwise XOR + result = op2.(int) ^ op1.(int) + case "%=": + // Equals + result = op2 == op1 + case "%>": + // Greater-than + result = op2.(int) > op1.(int) + case "%<": + // Lesser-than + result = op2.(int) < op1.(int) + case "%A": + // Logical AND + result = op2.(bool) && op1.(bool) + case "%O": + // Logical OR + result = op2.(bool) || op1.(bool) + case "%!": + // Logical complement + result = !op1.(bool) + case "%~": + // Bitwise complement + result = ^(op1.(int)) + } + ps.st.push(result) + } + + i = indices[q][1] - 1 + q++ + } else { + // We are not "inside" a token, so just skip until the end or the next + // token, and add all characters to the buffer. + j := i + if q != len(indices) { + for !(j >= indices[q][0] && j < indices[q][1]) { + j++ + } + } else { + j = len(attr) + } + buf.WriteString(string(attr[i:j])) + i = j + } + } + // Return the buffer as a string. + return buf.String(), nil +} + +// Push a stacker-value onto the stack. +func (st *stack) push(s stacker) { + *st = append(*st, s) +} + +// Pop a stacker-value from the stack. +func (st *stack) pop() (stacker, error) { + if len(*st) == 0 { + return nil, errors.New("Stack is empty.") + } + newStack := make(stack, len(*st)-1) + val := (*st)[len(*st)-1] + copy(newStack, (*st)[:len(*st)-1]) + *st = newStack + return val, nil +} + +// Initialize regexes and the static vars (that don't get changed between +// calls. +func init() { + // Initialize the main regex. + expStr := strings.Join(exp[:], "|") + regex, _ = regexp.Compile(expStr) + // Initialize the static variables. + staticVar = make(map[byte]stacker, 26) +} diff --git a/vendor/github.com/Nvveen/Gotty/types.go b/vendor/github.com/Nvveen/Gotty/types.go new file mode 100644 index 00000000000..9bcc65e9b88 --- /dev/null +++ b/vendor/github.com/Nvveen/Gotty/types.go @@ -0,0 +1,23 @@ +// Copyright 2012 Neal van Veen. All rights reserved. +// Usage of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package gotty + +type TermInfo struct { + boolAttributes map[string]bool + numAttributes map[string]int16 + strAttributes map[string]string + // The various names of the TermInfo file. + Names []string +} + +type stacker interface { +} +type stack []stacker + +type parser struct { + st stack + parameters []stacker + dynamicVar map[byte]stacker +} diff --git a/vendor/github.com/docker/docker/builder/dockerfile/command/command.go b/vendor/github.com/docker/docker/builder/dockerfile/command/command.go new file mode 100644 index 00000000000..f23c6874b55 --- /dev/null +++ b/vendor/github.com/docker/docker/builder/dockerfile/command/command.go @@ -0,0 +1,46 @@ +// Package command contains the set of Dockerfile commands. +package command + +// Define constants for the command strings +const ( + Add = "add" + Arg = "arg" + Cmd = "cmd" + Copy = "copy" + Entrypoint = "entrypoint" + Env = "env" + Expose = "expose" + From = "from" + Healthcheck = "healthcheck" + Label = "label" + Maintainer = "maintainer" + Onbuild = "onbuild" + Run = "run" + Shell = "shell" + StopSignal = "stopsignal" + User = "user" + Volume = "volume" + Workdir = "workdir" +) + +// Commands is list of all Dockerfile commands +var Commands = map[string]struct{}{ + Add: {}, + Arg: {}, + Cmd: {}, + Copy: {}, + Entrypoint: {}, + Env: {}, + Expose: {}, + From: {}, + Healthcheck: {}, + Label: {}, + Maintainer: {}, + Onbuild: {}, + Run: {}, + Shell: {}, + StopSignal: {}, + User: {}, + Volume: {}, + Workdir: {}, +} diff --git a/vendor/github.com/docker/docker/builder/dockerfile/parser/line_parsers.go b/vendor/github.com/docker/docker/builder/dockerfile/parser/line_parsers.go new file mode 100644 index 00000000000..9b4a4c59f90 --- /dev/null +++ b/vendor/github.com/docker/docker/builder/dockerfile/parser/line_parsers.go @@ -0,0 +1,361 @@ +package parser + +// line parsers are dispatch calls that parse a single unit of text into a +// Node object which contains the whole statement. Dockerfiles have varied +// (but not usually unique, see ONBUILD for a unique example) parsing rules +// per-command, and these unify the processing in a way that makes it +// manageable. + +import ( + "encoding/json" + "errors" + "fmt" + "strings" + "unicode" + "unicode/utf8" +) + +var ( + errDockerfileNotStringArray = errors.New("When using JSON array syntax, arrays must be comprised of strings only.") +) + +// ignore the current argument. This will still leave a command parsed, but +// will not incorporate the arguments into the ast. +func parseIgnore(rest string, d *Directive) (*Node, map[string]bool, error) { + return &Node{}, nil, nil +} + +// used for onbuild. Could potentially be used for anything that represents a +// statement with sub-statements. +// +// ONBUILD RUN foo bar -> (onbuild (run foo bar)) +// +func parseSubCommand(rest string, d *Directive) (*Node, map[string]bool, error) { + if rest == "" { + return nil, nil, nil + } + + _, child, err := ParseLine(rest, d, false) + if err != nil { + return nil, nil, err + } + + return &Node{Children: []*Node{child}}, nil, nil +} + +// helper to parse words (i.e space delimited or quoted strings) in a statement. +// The quotes are preserved as part of this function and they are stripped later +// as part of processWords(). +func parseWords(rest string, d *Directive) []string { + const ( + inSpaces = iota // looking for start of a word + inWord + inQuote + ) + + words := []string{} + phase := inSpaces + word := "" + quote := '\000' + blankOK := false + var ch rune + var chWidth int + + for pos := 0; pos <= len(rest); pos += chWidth { + if pos != len(rest) { + ch, chWidth = utf8.DecodeRuneInString(rest[pos:]) + } + + if phase == inSpaces { // Looking for start of word + if pos == len(rest) { // end of input + break + } + if unicode.IsSpace(ch) { // skip spaces + continue + } + phase = inWord // found it, fall through + } + if (phase == inWord || phase == inQuote) && (pos == len(rest)) { + if blankOK || len(word) > 0 { + words = append(words, word) + } + break + } + if phase == inWord { + if unicode.IsSpace(ch) { + phase = inSpaces + if blankOK || len(word) > 0 { + words = append(words, word) + } + word = "" + blankOK = false + continue + } + if ch == '\'' || ch == '"' { + quote = ch + blankOK = true + phase = inQuote + } + if ch == d.EscapeToken { + if pos+chWidth == len(rest) { + continue // just skip an escape token at end of line + } + // If we're not quoted and we see an escape token, then always just + // add the escape token plus the char to the word, even if the char + // is a quote. + word += string(ch) + pos += chWidth + ch, chWidth = utf8.DecodeRuneInString(rest[pos:]) + } + word += string(ch) + continue + } + if phase == inQuote { + if ch == quote { + phase = inWord + } + // The escape token is special except for ' quotes - can't escape anything for ' + if ch == d.EscapeToken && quote != '\'' { + if pos+chWidth == len(rest) { + phase = inWord + continue // just skip the escape token at end + } + pos += chWidth + word += string(ch) + ch, chWidth = utf8.DecodeRuneInString(rest[pos:]) + } + word += string(ch) + } + } + + return words +} + +// parse environment like statements. Note that this does *not* handle +// variable interpolation, which will be handled in the evaluator. +func parseNameVal(rest string, key string, d *Directive) (*Node, map[string]bool, error) { + // This is kind of tricky because we need to support the old + // variant: KEY name value + // as well as the new one: KEY name=value ... + // The trigger to know which one is being used will be whether we hit + // a space or = first. space ==> old, "=" ==> new + + words := parseWords(rest, d) + if len(words) == 0 { + return nil, nil, nil + } + + var rootnode *Node + + // Old format (KEY name value) + if !strings.Contains(words[0], "=") { + node := &Node{} + rootnode = node + strs := tokenWhitespace.Split(rest, 2) + + if len(strs) < 2 { + return nil, nil, fmt.Errorf(key + " must have two arguments") + } + + node.Value = strs[0] + node.Next = &Node{} + node.Next.Value = strs[1] + } else { + var prevNode *Node + for i, word := range words { + if !strings.Contains(word, "=") { + return nil, nil, fmt.Errorf("Syntax error - can't find = in %q. Must be of the form: name=value", word) + } + parts := strings.SplitN(word, "=", 2) + + name := &Node{} + value := &Node{} + + name.Next = value + name.Value = parts[0] + value.Value = parts[1] + + if i == 0 { + rootnode = name + } else { + prevNode.Next = name + } + prevNode = value + } + } + + return rootnode, nil, nil +} + +func parseEnv(rest string, d *Directive) (*Node, map[string]bool, error) { + return parseNameVal(rest, "ENV", d) +} + +func parseLabel(rest string, d *Directive) (*Node, map[string]bool, error) { + return parseNameVal(rest, "LABEL", d) +} + +// parses a statement containing one or more keyword definition(s) and/or +// value assignments, like `name1 name2= name3="" name4=value`. +// Note that this is a stricter format than the old format of assignment, +// allowed by parseNameVal(), in a way that this only allows assignment of the +// form `keyword=[]` like `name2=`, `name3=""`, and `name4=value` above. +// In addition, a keyword definition alone is of the form `keyword` like `name1` +// above. And the assignments `name2=` and `name3=""` are equivalent and +// assign an empty value to the respective keywords. +func parseNameOrNameVal(rest string, d *Directive) (*Node, map[string]bool, error) { + words := parseWords(rest, d) + if len(words) == 0 { + return nil, nil, nil + } + + var ( + rootnode *Node + prevNode *Node + ) + for i, word := range words { + node := &Node{} + node.Value = word + if i == 0 { + rootnode = node + } else { + prevNode.Next = node + } + prevNode = node + } + + return rootnode, nil, nil +} + +// parses a whitespace-delimited set of arguments. The result is effectively a +// linked list of string arguments. +func parseStringsWhitespaceDelimited(rest string, d *Directive) (*Node, map[string]bool, error) { + if rest == "" { + return nil, nil, nil + } + + node := &Node{} + rootnode := node + prevnode := node + for _, str := range tokenWhitespace.Split(rest, -1) { // use regexp + prevnode = node + node.Value = str + node.Next = &Node{} + node = node.Next + } + + // XXX to get around regexp.Split *always* providing an empty string at the + // end due to how our loop is constructed, nil out the last node in the + // chain. + prevnode.Next = nil + + return rootnode, nil, nil +} + +// parseString just wraps the string in quotes and returns a working node. +func parseString(rest string, d *Directive) (*Node, map[string]bool, error) { + if rest == "" { + return nil, nil, nil + } + n := &Node{} + n.Value = rest + return n, nil, nil +} + +// parseJSON converts JSON arrays to an AST. +func parseJSON(rest string, d *Directive) (*Node, map[string]bool, error) { + rest = strings.TrimLeftFunc(rest, unicode.IsSpace) + if !strings.HasPrefix(rest, "[") { + return nil, nil, fmt.Errorf(`Error parsing "%s" as a JSON array`, rest) + } + + var myJSON []interface{} + if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJSON); err != nil { + return nil, nil, err + } + + var top, prev *Node + for _, str := range myJSON { + s, ok := str.(string) + if !ok { + return nil, nil, errDockerfileNotStringArray + } + + node := &Node{Value: s} + if prev == nil { + top = node + } else { + prev.Next = node + } + prev = node + } + + return top, map[string]bool{"json": true}, nil +} + +// parseMaybeJSON determines if the argument appears to be a JSON array. If +// so, passes to parseJSON; if not, quotes the result and returns a single +// node. +func parseMaybeJSON(rest string, d *Directive) (*Node, map[string]bool, error) { + if rest == "" { + return nil, nil, nil + } + + node, attrs, err := parseJSON(rest, d) + + if err == nil { + return node, attrs, nil + } + if err == errDockerfileNotStringArray { + return nil, nil, err + } + + node = &Node{} + node.Value = rest + return node, nil, nil +} + +// parseMaybeJSONToList determines if the argument appears to be a JSON array. If +// so, passes to parseJSON; if not, attempts to parse it as a whitespace +// delimited string. +func parseMaybeJSONToList(rest string, d *Directive) (*Node, map[string]bool, error) { + node, attrs, err := parseJSON(rest, d) + + if err == nil { + return node, attrs, nil + } + if err == errDockerfileNotStringArray { + return nil, nil, err + } + + return parseStringsWhitespaceDelimited(rest, d) +} + +// The HEALTHCHECK command is like parseMaybeJSON, but has an extra type argument. +func parseHealthConfig(rest string, d *Directive) (*Node, map[string]bool, error) { + // Find end of first argument + var sep int + for ; sep < len(rest); sep++ { + if unicode.IsSpace(rune(rest[sep])) { + break + } + } + next := sep + for ; next < len(rest); next++ { + if !unicode.IsSpace(rune(rest[next])) { + break + } + } + + if sep == 0 { + return nil, nil, nil + } + + typ := rest[:sep] + cmd, attrs, err := parseMaybeJSON(rest[next:], d) + if err != nil { + return nil, nil, err + } + + return &Node{Value: typ, Next: cmd}, attrs, err +} diff --git a/vendor/github.com/docker/docker/builder/dockerfile/parser/parser.go b/vendor/github.com/docker/docker/builder/dockerfile/parser/parser.go new file mode 100644 index 00000000000..1b2dc8f3a02 --- /dev/null +++ b/vendor/github.com/docker/docker/builder/dockerfile/parser/parser.go @@ -0,0 +1,221 @@ +// Package parser implements a parser and parse tree dumper for Dockerfiles. +package parser + +import ( + "bufio" + "bytes" + "fmt" + "io" + "regexp" + "strings" + "unicode" + + "github.com/docker/docker/builder/dockerfile/command" +) + +// Node is a structure used to represent a parse tree. +// +// In the node there are three fields, Value, Next, and Children. Value is the +// current token's string value. Next is always the next non-child token, and +// children contains all the children. Here's an example: +// +// (value next (child child-next child-next-next) next-next) +// +// This data structure is frankly pretty lousy for handling complex languages, +// but lucky for us the Dockerfile isn't very complicated. This structure +// works a little more effectively than a "proper" parse tree for our needs. +// +type Node struct { + Value string // actual content + Next *Node // the next item in the current sexp + Children []*Node // the children of this sexp + Attributes map[string]bool // special attributes for this node + Original string // original line used before parsing + Flags []string // only top Node should have this set + StartLine int // the line in the original dockerfile where the node begins + EndLine int // the line in the original dockerfile where the node ends +} + +// Directive is the structure used during a build run to hold the state of +// parsing directives. +type Directive struct { + EscapeToken rune // Current escape token + LineContinuationRegex *regexp.Regexp // Current line continuation regex + LookingForDirectives bool // Whether we are currently looking for directives + EscapeSeen bool // Whether the escape directive has been seen +} + +var ( + dispatch map[string]func(string, *Directive) (*Node, map[string]bool, error) + tokenWhitespace = regexp.MustCompile(`[\t\v\f\r ]+`) + tokenEscapeCommand = regexp.MustCompile(`^#[ \t]*escape[ \t]*=[ \t]*(?P.).*$`) + tokenComment = regexp.MustCompile(`^#.*$`) +) + +// DefaultEscapeToken is the default escape token +const DefaultEscapeToken = "\\" + +// SetEscapeToken sets the default token for escaping characters in a Dockerfile. +func SetEscapeToken(s string, d *Directive) error { + if s != "`" && s != "\\" { + return fmt.Errorf("invalid ESCAPE '%s'. Must be ` or \\", s) + } + d.EscapeToken = rune(s[0]) + d.LineContinuationRegex = regexp.MustCompile(`\` + s + `[ \t]*$`) + return nil +} + +func init() { + // Dispatch Table. see line_parsers.go for the parse functions. + // The command is parsed and mapped to the line parser. The line parser + // receives the arguments but not the command, and returns an AST after + // reformulating the arguments according to the rules in the parser + // functions. Errors are propagated up by Parse() and the resulting AST can + // be incorporated directly into the existing AST as a next. + dispatch = map[string]func(string, *Directive) (*Node, map[string]bool, error){ + command.Add: parseMaybeJSONToList, + command.Arg: parseNameOrNameVal, + command.Cmd: parseMaybeJSON, + command.Copy: parseMaybeJSONToList, + command.Entrypoint: parseMaybeJSON, + command.Env: parseEnv, + command.Expose: parseStringsWhitespaceDelimited, + command.From: parseString, + command.Healthcheck: parseHealthConfig, + command.Label: parseLabel, + command.Maintainer: parseString, + command.Onbuild: parseSubCommand, + command.Run: parseMaybeJSON, + command.Shell: parseMaybeJSON, + command.StopSignal: parseString, + command.User: parseString, + command.Volume: parseMaybeJSONToList, + command.Workdir: parseString, + } +} + +// ParseLine parses a line and returns the remainder. +func ParseLine(line string, d *Directive, ignoreCont bool) (string, *Node, error) { + // Handle the parser directive '# escape=. Parser directives must precede + // any builder instruction or other comments, and cannot be repeated. + if d.LookingForDirectives { + tecMatch := tokenEscapeCommand.FindStringSubmatch(strings.ToLower(line)) + if len(tecMatch) > 0 { + if d.EscapeSeen == true { + return "", nil, fmt.Errorf("only one escape parser directive can be used") + } + for i, n := range tokenEscapeCommand.SubexpNames() { + if n == "escapechar" { + if err := SetEscapeToken(tecMatch[i], d); err != nil { + return "", nil, err + } + d.EscapeSeen = true + return "", nil, nil + } + } + } + } + + d.LookingForDirectives = false + + if line = stripComments(line); line == "" { + return "", nil, nil + } + + if !ignoreCont && d.LineContinuationRegex.MatchString(line) { + line = d.LineContinuationRegex.ReplaceAllString(line, "") + return line, nil, nil + } + + cmd, flags, args, err := splitCommand(line) + if err != nil { + return "", nil, err + } + + node := &Node{} + node.Value = cmd + + sexp, attrs, err := fullDispatch(cmd, args, d) + if err != nil { + return "", nil, err + } + + node.Next = sexp + node.Attributes = attrs + node.Original = line + node.Flags = flags + + return "", node, nil +} + +// Parse is the main parse routine. +// It handles an io.ReadWriteCloser and returns the root of the AST. +func Parse(rwc io.Reader, d *Directive) (*Node, error) { + currentLine := 0 + root := &Node{} + root.StartLine = -1 + scanner := bufio.NewScanner(rwc) + + utf8bom := []byte{0xEF, 0xBB, 0xBF} + for scanner.Scan() { + scannedBytes := scanner.Bytes() + // We trim UTF8 BOM + if currentLine == 0 { + scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom) + } + scannedLine := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace) + currentLine++ + line, child, err := ParseLine(scannedLine, d, false) + if err != nil { + return nil, err + } + startLine := currentLine + + if line != "" && child == nil { + for scanner.Scan() { + newline := scanner.Text() + currentLine++ + + if stripComments(strings.TrimSpace(newline)) == "" { + continue + } + + line, child, err = ParseLine(line+newline, d, false) + if err != nil { + return nil, err + } + + if child != nil { + break + } + } + if child == nil && line != "" { + // When we call ParseLine we'll pass in 'true' for + // the ignoreCont param if we're at the EOF. This will + // prevent the func from returning immediately w/o + // parsing the line thinking that there's more input + // to come. + + _, child, err = ParseLine(line, d, scanner.Err() == nil) + if err != nil { + return nil, err + } + } + } + + if child != nil { + // Update the line information for the current child. + child.StartLine = startLine + child.EndLine = currentLine + // Update the line information for the root. The starting line of the root is always the + // starting line of the first child and the ending line is the ending line of the last child. + if root.StartLine < 0 { + root.StartLine = currentLine + } + root.EndLine = currentLine + root.Children = append(root.Children, child) + } + } + + return root, nil +} diff --git a/vendor/github.com/docker/docker/builder/dockerfile/parser/utils.go b/vendor/github.com/docker/docker/builder/dockerfile/parser/utils.go new file mode 100644 index 00000000000..1f77f057ce9 --- /dev/null +++ b/vendor/github.com/docker/docker/builder/dockerfile/parser/utils.go @@ -0,0 +1,174 @@ +package parser + +import ( + "fmt" + "strconv" + "strings" + "unicode" +) + +// Dump dumps the AST defined by `node` as a list of sexps. +// Returns a string suitable for printing. +func (node *Node) Dump() string { + str := "" + str += node.Value + + if len(node.Flags) > 0 { + str += fmt.Sprintf(" %q", node.Flags) + } + + for _, n := range node.Children { + str += "(" + n.Dump() + ")\n" + } + + for n := node.Next; n != nil; n = n.Next { + if len(n.Children) > 0 { + str += " " + n.Dump() + } else { + str += " " + strconv.Quote(n.Value) + } + } + + return strings.TrimSpace(str) +} + +// performs the dispatch based on the two primal strings, cmd and args. Please +// look at the dispatch table in parser.go to see how these dispatchers work. +func fullDispatch(cmd, args string, d *Directive) (*Node, map[string]bool, error) { + fn := dispatch[cmd] + + // Ignore invalid Dockerfile instructions + if fn == nil { + fn = parseIgnore + } + + sexp, attrs, err := fn(args, d) + if err != nil { + return nil, nil, err + } + + return sexp, attrs, nil +} + +// splitCommand takes a single line of text and parses out the cmd and args, +// which are used for dispatching to more exact parsing functions. +func splitCommand(line string) (string, []string, string, error) { + var args string + var flags []string + + // Make sure we get the same results irrespective of leading/trailing spaces + cmdline := tokenWhitespace.Split(strings.TrimSpace(line), 2) + cmd := strings.ToLower(cmdline[0]) + + if len(cmdline) == 2 { + var err error + args, flags, err = extractBuilderFlags(cmdline[1]) + if err != nil { + return "", nil, "", err + } + } + + return cmd, flags, strings.TrimSpace(args), nil +} + +// covers comments and empty lines. Lines should be trimmed before passing to +// this function. +func stripComments(line string) string { + // string is already trimmed at this point + if tokenComment.MatchString(line) { + return tokenComment.ReplaceAllString(line, "") + } + + return line +} + +func extractBuilderFlags(line string) (string, []string, error) { + // Parses the BuilderFlags and returns the remaining part of the line + + const ( + inSpaces = iota // looking for start of a word + inWord + inQuote + ) + + words := []string{} + phase := inSpaces + word := "" + quote := '\000' + blankOK := false + var ch rune + + for pos := 0; pos <= len(line); pos++ { + if pos != len(line) { + ch = rune(line[pos]) + } + + if phase == inSpaces { // Looking for start of word + if pos == len(line) { // end of input + break + } + if unicode.IsSpace(ch) { // skip spaces + continue + } + + // Only keep going if the next word starts with -- + if ch != '-' || pos+1 == len(line) || rune(line[pos+1]) != '-' { + return line[pos:], words, nil + } + + phase = inWord // found something with "--", fall through + } + if (phase == inWord || phase == inQuote) && (pos == len(line)) { + if word != "--" && (blankOK || len(word) > 0) { + words = append(words, word) + } + break + } + if phase == inWord { + if unicode.IsSpace(ch) { + phase = inSpaces + if word == "--" { + return line[pos:], words, nil + } + if blankOK || len(word) > 0 { + words = append(words, word) + } + word = "" + blankOK = false + continue + } + if ch == '\'' || ch == '"' { + quote = ch + blankOK = true + phase = inQuote + continue + } + if ch == '\\' { + if pos+1 == len(line) { + continue // just skip \ at end + } + pos++ + ch = rune(line[pos]) + } + word += string(ch) + continue + } + if phase == inQuote { + if ch == quote { + phase = inWord + continue + } + if ch == '\\' { + if pos+1 == len(line) { + phase = inWord + continue // just skip \ at end + } + pos++ + ch = rune(line[pos]) + } + word += string(ch) + } + } + + return "", words, nil +} diff --git a/vendor/github.com/docker/docker/opts/env.go b/vendor/github.com/docker/docker/opts/env.go new file mode 100644 index 00000000000..e6ddd733090 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/env.go @@ -0,0 +1,46 @@ +package opts + +import ( + "fmt" + "os" + "runtime" + "strings" +) + +// ValidateEnv validates an environment variable and returns it. +// If no value is specified, it returns the current value using os.Getenv. +// +// As on ParseEnvFile and related to #16585, environment variable names +// are not validate what so ever, it's up to application inside docker +// to validate them or not. +// +// The only validation here is to check if name is empty, per #25099 +func ValidateEnv(val string) (string, error) { + arr := strings.Split(val, "=") + if arr[0] == "" { + return "", fmt.Errorf("invalid environment variable: %s", val) + } + if len(arr) > 1 { + return val, nil + } + if !doesEnvExist(val) { + return val, nil + } + return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil +} + +func doesEnvExist(name string) bool { + for _, entry := range os.Environ() { + parts := strings.SplitN(entry, "=", 2) + if runtime.GOOS == "windows" { + // Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent. + if strings.EqualFold(parts[0], name) { + return true + } + } + if parts[0] == name { + return true + } + } + return false +} diff --git a/vendor/github.com/docker/docker/opts/hosts.go b/vendor/github.com/docker/docker/opts/hosts.go new file mode 100644 index 00000000000..594cccf2fbe --- /dev/null +++ b/vendor/github.com/docker/docker/opts/hosts.go @@ -0,0 +1,165 @@ +package opts + +import ( + "fmt" + "net" + "net/url" + "strconv" + "strings" +) + +var ( + // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. dockerd -H tcp:// + // These are the IANA registered port numbers for use with Docker + // see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker + DefaultHTTPPort = 2375 // Default HTTP Port + // DefaultTLSHTTPPort Default HTTP Port used when TLS enabled + DefaultTLSHTTPPort = 2376 // Default TLS encrypted HTTP Port + // DefaultUnixSocket Path for the unix socket. + // Docker daemon by default always listens on the default unix socket + DefaultUnixSocket = "/var/run/docker.sock" + // DefaultTCPHost constant defines the default host string used by docker on Windows + DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) + // DefaultTLSHost constant defines the default host string used by docker for TLS sockets + DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort) + // DefaultNamedPipe defines the default named pipe used by docker on Windows + DefaultNamedPipe = `//./pipe/docker_engine` +) + +// ValidateHost validates that the specified string is a valid host and returns it. +func ValidateHost(val string) (string, error) { + host := strings.TrimSpace(val) + // The empty string means default and is not handled by parseDockerDaemonHost + if host != "" { + _, err := parseDockerDaemonHost(host) + if err != nil { + return val, err + } + } + // Note: unlike most flag validators, we don't return the mutated value here + // we need to know what the user entered later (using ParseHost) to adjust for TLS + return val, nil +} + +// ParseHost and set defaults for a Daemon host string +func ParseHost(defaultToTLS bool, val string) (string, error) { + host := strings.TrimSpace(val) + if host == "" { + if defaultToTLS { + host = DefaultTLSHost + } else { + host = DefaultHost + } + } else { + var err error + host, err = parseDockerDaemonHost(host) + if err != nil { + return val, err + } + } + return host, nil +} + +// parseDockerDaemonHost parses the specified address and returns an address that will be used as the host. +// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go. +func parseDockerDaemonHost(addr string) (string, error) { + addrParts := strings.SplitN(addr, "://", 2) + if len(addrParts) == 1 && addrParts[0] != "" { + addrParts = []string{"tcp", addrParts[0]} + } + + switch addrParts[0] { + case "tcp": + return ParseTCPAddr(addrParts[1], DefaultTCPHost) + case "unix": + return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket) + case "npipe": + return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe) + case "fd": + return addr, nil + default: + return "", fmt.Errorf("Invalid bind address format: %s", addr) + } +} + +// parseSimpleProtoAddr parses and validates that the specified address is a valid +// socket address for simple protocols like unix and npipe. It returns a formatted +// socket address, either using the address parsed from addr, or the contents of +// defaultAddr if addr is a blank string. +func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) { + addr = strings.TrimPrefix(addr, proto+"://") + if strings.Contains(addr, "://") { + return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr) + } + if addr == "" { + addr = defaultAddr + } + return fmt.Sprintf("%s://%s", proto, addr), nil +} + +// ParseTCPAddr parses and validates that the specified address is a valid TCP +// address. It returns a formatted TCP address, either using the address parsed +// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string. +// tryAddr is expected to have already been Trim()'d +// defaultAddr must be in the full `tcp://host:port` form +func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) { + if tryAddr == "" || tryAddr == "tcp://" { + return defaultAddr, nil + } + addr := strings.TrimPrefix(tryAddr, "tcp://") + if strings.Contains(addr, "://") || addr == "" { + return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr) + } + + defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://") + defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr) + if err != nil { + return "", err + } + // url.Parse fails for trailing colon on IPv6 brackets on Go 1.5, but + // not 1.4. See https://github.com/golang/go/issues/12200 and + // https://github.com/golang/go/issues/6530. + if strings.HasSuffix(addr, "]:") { + addr += defaultPort + } + + u, err := url.Parse("tcp://" + addr) + if err != nil { + return "", err + } + host, port, err := net.SplitHostPort(u.Host) + if err != nil { + // try port addition once + host, port, err = net.SplitHostPort(net.JoinHostPort(u.Host, defaultPort)) + } + if err != nil { + return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) + } + + if host == "" { + host = defaultHost + } + if port == "" { + port = defaultPort + } + p, err := strconv.Atoi(port) + if err != nil && p == 0 { + return "", fmt.Errorf("Invalid bind address format: %s", tryAddr) + } + + return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil +} + +// ValidateExtraHost validates that the specified string is a valid extrahost and returns it. +// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6). +func ValidateExtraHost(val string) (string, error) { + // allow for IPv6 addresses in extra hosts by only splitting on first ":" + arr := strings.SplitN(val, ":", 2) + if len(arr) != 2 || len(arr[0]) == 0 { + return "", fmt.Errorf("bad format for add-host: %q", val) + } + if _, err := ValidateIPAddress(arr[1]); err != nil { + return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1]) + } + return val, nil +} diff --git a/vendor/github.com/docker/docker/opts/hosts_unix.go b/vendor/github.com/docker/docker/opts/hosts_unix.go new file mode 100644 index 00000000000..611407a9d94 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/hosts_unix.go @@ -0,0 +1,8 @@ +// +build !windows + +package opts + +import "fmt" + +// DefaultHost constant defines the default host string used by docker on other hosts than Windows +var DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket) diff --git a/vendor/github.com/docker/docker/opts/hosts_windows.go b/vendor/github.com/docker/docker/opts/hosts_windows.go new file mode 100644 index 00000000000..7c239e00f1e --- /dev/null +++ b/vendor/github.com/docker/docker/opts/hosts_windows.go @@ -0,0 +1,6 @@ +// +build windows + +package opts + +// DefaultHost constant defines the default host string used by docker on Windows +var DefaultHost = "npipe://" + DefaultNamedPipe diff --git a/vendor/github.com/docker/docker/opts/ip.go b/vendor/github.com/docker/docker/opts/ip.go new file mode 100644 index 00000000000..fb03b50111f --- /dev/null +++ b/vendor/github.com/docker/docker/opts/ip.go @@ -0,0 +1,47 @@ +package opts + +import ( + "fmt" + "net" +) + +// IPOpt holds an IP. It is used to store values from CLI flags. +type IPOpt struct { + *net.IP +} + +// NewIPOpt creates a new IPOpt from a reference net.IP and a +// string representation of an IP. If the string is not a valid +// IP it will fallback to the specified reference. +func NewIPOpt(ref *net.IP, defaultVal string) *IPOpt { + o := &IPOpt{ + IP: ref, + } + o.Set(defaultVal) + return o +} + +// Set sets an IPv4 or IPv6 address from a given string. If the given +// string is not parseable as an IP address it returns an error. +func (o *IPOpt) Set(val string) error { + ip := net.ParseIP(val) + if ip == nil { + return fmt.Errorf("%s is not an ip address", val) + } + *o.IP = ip + return nil +} + +// String returns the IP address stored in the IPOpt. If stored IP is a +// nil pointer, it returns an empty string. +func (o *IPOpt) String() string { + if *o.IP == nil { + return "" + } + return o.IP.String() +} + +// Type returns the type of the option +func (o *IPOpt) Type() string { + return "ip" +} diff --git a/vendor/github.com/docker/docker/opts/mount.go b/vendor/github.com/docker/docker/opts/mount.go new file mode 100644 index 00000000000..d4ccf838d90 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/mount.go @@ -0,0 +1,173 @@ +package opts + +import ( + "encoding/csv" + "fmt" + "os" + "strconv" + "strings" + + mounttypes "github.com/docker/docker/api/types/mount" + "github.com/docker/go-units" +) + +// MountOpt is a Value type for parsing mounts +type MountOpt struct { + values []mounttypes.Mount +} + +// Set a new mount value +func (m *MountOpt) Set(value string) error { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err + } + + mount := mounttypes.Mount{} + + volumeOptions := func() *mounttypes.VolumeOptions { + if mount.VolumeOptions == nil { + mount.VolumeOptions = &mounttypes.VolumeOptions{ + Labels: make(map[string]string), + } + } + if mount.VolumeOptions.DriverConfig == nil { + mount.VolumeOptions.DriverConfig = &mounttypes.Driver{} + } + return mount.VolumeOptions + } + + bindOptions := func() *mounttypes.BindOptions { + if mount.BindOptions == nil { + mount.BindOptions = new(mounttypes.BindOptions) + } + return mount.BindOptions + } + + tmpfsOptions := func() *mounttypes.TmpfsOptions { + if mount.TmpfsOptions == nil { + mount.TmpfsOptions = new(mounttypes.TmpfsOptions) + } + return mount.TmpfsOptions + } + + setValueOnMap := func(target map[string]string, value string) { + parts := strings.SplitN(value, "=", 2) + if len(parts) == 1 { + target[value] = "" + } else { + target[parts[0]] = parts[1] + } + } + + mount.Type = mounttypes.TypeVolume // default to volume mounts + // Set writable as the default + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + key := strings.ToLower(parts[0]) + + if len(parts) == 1 { + switch key { + case "readonly", "ro": + mount.ReadOnly = true + continue + case "volume-nocopy": + volumeOptions().NoCopy = true + continue + } + } + + if len(parts) != 2 { + return fmt.Errorf("invalid field '%s' must be a key=value pair", field) + } + + value := parts[1] + switch key { + case "type": + mount.Type = mounttypes.Type(strings.ToLower(value)) + case "source", "src": + mount.Source = value + case "target", "dst", "destination": + mount.Target = value + case "readonly", "ro": + mount.ReadOnly, err = strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + case "consistency": + mount.Consistency = mounttypes.Consistency(strings.ToLower(value)) + case "bind-propagation": + bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value)) + case "volume-nocopy": + volumeOptions().NoCopy, err = strconv.ParseBool(value) + if err != nil { + return fmt.Errorf("invalid value for volume-nocopy: %s", value) + } + case "volume-label": + setValueOnMap(volumeOptions().Labels, value) + case "volume-driver": + volumeOptions().DriverConfig.Name = value + case "volume-opt": + if volumeOptions().DriverConfig.Options == nil { + volumeOptions().DriverConfig.Options = make(map[string]string) + } + setValueOnMap(volumeOptions().DriverConfig.Options, value) + case "tmpfs-size": + sizeBytes, err := units.RAMInBytes(value) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + tmpfsOptions().SizeBytes = sizeBytes + case "tmpfs-mode": + ui64, err := strconv.ParseUint(value, 8, 32) + if err != nil { + return fmt.Errorf("invalid value for %s: %s", key, value) + } + tmpfsOptions().Mode = os.FileMode(ui64) + default: + return fmt.Errorf("unexpected key '%s' in '%s'", key, field) + } + } + + if mount.Type == "" { + return fmt.Errorf("type is required") + } + + if mount.Target == "" { + return fmt.Errorf("target is required") + } + + if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume { + return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type) + } + if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind { + return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type) + } + if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs { + return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type) + } + + m.values = append(m.values, mount) + return nil +} + +// Type returns the type of this option +func (m *MountOpt) Type() string { + return "mount" +} + +// String returns a string repr of this option +func (m *MountOpt) String() string { + mounts := []string{} + for _, mount := range m.values { + repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target) + mounts = append(mounts, repr) + } + return strings.Join(mounts, ", ") +} + +// Value returns the mounts +func (m *MountOpt) Value() []mounttypes.Mount { + return m.values +} diff --git a/vendor/github.com/docker/docker/opts/opts.go b/vendor/github.com/docker/docker/opts/opts.go new file mode 100644 index 00000000000..8c82960c20d --- /dev/null +++ b/vendor/github.com/docker/docker/opts/opts.go @@ -0,0 +1,482 @@ +package opts + +import ( + "fmt" + "math/big" + "net" + "path" + "regexp" + "strings" + + "github.com/docker/docker/api/types/filters" + units "github.com/docker/go-units" +) + +var ( + alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) + domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`) +) + +// ListOpts holds a list of values and a validation function. +type ListOpts struct { + values *[]string + validator ValidatorFctType +} + +// NewListOpts creates a new ListOpts with the specified validator. +func NewListOpts(validator ValidatorFctType) ListOpts { + var values []string + return *NewListOptsRef(&values, validator) +} + +// NewListOptsRef creates a new ListOpts with the specified values and validator. +func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts { + return &ListOpts{ + values: values, + validator: validator, + } +} + +func (opts *ListOpts) String() string { + return fmt.Sprintf("%v", []string((*opts.values))) +} + +// Set validates if needed the input value and adds it to the +// internal slice. +func (opts *ListOpts) Set(value string) error { + if opts.validator != nil { + v, err := opts.validator(value) + if err != nil { + return err + } + value = v + } + (*opts.values) = append((*opts.values), value) + return nil +} + +// Delete removes the specified element from the slice. +func (opts *ListOpts) Delete(key string) { + for i, k := range *opts.values { + if k == key { + (*opts.values) = append((*opts.values)[:i], (*opts.values)[i+1:]...) + return + } + } +} + +// GetMap returns the content of values in a map in order to avoid +// duplicates. +func (opts *ListOpts) GetMap() map[string]struct{} { + ret := make(map[string]struct{}) + for _, k := range *opts.values { + ret[k] = struct{}{} + } + return ret +} + +// GetAll returns the values of slice. +func (opts *ListOpts) GetAll() []string { + return (*opts.values) +} + +// GetAllOrEmpty returns the values of the slice +// or an empty slice when there are no values. +func (opts *ListOpts) GetAllOrEmpty() []string { + v := *opts.values + if v == nil { + return make([]string, 0) + } + return v +} + +// Get checks the existence of the specified key. +func (opts *ListOpts) Get(key string) bool { + for _, k := range *opts.values { + if k == key { + return true + } + } + return false +} + +// Len returns the amount of element in the slice. +func (opts *ListOpts) Len() int { + return len((*opts.values)) +} + +// Type returns a string name for this Option type +func (opts *ListOpts) Type() string { + return "list" +} + +// WithValidator returns the ListOpts with validator set. +func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts { + opts.validator = validator + return opts +} + +// NamedOption is an interface that list and map options +// with names implement. +type NamedOption interface { + Name() string +} + +// NamedListOpts is a ListOpts with a configuration name. +// This struct is useful to keep reference to the assigned +// field name in the internal configuration struct. +type NamedListOpts struct { + name string + ListOpts +} + +var _ NamedOption = &NamedListOpts{} + +// NewNamedListOptsRef creates a reference to a new NamedListOpts struct. +func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts { + return &NamedListOpts{ + name: name, + ListOpts: *NewListOptsRef(values, validator), + } +} + +// Name returns the name of the NamedListOpts in the configuration. +func (o *NamedListOpts) Name() string { + return o.name +} + +// MapOpts holds a map of values and a validation function. +type MapOpts struct { + values map[string]string + validator ValidatorFctType +} + +// Set validates if needed the input value and add it to the +// internal map, by splitting on '='. +func (opts *MapOpts) Set(value string) error { + if opts.validator != nil { + v, err := opts.validator(value) + if err != nil { + return err + } + value = v + } + vals := strings.SplitN(value, "=", 2) + if len(vals) == 1 { + (opts.values)[vals[0]] = "" + } else { + (opts.values)[vals[0]] = vals[1] + } + return nil +} + +// GetAll returns the values of MapOpts as a map. +func (opts *MapOpts) GetAll() map[string]string { + return opts.values +} + +func (opts *MapOpts) String() string { + return fmt.Sprintf("%v", map[string]string((opts.values))) +} + +// Type returns a string name for this Option type +func (opts *MapOpts) Type() string { + return "map" +} + +// NewMapOpts creates a new MapOpts with the specified map of values and a validator. +func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts { + if values == nil { + values = make(map[string]string) + } + return &MapOpts{ + values: values, + validator: validator, + } +} + +// NamedMapOpts is a MapOpts struct with a configuration name. +// This struct is useful to keep reference to the assigned +// field name in the internal configuration struct. +type NamedMapOpts struct { + name string + MapOpts +} + +var _ NamedOption = &NamedMapOpts{} + +// NewNamedMapOpts creates a reference to a new NamedMapOpts struct. +func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts { + return &NamedMapOpts{ + name: name, + MapOpts: *NewMapOpts(values, validator), + } +} + +// Name returns the name of the NamedMapOpts in the configuration. +func (o *NamedMapOpts) Name() string { + return o.name +} + +// ValidatorFctType defines a validator function that returns a validated string and/or an error. +type ValidatorFctType func(val string) (string, error) + +// ValidatorFctListType defines a validator function that returns a validated list of string and/or an error +type ValidatorFctListType func(val string) ([]string, error) + +// ValidateIPAddress validates an Ip address. +func ValidateIPAddress(val string) (string, error) { + var ip = net.ParseIP(strings.TrimSpace(val)) + if ip != nil { + return ip.String(), nil + } + return "", fmt.Errorf("%s is not an ip address", val) +} + +// ValidateMACAddress validates a MAC address. +func ValidateMACAddress(val string) (string, error) { + _, err := net.ParseMAC(strings.TrimSpace(val)) + if err != nil { + return "", err + } + return val, nil +} + +// ValidateDNSSearch validates domain for resolvconf search configuration. +// A zero length domain is represented by a dot (.). +func ValidateDNSSearch(val string) (string, error) { + if val = strings.Trim(val, " "); val == "." { + return val, nil + } + return validateDomain(val) +} + +func validateDomain(val string) (string, error) { + if alphaRegexp.FindString(val) == "" { + return "", fmt.Errorf("%s is not a valid domain", val) + } + ns := domainRegexp.FindSubmatch([]byte(val)) + if len(ns) > 0 && len(ns[1]) < 255 { + return string(ns[1]), nil + } + return "", fmt.Errorf("%s is not a valid domain", val) +} + +// ValidateLabel validates that the specified string is a valid label, and returns it. +// Labels are in the form on key=value. +func ValidateLabel(val string) (string, error) { + if strings.Count(val, "=") < 1 { + return "", fmt.Errorf("bad attribute format: %s", val) + } + return val, nil +} + +// ValidateSysctl validates a sysctl and returns it. +func ValidateSysctl(val string) (string, error) { + validSysctlMap := map[string]bool{ + "kernel.msgmax": true, + "kernel.msgmnb": true, + "kernel.msgmni": true, + "kernel.sem": true, + "kernel.shmall": true, + "kernel.shmmax": true, + "kernel.shmmni": true, + "kernel.shm_rmid_forced": true, + } + validSysctlPrefixes := []string{ + "net.", + "fs.mqueue.", + } + arr := strings.Split(val, "=") + if len(arr) < 2 { + return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) + } + if validSysctlMap[arr[0]] { + return val, nil + } + + for _, vp := range validSysctlPrefixes { + if strings.HasPrefix(arr[0], vp) { + return val, nil + } + } + return "", fmt.Errorf("sysctl '%s' is not whitelisted", val) +} + +// FilterOpt is a flag type for validating filters +type FilterOpt struct { + filter filters.Args +} + +// NewFilterOpt returns a new FilterOpt +func NewFilterOpt() FilterOpt { + return FilterOpt{filter: filters.NewArgs()} +} + +func (o *FilterOpt) String() string { + repr, err := filters.ToParam(o.filter) + if err != nil { + return "invalid filters" + } + return repr +} + +// Set sets the value of the opt by parsing the command line value +func (o *FilterOpt) Set(value string) error { + var err error + o.filter, err = filters.ParseFlag(value, o.filter) + return err +} + +// Type returns the option type +func (o *FilterOpt) Type() string { + return "filter" +} + +// Value returns the value of this option +func (o *FilterOpt) Value() filters.Args { + return o.filter +} + +// NanoCPUs is a type for fixed point fractional number. +type NanoCPUs int64 + +// String returns the string format of the number +func (c *NanoCPUs) String() string { + return big.NewRat(c.Value(), 1e9).FloatString(3) +} + +// Set sets the value of the NanoCPU by passing a string +func (c *NanoCPUs) Set(value string) error { + cpus, err := ParseCPUs(value) + *c = NanoCPUs(cpus) + return err +} + +// Type returns the type +func (c *NanoCPUs) Type() string { + return "decimal" +} + +// Value returns the value in int64 +func (c *NanoCPUs) Value() int64 { + return int64(*c) +} + +// ParseCPUs takes a string ratio and returns an integer value of nano cpus +func ParseCPUs(value string) (int64, error) { + cpu, ok := new(big.Rat).SetString(value) + if !ok { + return 0, fmt.Errorf("failed to parse %v as a rational number", value) + } + nano := cpu.Mul(cpu, big.NewRat(1e9, 1)) + if !nano.IsInt() { + return 0, fmt.Errorf("value is too precise") + } + return nano.Num().Int64(), nil +} + +// ParseLink parses and validates the specified string as a link format (name:alias) +func ParseLink(val string) (string, string, error) { + if val == "" { + return "", "", fmt.Errorf("empty string specified for links") + } + arr := strings.Split(val, ":") + if len(arr) > 2 { + return "", "", fmt.Errorf("bad format for links: %s", val) + } + if len(arr) == 1 { + return val, val, nil + } + // This is kept because we can actually get a HostConfig with links + // from an already created container and the format is not `foo:bar` + // but `/foo:/c1/bar` + if strings.HasPrefix(arr[0], "/") { + _, alias := path.Split(arr[1]) + return arr[0][1:], alias, nil + } + return arr[0], arr[1], nil +} + +// ValidateLink validates that the specified string has a valid link format (containerName:alias). +func ValidateLink(val string) (string, error) { + _, _, err := ParseLink(val) + return val, err +} + +// MemBytes is a type for human readable memory bytes (like 128M, 2g, etc) +type MemBytes int64 + +// String returns the string format of the human readable memory bytes +func (m *MemBytes) String() string { + // NOTE: In spf13/pflag/flag.go, "0" is considered as "zero value" while "0 B" is not. + // We return "0" in case value is 0 here so that the default value is hidden. + // (Sometimes "default 0 B" is actually misleading) + if m.Value() != 0 { + return units.BytesSize(float64(m.Value())) + } + return "0" +} + +// Set sets the value of the MemBytes by passing a string +func (m *MemBytes) Set(value string) error { + val, err := units.RAMInBytes(value) + *m = MemBytes(val) + return err +} + +// Type returns the type +func (m *MemBytes) Type() string { + return "bytes" +} + +// Value returns the value in int64 +func (m *MemBytes) Value() int64 { + return int64(*m) +} + +// UnmarshalJSON is the customized unmarshaler for MemBytes +func (m *MemBytes) UnmarshalJSON(s []byte) error { + if len(s) <= 2 || s[0] != '"' || s[len(s)-1] != '"' { + return fmt.Errorf("invalid size: %q", s) + } + val, err := units.RAMInBytes(string(s[1 : len(s)-1])) + *m = MemBytes(val) + return err +} + +// MemSwapBytes is a type for human readable memory bytes (like 128M, 2g, etc). +// It differs from MemBytes in that -1 is valid and the default. +type MemSwapBytes int64 + +// Set sets the value of the MemSwapBytes by passing a string +func (m *MemSwapBytes) Set(value string) error { + if value == "-1" { + *m = MemSwapBytes(-1) + return nil + } + val, err := units.RAMInBytes(value) + *m = MemSwapBytes(val) + return err +} + +// Type returns the type +func (m *MemSwapBytes) Type() string { + return "bytes" +} + +// Value returns the value in int64 +func (m *MemSwapBytes) Value() int64 { + return int64(*m) +} + +func (m *MemSwapBytes) String() string { + b := MemBytes(*m) + return b.String() +} + +// UnmarshalJSON is the customized unmarshaler for MemSwapBytes +func (m *MemSwapBytes) UnmarshalJSON(s []byte) error { + b := MemBytes(*m) + return b.UnmarshalJSON(s) +} diff --git a/vendor/github.com/docker/docker/opts/opts_unix.go b/vendor/github.com/docker/docker/opts/opts_unix.go new file mode 100644 index 00000000000..2766a43a088 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/opts_unix.go @@ -0,0 +1,6 @@ +// +build !windows + +package opts + +// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080 +const DefaultHTTPHost = "localhost" diff --git a/vendor/github.com/docker/docker/opts/opts_windows.go b/vendor/github.com/docker/docker/opts/opts_windows.go new file mode 100644 index 00000000000..98b7251a9e4 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/opts_windows.go @@ -0,0 +1,56 @@ +package opts + +// TODO Windows. Identify bug in GOLang 1.5.1+ and/or Windows Server 2016 TP5. +// @jhowardmsft, @swernli. +// +// On Windows, this mitigates a problem with the default options of running +// a docker client against a local docker daemon on TP5. +// +// What was found that if the default host is "localhost", even if the client +// (and daemon as this is local) is not physically on a network, and the DNS +// cache is flushed (ipconfig /flushdns), then the client will pause for +// exactly one second when connecting to the daemon for calls. For example +// using docker run windowsservercore cmd, the CLI will send a create followed +// by an attach. You see the delay between the attach finishing and the attach +// being seen by the daemon. +// +// Here's some daemon debug logs with additional debug spew put in. The +// AfterWriteJSON log is the very last thing the daemon does as part of the +// create call. The POST /attach is the second CLI call. Notice the second +// time gap. +// +// time="2015-11-06T13:38:37.259627400-08:00" level=debug msg="After createRootfs" +// time="2015-11-06T13:38:37.263626300-08:00" level=debug msg="After setHostConfig" +// time="2015-11-06T13:38:37.267631200-08:00" level=debug msg="before createContainerPl...." +// time="2015-11-06T13:38:37.271629500-08:00" level=debug msg=ToDiskLocking.... +// time="2015-11-06T13:38:37.275643200-08:00" level=debug msg="loggin event...." +// time="2015-11-06T13:38:37.277627600-08:00" level=debug msg="logged event...." +// time="2015-11-06T13:38:37.279631800-08:00" level=debug msg="In defer func" +// time="2015-11-06T13:38:37.282628100-08:00" level=debug msg="After daemon.create" +// time="2015-11-06T13:38:37.286651700-08:00" level=debug msg="return 2" +// time="2015-11-06T13:38:37.289629500-08:00" level=debug msg="Returned from daemon.ContainerCreate" +// time="2015-11-06T13:38:37.311629100-08:00" level=debug msg="After WriteJSON" +// ... 1 second gap here.... +// time="2015-11-06T13:38:38.317866200-08:00" level=debug msg="Calling POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach" +// time="2015-11-06T13:38:38.326882500-08:00" level=info msg="POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach?stderr=1&stdin=1&stdout=1&stream=1" +// +// We suspect this is either a bug introduced in GOLang 1.5.1, or that a change +// in GOLang 1.5.1 (from 1.4.3) is exposing a bug in Windows. In theory, +// the Windows networking stack is supposed to resolve "localhost" internally, +// without hitting DNS, or even reading the hosts file (which is why localhost +// is commented out in the hosts file on Windows). +// +// We have validated that working around this using the actual IPv4 localhost +// address does not cause the delay. +// +// This does not occur with the docker client built with 1.4.3 on the same +// Windows build, regardless of whether the daemon is built using 1.5.1 +// or 1.4.3. It does not occur on Linux. We also verified we see the same thing +// on a cross-compiled Windows binary (from Linux). +// +// Final note: This is a mitigation, not a 'real' fix. It is still susceptible +// to the delay if a user were to do 'docker run -H=tcp://localhost:2375...' +// explicitly. + +// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080 +const DefaultHTTPHost = "127.0.0.1" diff --git a/vendor/github.com/docker/docker/opts/port.go b/vendor/github.com/docker/docker/opts/port.go new file mode 100644 index 00000000000..152683c9814 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/port.go @@ -0,0 +1,162 @@ +package opts + +import ( + "encoding/csv" + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/docker/docker/api/types/swarm" + "github.com/docker/go-connections/nat" +) + +const ( + portOptTargetPort = "target" + portOptPublishedPort = "published" + portOptProtocol = "protocol" + portOptMode = "mode" +) + +// PortOpt represents a port config in swarm mode. +type PortOpt struct { + ports []swarm.PortConfig +} + +// Set a new port value +func (p *PortOpt) Set(value string) error { + longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value) + if err != nil { + return err + } + if longSyntax { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err + } + + pConfig := swarm.PortConfig{} + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid field %s", field) + } + + key := strings.ToLower(parts[0]) + value := strings.ToLower(parts[1]) + + switch key { + case portOptProtocol: + if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) { + return fmt.Errorf("invalid protocol value %s", value) + } + + pConfig.Protocol = swarm.PortConfigProtocol(value) + case portOptMode: + if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) { + return fmt.Errorf("invalid publish mode value %s", value) + } + + pConfig.PublishMode = swarm.PortConfigPublishMode(value) + case portOptTargetPort: + tPort, err := strconv.ParseUint(value, 10, 16) + if err != nil { + return err + } + + pConfig.TargetPort = uint32(tPort) + case portOptPublishedPort: + pPort, err := strconv.ParseUint(value, 10, 16) + if err != nil { + return err + } + + pConfig.PublishedPort = uint32(pPort) + default: + return fmt.Errorf("invalid field key %s", key) + } + } + + if pConfig.TargetPort == 0 { + return fmt.Errorf("missing mandatory field %q", portOptTargetPort) + } + + if pConfig.PublishMode == "" { + pConfig.PublishMode = swarm.PortConfigPublishModeIngress + } + + if pConfig.Protocol == "" { + pConfig.Protocol = swarm.PortConfigProtocolTCP + } + + p.ports = append(p.ports, pConfig) + } else { + // short syntax + portConfigs := []swarm.PortConfig{} + ports, portBindingMap, err := nat.ParsePortSpecs([]string{value}) + if err != nil { + return err + } + for _, portBindings := range portBindingMap { + for _, portBinding := range portBindings { + if portBinding.HostIP != "" { + return fmt.Errorf("HostIP is not supported.") + } + } + } + + for port := range ports { + portConfig, err := ConvertPortToPortConfig(port, portBindingMap) + if err != nil { + return err + } + portConfigs = append(portConfigs, portConfig...) + } + p.ports = append(p.ports, portConfigs...) + } + return nil +} + +// Type returns the type of this option +func (p *PortOpt) Type() string { + return "port" +} + +// String returns a string repr of this option +func (p *PortOpt) String() string { + ports := []string{} + for _, port := range p.ports { + repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode) + ports = append(ports, repr) + } + return strings.Join(ports, ", ") +} + +// Value returns the ports +func (p *PortOpt) Value() []swarm.PortConfig { + return p.ports +} + +// ConvertPortToPortConfig converts ports to the swarm type +func ConvertPortToPortConfig( + port nat.Port, + portBindings map[nat.Port][]nat.PortBinding, +) ([]swarm.PortConfig, error) { + ports := []swarm.PortConfig{} + + for _, binding := range portBindings[port] { + hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16) + if err != nil && binding.HostPort != "" { + return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port()) + } + ports = append(ports, swarm.PortConfig{ + //TODO Name: ? + Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())), + TargetPort: uint32(port.Int()), + PublishedPort: uint32(hostPort), + PublishMode: swarm.PortConfigPublishModeIngress, + }) + } + return ports, nil +} diff --git a/vendor/github.com/docker/docker/opts/quotedstring.go b/vendor/github.com/docker/docker/opts/quotedstring.go new file mode 100644 index 00000000000..fb1e5374bc3 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/quotedstring.go @@ -0,0 +1,37 @@ +package opts + +// QuotedString is a string that may have extra quotes around the value. The +// quotes are stripped from the value. +type QuotedString struct { + value *string +} + +// Set sets a new value +func (s *QuotedString) Set(val string) error { + *s.value = trimQuotes(val) + return nil +} + +// Type returns the type of the value +func (s *QuotedString) Type() string { + return "string" +} + +func (s *QuotedString) String() string { + return string(*s.value) +} + +func trimQuotes(value string) string { + lastIndex := len(value) - 1 + for _, char := range []byte{'\'', '"'} { + if value[0] == char && value[lastIndex] == char { + return value[1:lastIndex] + } + } + return value +} + +// NewQuotedString returns a new quoted string option +func NewQuotedString(value *string) *QuotedString { + return &QuotedString{value: value} +} diff --git a/vendor/github.com/docker/docker/opts/runtime.go b/vendor/github.com/docker/docker/opts/runtime.go new file mode 100644 index 00000000000..4361b3ce094 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/runtime.go @@ -0,0 +1,79 @@ +package opts + +import ( + "fmt" + "strings" + + "github.com/docker/docker/api/types" +) + +// RuntimeOpt defines a map of Runtimes +type RuntimeOpt struct { + name string + stockRuntimeName string + values *map[string]types.Runtime +} + +// NewNamedRuntimeOpt creates a new RuntimeOpt +func NewNamedRuntimeOpt(name string, ref *map[string]types.Runtime, stockRuntime string) *RuntimeOpt { + if ref == nil { + ref = &map[string]types.Runtime{} + } + return &RuntimeOpt{name: name, values: ref, stockRuntimeName: stockRuntime} +} + +// Name returns the name of the NamedListOpts in the configuration. +func (o *RuntimeOpt) Name() string { + return o.name +} + +// Set validates and updates the list of Runtimes +func (o *RuntimeOpt) Set(val string) error { + parts := strings.SplitN(val, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid runtime argument: %s", val) + } + + parts[0] = strings.TrimSpace(parts[0]) + parts[1] = strings.TrimSpace(parts[1]) + if parts[0] == "" || parts[1] == "" { + return fmt.Errorf("invalid runtime argument: %s", val) + } + + parts[0] = strings.ToLower(parts[0]) + if parts[0] == o.stockRuntimeName { + return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName) + } + + if _, ok := (*o.values)[parts[0]]; ok { + return fmt.Errorf("runtime '%s' was already defined", parts[0]) + } + + (*o.values)[parts[0]] = types.Runtime{Path: parts[1]} + + return nil +} + +// String returns Runtime values as a string. +func (o *RuntimeOpt) String() string { + var out []string + for k := range *o.values { + out = append(out, k) + } + + return fmt.Sprintf("%v", out) +} + +// GetMap returns a map of Runtimes (name: path) +func (o *RuntimeOpt) GetMap() map[string]types.Runtime { + if o.values != nil { + return *o.values + } + + return map[string]types.Runtime{} +} + +// Type returns the type of the option +func (o *RuntimeOpt) Type() string { + return "runtime" +} diff --git a/vendor/github.com/docker/docker/opts/secret.go b/vendor/github.com/docker/docker/opts/secret.go new file mode 100644 index 00000000000..56ed29eb5b5 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/secret.go @@ -0,0 +1,103 @@ +package opts + +import ( + "encoding/csv" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + swarmtypes "github.com/docker/docker/api/types/swarm" +) + +// SecretOpt is a Value type for parsing secrets +type SecretOpt struct { + values []*swarmtypes.SecretReference +} + +// Set a new secret value +func (o *SecretOpt) Set(value string) error { + csvReader := csv.NewReader(strings.NewReader(value)) + fields, err := csvReader.Read() + if err != nil { + return err + } + + options := &swarmtypes.SecretReference{ + File: &swarmtypes.SecretReferenceFileTarget{ + UID: "0", + GID: "0", + Mode: 0444, + }, + } + + // support a simple syntax of --secret foo + if len(fields) == 1 { + options.File.Name = fields[0] + options.SecretName = fields[0] + o.values = append(o.values, options) + return nil + } + + for _, field := range fields { + parts := strings.SplitN(field, "=", 2) + key := strings.ToLower(parts[0]) + + if len(parts) != 2 { + return fmt.Errorf("invalid field '%s' must be a key=value pair", field) + } + + value := parts[1] + switch key { + case "source", "src": + options.SecretName = value + case "target": + tDir, _ := filepath.Split(value) + if tDir != "" { + return fmt.Errorf("target must not be a path") + } + options.File.Name = value + case "uid": + options.File.UID = value + case "gid": + options.File.GID = value + case "mode": + m, err := strconv.ParseUint(value, 0, 32) + if err != nil { + return fmt.Errorf("invalid mode specified: %v", err) + } + + options.File.Mode = os.FileMode(m) + default: + return fmt.Errorf("invalid field in secret request: %s", key) + } + } + + if options.SecretName == "" { + return fmt.Errorf("source is required") + } + + o.values = append(o.values, options) + return nil +} + +// Type returns the type of this option +func (o *SecretOpt) Type() string { + return "secret" +} + +// String returns a string repr of this option +func (o *SecretOpt) String() string { + secrets := []string{} + for _, secret := range o.values { + repr := fmt.Sprintf("%s -> %s", secret.SecretName, secret.File.Name) + secrets = append(secrets, repr) + } + return strings.Join(secrets, ", ") +} + +// Value returns the secret requests +func (o *SecretOpt) Value() []*swarmtypes.SecretReference { + return o.values +} diff --git a/vendor/github.com/docker/docker/opts/throttledevice.go b/vendor/github.com/docker/docker/opts/throttledevice.go new file mode 100644 index 00000000000..65dd3ebf68c --- /dev/null +++ b/vendor/github.com/docker/docker/opts/throttledevice.go @@ -0,0 +1,111 @@ +package opts + +import ( + "fmt" + "strconv" + "strings" + + "github.com/docker/docker/api/types/blkiodev" + "github.com/docker/go-units" +) + +// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error. +type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error) + +// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format. +func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := units.RAMInBytes(split[1]) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :[]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) + } + + return &blkiodev.ThrottleDevice{ + Path: split[0], + Rate: uint64(rate), + }, nil +} + +// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format. +func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + rate, err := strconv.ParseUint(split[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) + } + if rate < 0 { + return nil, fmt.Errorf("invalid rate for device: %s. The correct format is :. Number must be a positive integer", val) + } + + return &blkiodev.ThrottleDevice{ + Path: split[0], + Rate: uint64(rate), + }, nil +} + +// ThrottledeviceOpt defines a map of ThrottleDevices +type ThrottledeviceOpt struct { + values []*blkiodev.ThrottleDevice + validator ValidatorThrottleFctType +} + +// NewThrottledeviceOpt creates a new ThrottledeviceOpt +func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt { + values := []*blkiodev.ThrottleDevice{} + return ThrottledeviceOpt{ + values: values, + validator: validator, + } +} + +// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt +func (opt *ThrottledeviceOpt) Set(val string) error { + var value *blkiodev.ThrottleDevice + if opt.validator != nil { + v, err := opt.validator(val) + if err != nil { + return err + } + value = v + } + (opt.values) = append((opt.values), value) + return nil +} + +// String returns ThrottledeviceOpt values as a string. +func (opt *ThrottledeviceOpt) String() string { + var out []string + for _, v := range opt.values { + out = append(out, v.String()) + } + + return fmt.Sprintf("%v", out) +} + +// GetList returns a slice of pointers to ThrottleDevices. +func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice { + var throttledevice []*blkiodev.ThrottleDevice + throttledevice = append(throttledevice, opt.values...) + + return throttledevice +} + +// Type returns the option type +func (opt *ThrottledeviceOpt) Type() string { + return "list" +} diff --git a/vendor/github.com/docker/docker/opts/ulimit.go b/vendor/github.com/docker/docker/opts/ulimit.go new file mode 100644 index 00000000000..5adfe308519 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/ulimit.go @@ -0,0 +1,57 @@ +package opts + +import ( + "fmt" + + "github.com/docker/go-units" +) + +// UlimitOpt defines a map of Ulimits +type UlimitOpt struct { + values *map[string]*units.Ulimit +} + +// NewUlimitOpt creates a new UlimitOpt +func NewUlimitOpt(ref *map[string]*units.Ulimit) *UlimitOpt { + if ref == nil { + ref = &map[string]*units.Ulimit{} + } + return &UlimitOpt{ref} +} + +// Set validates a Ulimit and sets its name as a key in UlimitOpt +func (o *UlimitOpt) Set(val string) error { + l, err := units.ParseUlimit(val) + if err != nil { + return err + } + + (*o.values)[l.Name] = l + + return nil +} + +// String returns Ulimit values as a string. +func (o *UlimitOpt) String() string { + var out []string + for _, v := range *o.values { + out = append(out, v.String()) + } + + return fmt.Sprintf("%v", out) +} + +// GetList returns a slice of pointers to Ulimits. +func (o *UlimitOpt) GetList() []*units.Ulimit { + var ulimits []*units.Ulimit + for _, v := range *o.values { + ulimits = append(ulimits, v) + } + + return ulimits +} + +// Type returns the option type +func (o *UlimitOpt) Type() string { + return "ulimit" +} diff --git a/vendor/github.com/docker/docker/opts/weightdevice.go b/vendor/github.com/docker/docker/opts/weightdevice.go new file mode 100644 index 00000000000..7e3d064f272 --- /dev/null +++ b/vendor/github.com/docker/docker/opts/weightdevice.go @@ -0,0 +1,89 @@ +package opts + +import ( + "fmt" + "strconv" + "strings" + + "github.com/docker/docker/api/types/blkiodev" +) + +// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error. +type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error) + +// ValidateWeightDevice validates that the specified string has a valid device-weight format. +func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) { + split := strings.SplitN(val, ":", 2) + if len(split) != 2 { + return nil, fmt.Errorf("bad format: %s", val) + } + if !strings.HasPrefix(split[0], "/dev/") { + return nil, fmt.Errorf("bad format for device path: %s", val) + } + weight, err := strconv.ParseUint(split[1], 10, 0) + if err != nil { + return nil, fmt.Errorf("invalid weight for device: %s", val) + } + if weight > 0 && (weight < 10 || weight > 1000) { + return nil, fmt.Errorf("invalid weight for device: %s", val) + } + + return &blkiodev.WeightDevice{ + Path: split[0], + Weight: uint16(weight), + }, nil +} + +// WeightdeviceOpt defines a map of WeightDevices +type WeightdeviceOpt struct { + values []*blkiodev.WeightDevice + validator ValidatorWeightFctType +} + +// NewWeightdeviceOpt creates a new WeightdeviceOpt +func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt { + values := []*blkiodev.WeightDevice{} + return WeightdeviceOpt{ + values: values, + validator: validator, + } +} + +// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt +func (opt *WeightdeviceOpt) Set(val string) error { + var value *blkiodev.WeightDevice + if opt.validator != nil { + v, err := opt.validator(val) + if err != nil { + return err + } + value = v + } + (opt.values) = append((opt.values), value) + return nil +} + +// String returns WeightdeviceOpt values as a string. +func (opt *WeightdeviceOpt) String() string { + var out []string + for _, v := range opt.values { + out = append(out, v.String()) + } + + return fmt.Sprintf("%v", out) +} + +// GetList returns a slice of pointers to WeightDevices. +func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice { + var weightdevice []*blkiodev.WeightDevice + for _, v := range opt.values { + weightdevice = append(weightdevice, v) + } + + return weightdevice +} + +// Type returns the option type +func (opt *WeightdeviceOpt) Type() string { + return "list" +} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir.go b/vendor/github.com/docker/docker/pkg/homedir/homedir.go new file mode 100644 index 00000000000..8154e83f0c9 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir.go @@ -0,0 +1,39 @@ +package homedir + +import ( + "os" + "runtime" + + "github.com/opencontainers/runc/libcontainer/user" +) + +// Key returns the env var name for the user's home dir based on +// the platform being run on +func Key() string { + if runtime.GOOS == "windows" { + return "USERPROFILE" + } + return "HOME" +} + +// Get returns the home directory of the current user with the help of +// environment variables depending on the target operating system. +// Returned path should be used with "path/filepath" to form new paths. +func Get() string { + home := os.Getenv(Key()) + if home == "" && runtime.GOOS != "windows" { + if u, err := user.CurrentUser(); err == nil { + return u.Home + } + } + return home +} + +// GetShortcutString returns the string that is shortcut to user's home directory +// in the native shell of the platform running on. +func GetShortcutString() string { + if runtime.GOOS == "windows" { + return "%USERPROFILE%" // be careful while using in format functions + } + return "~" +} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go new file mode 100644 index 00000000000..012fe52a28f --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir_linux.go @@ -0,0 +1,23 @@ +// +build linux + +package homedir + +import ( + "os" + + "github.com/docker/docker/pkg/idtools" +) + +// GetStatic returns the home directory for the current user without calling +// os/user.Current(). This is useful for static-linked binary on glibc-based +// system, because a call to os/user.Current() in a static binary leads to +// segfault due to a glibc issue that won't be fixed in a short term. +// (#29344, golang/go#13470, https://sourceware.org/bugzilla/show_bug.cgi?id=19341) +func GetStatic() (string, error) { + uid := os.Getuid() + usr, err := idtools.LookupUID(uid) + if err != nil { + return "", err + } + return usr.Home, nil +} diff --git a/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go b/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go new file mode 100644 index 00000000000..6b96b856f67 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/homedir/homedir_others.go @@ -0,0 +1,13 @@ +// +build !linux + +package homedir + +import ( + "errors" +) + +// GetStatic is not needed for non-linux systems. +// (Precisely, it is needed only for glibc-based linux systems.) +func GetStatic() (string, error) { + return "", errors.New("homedir.GetStatic() is not supported on this system") +} diff --git a/vendor/github.com/docker/docker/pkg/jsonlog/jsonlog.go b/vendor/github.com/docker/docker/pkg/jsonlog/jsonlog.go new file mode 100644 index 00000000000..4734c311196 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/jsonlog/jsonlog.go @@ -0,0 +1,42 @@ +package jsonlog + +import ( + "encoding/json" + "fmt" + "time" +) + +// JSONLog represents a log message, typically a single entry from a given log stream. +// JSONLogs can be easily serialized to and from JSON and support custom formatting. +type JSONLog struct { + // Log is the log message + Log string `json:"log,omitempty"` + // Stream is the log source + Stream string `json:"stream,omitempty"` + // Created is the created timestamp of log + Created time.Time `json:"time"` + // Attrs is the list of extra attributes provided by the user + Attrs map[string]string `json:"attrs,omitempty"` +} + +// Format returns the log formatted according to format +// If format is nil, returns the log message +// If format is json, returns the log marshaled in json format +// By default, returns the log with the log time formatted according to format. +func (jl *JSONLog) Format(format string) (string, error) { + if format == "" { + return jl.Log, nil + } + if format == "json" { + m, err := json.Marshal(jl) + return string(m), err + } + return fmt.Sprintf("%s %s", jl.Created.Format(format), jl.Log), nil +} + +// Reset resets the log to nil. +func (jl *JSONLog) Reset() { + jl.Log = "" + jl.Stream = "" + jl.Created = time.Time{} +} diff --git a/vendor/github.com/docker/docker/pkg/jsonlog/jsonlog_marshalling.go b/vendor/github.com/docker/docker/pkg/jsonlog/jsonlog_marshalling.go new file mode 100644 index 00000000000..83ce684a8ef --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/jsonlog/jsonlog_marshalling.go @@ -0,0 +1,178 @@ +// This code was initially generated by ffjson +// This code was generated via the following steps: +// $ go get -u github.com/pquerna/ffjson +// $ make BIND_DIR=. shell +// $ ffjson pkg/jsonlog/jsonlog.go +// $ mv pkg/jsonglog/jsonlog_ffjson.go pkg/jsonlog/jsonlog_marshalling.go +// +// It has been modified to improve the performance of time marshalling to JSON +// and to clean it up. +// Should this code need to be regenerated when the JSONLog struct is changed, +// the relevant changes which have been made are: +// import ( +// "bytes" +//- +// "unicode/utf8" +// ) +// +// func (mj *JSONLog) MarshalJSON() ([]byte, error) { +//@@ -20,13 +16,13 @@ func (mj *JSONLog) MarshalJSON() ([]byte, error) { +// } +// return buf.Bytes(), nil +// } +//+ +// func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error { +//- var err error +//- var obj []byte +//- var first bool = true +//- _ = obj +//- _ = err +//- _ = first +//+ var ( +//+ err error +//+ timestamp string +//+ first bool = true +//+ ) +// buf.WriteString(`{`) +// if len(mj.Log) != 0 { +// if first == true { +//@@ -52,11 +48,11 @@ func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error { +// buf.WriteString(`,`) +// } +// buf.WriteString(`"time":`) +//- obj, err = mj.Created.MarshalJSON() +//+ timestamp, err = FastTimeMarshalJSON(mj.Created) +// if err != nil { +// return err +// } +//- buf.Write(obj) +//+ buf.WriteString(timestamp) +// buf.WriteString(`}`) +// return nil +// } +// @@ -81,9 +81,10 @@ func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error { +// if len(mj.Log) != 0 { +// - if first == true { +// - first = false +// - } else { +// - buf.WriteString(`,`) +// - } +// + first = false +// buf.WriteString(`"log":`) +// ffjsonWriteJSONString(buf, mj.Log) +// } + +package jsonlog + +import ( + "bytes" + "unicode/utf8" +) + +// MarshalJSON marshals the JSONLog. +func (mj *JSONLog) MarshalJSON() ([]byte, error) { + var buf bytes.Buffer + buf.Grow(1024) + if err := mj.MarshalJSONBuf(&buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// MarshalJSONBuf marshals the JSONLog and stores the result to a bytes.Buffer. +func (mj *JSONLog) MarshalJSONBuf(buf *bytes.Buffer) error { + var ( + err error + timestamp string + first = true + ) + buf.WriteString(`{`) + if len(mj.Log) != 0 { + first = false + buf.WriteString(`"log":`) + ffjsonWriteJSONString(buf, mj.Log) + } + if len(mj.Stream) != 0 { + if first { + first = false + } else { + buf.WriteString(`,`) + } + buf.WriteString(`"stream":`) + ffjsonWriteJSONString(buf, mj.Stream) + } + if !first { + buf.WriteString(`,`) + } + buf.WriteString(`"time":`) + timestamp, err = FastTimeMarshalJSON(mj.Created) + if err != nil { + return err + } + buf.WriteString(timestamp) + buf.WriteString(`}`) + return nil +} + +func ffjsonWriteJSONString(buf *bytes.Buffer, s string) { + const hex = "0123456789abcdef" + + buf.WriteByte('"') + start := 0 + for i := 0; i < len(s); { + if b := s[i]; b < utf8.RuneSelf { + if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' { + i++ + continue + } + if start < i { + buf.WriteString(s[start:i]) + } + switch b { + case '\\', '"': + buf.WriteByte('\\') + buf.WriteByte(b) + case '\n': + buf.WriteByte('\\') + buf.WriteByte('n') + case '\r': + buf.WriteByte('\\') + buf.WriteByte('r') + default: + + buf.WriteString(`\u00`) + buf.WriteByte(hex[b>>4]) + buf.WriteByte(hex[b&0xF]) + } + i++ + start = i + continue + } + c, size := utf8.DecodeRuneInString(s[i:]) + if c == utf8.RuneError && size == 1 { + if start < i { + buf.WriteString(s[start:i]) + } + buf.WriteString(`\ufffd`) + i += size + start = i + continue + } + + if c == '\u2028' || c == '\u2029' { + if start < i { + buf.WriteString(s[start:i]) + } + buf.WriteString(`\u202`) + buf.WriteByte(hex[c&0xF]) + i += size + start = i + continue + } + i += size + } + if start < len(s) { + buf.WriteString(s[start:]) + } + buf.WriteByte('"') +} diff --git a/vendor/github.com/docker/docker/pkg/jsonlog/jsonlogbytes.go b/vendor/github.com/docker/docker/pkg/jsonlog/jsonlogbytes.go new file mode 100644 index 00000000000..df522c0d66f --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/jsonlog/jsonlogbytes.go @@ -0,0 +1,122 @@ +package jsonlog + +import ( + "bytes" + "encoding/json" + "unicode/utf8" +) + +// JSONLogs is based on JSONLog. +// It allows marshalling JSONLog from Log as []byte +// and an already marshalled Created timestamp. +type JSONLogs struct { + Log []byte `json:"log,omitempty"` + Stream string `json:"stream,omitempty"` + Created string `json:"time"` + + // json-encoded bytes + RawAttrs json.RawMessage `json:"attrs,omitempty"` +} + +// MarshalJSONBuf is based on the same method from JSONLog +// It has been modified to take into account the necessary changes. +func (mj *JSONLogs) MarshalJSONBuf(buf *bytes.Buffer) error { + var first = true + + buf.WriteString(`{`) + if len(mj.Log) != 0 { + first = false + buf.WriteString(`"log":`) + ffjsonWriteJSONBytesAsString(buf, mj.Log) + } + if len(mj.Stream) != 0 { + if first == true { + first = false + } else { + buf.WriteString(`,`) + } + buf.WriteString(`"stream":`) + ffjsonWriteJSONString(buf, mj.Stream) + } + if len(mj.RawAttrs) > 0 { + if first { + first = false + } else { + buf.WriteString(`,`) + } + buf.WriteString(`"attrs":`) + buf.Write(mj.RawAttrs) + } + if !first { + buf.WriteString(`,`) + } + buf.WriteString(`"time":`) + buf.WriteString(mj.Created) + buf.WriteString(`}`) + return nil +} + +// This is based on ffjsonWriteJSONBytesAsString. It has been changed +// to accept a string passed as a slice of bytes. +func ffjsonWriteJSONBytesAsString(buf *bytes.Buffer, s []byte) { + const hex = "0123456789abcdef" + + buf.WriteByte('"') + start := 0 + for i := 0; i < len(s); { + if b := s[i]; b < utf8.RuneSelf { + if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' { + i++ + continue + } + if start < i { + buf.Write(s[start:i]) + } + switch b { + case '\\', '"': + buf.WriteByte('\\') + buf.WriteByte(b) + case '\n': + buf.WriteByte('\\') + buf.WriteByte('n') + case '\r': + buf.WriteByte('\\') + buf.WriteByte('r') + default: + + buf.WriteString(`\u00`) + buf.WriteByte(hex[b>>4]) + buf.WriteByte(hex[b&0xF]) + } + i++ + start = i + continue + } + c, size := utf8.DecodeRune(s[i:]) + if c == utf8.RuneError && size == 1 { + if start < i { + buf.Write(s[start:i]) + } + buf.WriteString(`\ufffd`) + i += size + start = i + continue + } + + if c == '\u2028' || c == '\u2029' { + if start < i { + buf.Write(s[start:i]) + } + buf.WriteString(`\u202`) + buf.WriteByte(hex[c&0xF]) + i += size + start = i + continue + } + i += size + } + if start < len(s) { + buf.Write(s[start:]) + } + buf.WriteByte('"') +} diff --git a/vendor/github.com/docker/docker/pkg/jsonlog/time_marshalling.go b/vendor/github.com/docker/docker/pkg/jsonlog/time_marshalling.go new file mode 100644 index 00000000000..21173381495 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/jsonlog/time_marshalling.go @@ -0,0 +1,27 @@ +// Package jsonlog provides helper functions to parse and print time (time.Time) as JSON. +package jsonlog + +import ( + "errors" + "time" +) + +const ( + // RFC3339NanoFixed is our own version of RFC339Nano because we want one + // that pads the nano seconds part with zeros to ensure + // the timestamps are aligned in the logs. + RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" + // JSONFormat is the format used by FastMarshalJSON + JSONFormat = `"` + time.RFC3339Nano + `"` +) + +// FastTimeMarshalJSON avoids one of the extra allocations that +// time.MarshalJSON is making. +func FastTimeMarshalJSON(t time.Time) (string, error) { + if y := t.Year(); y < 0 || y >= 10000 { + // RFC 3339 is clear that years are 4 digits exactly. + // See golang.org/issue/4556#c15 for more discussion. + return "", errors.New("time.MarshalJSON: year outside of range [0,9999]") + } + return t.Format(JSONFormat), nil +} diff --git a/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go b/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go new file mode 100644 index 00000000000..c6ad345ce4d --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/jsonmessage/jsonmessage.go @@ -0,0 +1,293 @@ +package jsonmessage + +import ( + "encoding/json" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/Nvveen/Gotty" + + "github.com/docker/docker/pkg/jsonlog" + "github.com/docker/docker/pkg/term" + "github.com/docker/go-units" +) + +// JSONError wraps a concrete Code and Message, `Code` is +// is an integer error code, `Message` is the error message. +type JSONError struct { + Code int `json:"code,omitempty"` + Message string `json:"message,omitempty"` +} + +func (e *JSONError) Error() string { + return e.Message +} + +// JSONProgress describes a Progress. terminalFd is the fd of the current terminal, +// Start is the initial value for the operation. Current is the current status and +// value of the progress made towards Total. Total is the end value describing when +// we made 100% progress for an operation. +type JSONProgress struct { + terminalFd uintptr + Current int64 `json:"current,omitempty"` + Total int64 `json:"total,omitempty"` + Start int64 `json:"start,omitempty"` +} + +func (p *JSONProgress) String() string { + var ( + width = 200 + pbBox string + numbersBox string + timeLeftBox string + ) + + ws, err := term.GetWinsize(p.terminalFd) + if err == nil { + width = int(ws.Width) + } + + if p.Current <= 0 && p.Total <= 0 { + return "" + } + current := units.HumanSize(float64(p.Current)) + if p.Total <= 0 { + return fmt.Sprintf("%8v", current) + } + total := units.HumanSize(float64(p.Total)) + percentage := int(float64(p.Current)/float64(p.Total)*100) / 2 + if percentage > 50 { + percentage = 50 + } + if width > 110 { + // this number can't be negative gh#7136 + numSpaces := 0 + if 50-percentage > 0 { + numSpaces = 50 - percentage + } + pbBox = fmt.Sprintf("[%s>%s] ", strings.Repeat("=", percentage), strings.Repeat(" ", numSpaces)) + } + + numbersBox = fmt.Sprintf("%8v/%v", current, total) + + if p.Current > p.Total { + // remove total display if the reported current is wonky. + numbersBox = fmt.Sprintf("%8v", current) + } + + if p.Current > 0 && p.Start > 0 && percentage < 50 { + fromStart := time.Now().UTC().Sub(time.Unix(p.Start, 0)) + perEntry := fromStart / time.Duration(p.Current) + left := time.Duration(p.Total-p.Current) * perEntry + left = (left / time.Second) * time.Second + + if width > 50 { + timeLeftBox = " " + left.String() + } + } + return pbBox + numbersBox + timeLeftBox +} + +// JSONMessage defines a message struct. It describes +// the created time, where it from, status, ID of the +// message. It's used for docker events. +type JSONMessage struct { + Stream string `json:"stream,omitempty"` + Status string `json:"status,omitempty"` + Progress *JSONProgress `json:"progressDetail,omitempty"` + ProgressMessage string `json:"progress,omitempty"` //deprecated + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + Time int64 `json:"time,omitempty"` + TimeNano int64 `json:"timeNano,omitempty"` + Error *JSONError `json:"errorDetail,omitempty"` + ErrorMessage string `json:"error,omitempty"` //deprecated + // Aux contains out-of-band data, such as digests for push signing. + Aux *json.RawMessage `json:"aux,omitempty"` +} + +/* Satisfied by gotty.TermInfo as well as noTermInfo from below */ +type termInfo interface { + Parse(attr string, params ...interface{}) (string, error) +} + +type noTermInfo struct{} // canary used when no terminfo. + +func (ti *noTermInfo) Parse(attr string, params ...interface{}) (string, error) { + return "", fmt.Errorf("noTermInfo") +} + +func clearLine(out io.Writer, ti termInfo) { + // el2 (clear whole line) is not exposed by terminfo. + + // First clear line from beginning to cursor + if attr, err := ti.Parse("el1"); err == nil { + fmt.Fprintf(out, "%s", attr) + } else { + fmt.Fprintf(out, "\x1b[1K") + } + // Then clear line from cursor to end + if attr, err := ti.Parse("el"); err == nil { + fmt.Fprintf(out, "%s", attr) + } else { + fmt.Fprintf(out, "\x1b[K") + } +} + +func cursorUp(out io.Writer, ti termInfo, l int) { + if l == 0 { // Should never be the case, but be tolerant + return + } + if attr, err := ti.Parse("cuu", l); err == nil { + fmt.Fprintf(out, "%s", attr) + } else { + fmt.Fprintf(out, "\x1b[%dA", l) + } +} + +func cursorDown(out io.Writer, ti termInfo, l int) { + if l == 0 { // Should never be the case, but be tolerant + return + } + if attr, err := ti.Parse("cud", l); err == nil { + fmt.Fprintf(out, "%s", attr) + } else { + fmt.Fprintf(out, "\x1b[%dB", l) + } +} + +// Display displays the JSONMessage to `out`. `termInfo` is non-nil if `out` +// is a terminal. If this is the case, it will erase the entire current line +// when displaying the progressbar. +func (jm *JSONMessage) Display(out io.Writer, termInfo termInfo) error { + if jm.Error != nil { + if jm.Error.Code == 401 { + return fmt.Errorf("Authentication is required.") + } + return jm.Error + } + var endl string + if termInfo != nil && jm.Stream == "" && jm.Progress != nil { + clearLine(out, termInfo) + endl = "\r" + fmt.Fprintf(out, endl) + } else if jm.Progress != nil && jm.Progress.String() != "" { //disable progressbar in non-terminal + return nil + } + if jm.TimeNano != 0 { + fmt.Fprintf(out, "%s ", time.Unix(0, jm.TimeNano).Format(jsonlog.RFC3339NanoFixed)) + } else if jm.Time != 0 { + fmt.Fprintf(out, "%s ", time.Unix(jm.Time, 0).Format(jsonlog.RFC3339NanoFixed)) + } + if jm.ID != "" { + fmt.Fprintf(out, "%s: ", jm.ID) + } + if jm.From != "" { + fmt.Fprintf(out, "(from %s) ", jm.From) + } + if jm.Progress != nil && termInfo != nil { + fmt.Fprintf(out, "%s %s%s", jm.Status, jm.Progress.String(), endl) + } else if jm.ProgressMessage != "" { //deprecated + fmt.Fprintf(out, "%s %s%s", jm.Status, jm.ProgressMessage, endl) + } else if jm.Stream != "" { + fmt.Fprintf(out, "%s%s", jm.Stream, endl) + } else { + fmt.Fprintf(out, "%s%s\n", jm.Status, endl) + } + return nil +} + +// DisplayJSONMessagesStream displays a json message stream from `in` to `out`, `isTerminal` +// describes if `out` is a terminal. If this is the case, it will print `\n` at the end of +// each line and move the cursor while displaying. +func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(*json.RawMessage)) error { + var ( + dec = json.NewDecoder(in) + ids = make(map[string]int) + ) + + var termInfo termInfo + + if isTerminal { + term := os.Getenv("TERM") + if term == "" { + term = "vt102" + } + + var err error + if termInfo, err = gotty.OpenTermInfo(term); err != nil { + termInfo = &noTermInfo{} + } + } + + for { + diff := 0 + var jm JSONMessage + if err := dec.Decode(&jm); err != nil { + if err == io.EOF { + break + } + return err + } + + if jm.Aux != nil { + if auxCallback != nil { + auxCallback(jm.Aux) + } + continue + } + + if jm.Progress != nil { + jm.Progress.terminalFd = terminalFd + } + if jm.ID != "" && (jm.Progress != nil || jm.ProgressMessage != "") { + line, ok := ids[jm.ID] + if !ok { + // NOTE: This approach of using len(id) to + // figure out the number of lines of history + // only works as long as we clear the history + // when we output something that's not + // accounted for in the map, such as a line + // with no ID. + line = len(ids) + ids[jm.ID] = line + if termInfo != nil { + fmt.Fprintf(out, "\n") + } + } + diff = len(ids) - line + if termInfo != nil { + cursorUp(out, termInfo, diff) + } + } else { + // When outputting something that isn't progress + // output, clear the history of previous lines. We + // don't want progress entries from some previous + // operation to be updated (for example, pull -a + // with multiple tags). + ids = make(map[string]int) + } + err := jm.Display(out, termInfo) + if jm.ID != "" && termInfo != nil { + cursorDown(out, termInfo, diff) + } + if err != nil { + return err + } + } + return nil +} + +type stream interface { + io.Writer + FD() uintptr + IsTerminal() bool +} + +// DisplayJSONMessagesToStream prints json messages to the output stream +func DisplayJSONMessagesToStream(in io.Reader, stream stream, auxCallback func(*json.RawMessage)) error { + return DisplayJSONMessagesStream(in, stream, stream.FD(), stream.IsTerminal(), auxCallback) +} diff --git a/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go b/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go new file mode 100644 index 00000000000..a018a203f38 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/stdcopy/stdcopy.go @@ -0,0 +1,190 @@ +package stdcopy + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + "io" + "sync" +) + +// StdType is the type of standard stream +// a writer can multiplex to. +type StdType byte + +const ( + // Stdin represents standard input stream type. + Stdin StdType = iota + // Stdout represents standard output stream type. + Stdout + // Stderr represents standard error steam type. + Stderr + // Systemerr represents errors originating from the system that make it + // into the the multiplexed stream. + Systemerr + + stdWriterPrefixLen = 8 + stdWriterFdIndex = 0 + stdWriterSizeIndex = 4 + + startingBufLen = 32*1024 + stdWriterPrefixLen + 1 +) + +var bufPool = &sync.Pool{New: func() interface{} { return bytes.NewBuffer(nil) }} + +// stdWriter is wrapper of io.Writer with extra customized info. +type stdWriter struct { + io.Writer + prefix byte +} + +// Write sends the buffer to the underneath writer. +// It inserts the prefix header before the buffer, +// so stdcopy.StdCopy knows where to multiplex the output. +// It makes stdWriter to implement io.Writer. +func (w *stdWriter) Write(p []byte) (n int, err error) { + if w == nil || w.Writer == nil { + return 0, errors.New("Writer not instantiated") + } + if p == nil { + return 0, nil + } + + header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix} + binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(p))) + buf := bufPool.Get().(*bytes.Buffer) + buf.Write(header[:]) + buf.Write(p) + + n, err = w.Writer.Write(buf.Bytes()) + n -= stdWriterPrefixLen + if n < 0 { + n = 0 + } + + buf.Reset() + bufPool.Put(buf) + return +} + +// NewStdWriter instantiates a new Writer. +// Everything written to it will be encapsulated using a custom format, +// and written to the underlying `w` stream. +// This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection. +// `t` indicates the id of the stream to encapsulate. +// It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr. +func NewStdWriter(w io.Writer, t StdType) io.Writer { + return &stdWriter{ + Writer: w, + prefix: byte(t), + } +} + +// StdCopy is a modified version of io.Copy. +// +// StdCopy will demultiplex `src`, assuming that it contains two streams, +// previously multiplexed together using a StdWriter instance. +// As it reads from `src`, StdCopy will write to `dstout` and `dsterr`. +// +// StdCopy will read until it hits EOF on `src`. It will then return a nil error. +// In other words: if `err` is non nil, it indicates a real underlying error. +// +// `written` will hold the total number of bytes written to `dstout` and `dsterr`. +func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) { + var ( + buf = make([]byte, startingBufLen) + bufLen = len(buf) + nr, nw int + er, ew error + out io.Writer + frameSize int + ) + + for { + // Make sure we have at least a full header + for nr < stdWriterPrefixLen { + var nr2 int + nr2, er = src.Read(buf[nr:]) + nr += nr2 + if er == io.EOF { + if nr < stdWriterPrefixLen { + return written, nil + } + break + } + if er != nil { + return 0, er + } + } + + stream := StdType(buf[stdWriterFdIndex]) + // Check the first byte to know where to write + switch stream { + case Stdin: + fallthrough + case Stdout: + // Write on stdout + out = dstout + case Stderr: + // Write on stderr + out = dsterr + case Systemerr: + // If we're on Systemerr, we won't write anywhere. + // NB: if this code changes later, make sure you don't try to write + // to outstream if Systemerr is the stream + out = nil + default: + return 0, fmt.Errorf("Unrecognized input header: %d", buf[stdWriterFdIndex]) + } + + // Retrieve the size of the frame + frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4])) + + // Check if the buffer is big enough to read the frame. + // Extend it if necessary. + if frameSize+stdWriterPrefixLen > bufLen { + buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...) + bufLen = len(buf) + } + + // While the amount of bytes read is less than the size of the frame + header, we keep reading + for nr < frameSize+stdWriterPrefixLen { + var nr2 int + nr2, er = src.Read(buf[nr:]) + nr += nr2 + if er == io.EOF { + if nr < frameSize+stdWriterPrefixLen { + return written, nil + } + break + } + if er != nil { + return 0, er + } + } + + // we might have an error from the source mixed up in our multiplexed + // stream. if we do, return it. + if stream == Systemerr { + return written, fmt.Errorf("error from daemon in stream: %s", string(buf[stdWriterPrefixLen:frameSize+stdWriterPrefixLen])) + } + + // Write the retrieved frame (without header) + nw, ew = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen]) + if ew != nil { + return 0, ew + } + + // If the frame has not been fully written: error + if nw != frameSize { + return 0, io.ErrShortWrite + } + written += int64(nw) + + // Move the rest of the buffer to the beginning + copy(buf, buf[frameSize+stdWriterPrefixLen:]) + // Move the index + nr -= frameSize + stdWriterPrefixLen + } +} diff --git a/vendor/github.com/docker/docker/pkg/term/ascii.go b/vendor/github.com/docker/docker/pkg/term/ascii.go new file mode 100644 index 00000000000..f5262bccf5f --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/ascii.go @@ -0,0 +1,66 @@ +package term + +import ( + "fmt" + "strings" +) + +// ASCII list the possible supported ASCII key sequence +var ASCII = []string{ + "ctrl-@", + "ctrl-a", + "ctrl-b", + "ctrl-c", + "ctrl-d", + "ctrl-e", + "ctrl-f", + "ctrl-g", + "ctrl-h", + "ctrl-i", + "ctrl-j", + "ctrl-k", + "ctrl-l", + "ctrl-m", + "ctrl-n", + "ctrl-o", + "ctrl-p", + "ctrl-q", + "ctrl-r", + "ctrl-s", + "ctrl-t", + "ctrl-u", + "ctrl-v", + "ctrl-w", + "ctrl-x", + "ctrl-y", + "ctrl-z", + "ctrl-[", + "ctrl-\\", + "ctrl-]", + "ctrl-^", + "ctrl-_", +} + +// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code. +func ToBytes(keys string) ([]byte, error) { + codes := []byte{} +next: + for _, key := range strings.Split(keys, ",") { + if len(key) != 1 { + for code, ctrl := range ASCII { + if ctrl == key { + codes = append(codes, byte(code)) + continue next + } + } + if key == "DEL" { + codes = append(codes, 127) + } else { + return nil, fmt.Errorf("Unknown character: '%s'", key) + } + } else { + codes = append(codes, byte(key[0])) + } + } + return codes, nil +} diff --git a/vendor/github.com/docker/docker/pkg/term/tc_linux_cgo.go b/vendor/github.com/docker/docker/pkg/term/tc_linux_cgo.go new file mode 100644 index 00000000000..59dac5ba8e4 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/tc_linux_cgo.go @@ -0,0 +1,50 @@ +// +build linux,cgo + +package term + +import ( + "syscall" + "unsafe" +) + +// #include +import "C" + +// Termios is the Unix API for terminal I/O. +// It is passthrough for syscall.Termios in order to make it portable with +// other platforms where it is not available or handled differently. +type Termios syscall.Termios + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd uintptr) (*State, error) { + var oldState State + if err := tcget(fd, &oldState.termios); err != 0 { + return nil, err + } + + newState := oldState.termios + + C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&newState))) + if err := tcset(fd, &newState); err != 0 { + return nil, err + } + return &oldState, nil +} + +func tcget(fd uintptr, p *Termios) syscall.Errno { + ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p))) + if ret != 0 { + return err.(syscall.Errno) + } + return 0 +} + +func tcset(fd uintptr, p *Termios) syscall.Errno { + ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p))) + if ret != 0 { + return err.(syscall.Errno) + } + return 0 +} diff --git a/vendor/github.com/docker/docker/pkg/term/tc_other.go b/vendor/github.com/docker/docker/pkg/term/tc_other.go new file mode 100644 index 00000000000..750d7c3f607 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/tc_other.go @@ -0,0 +1,20 @@ +// +build !windows +// +build !linux !cgo +// +build !solaris !cgo + +package term + +import ( + "syscall" + "unsafe" +) + +func tcget(fd uintptr, p *Termios) syscall.Errno { + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(p))) + return err +} + +func tcset(fd uintptr, p *Termios) syscall.Errno { + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(p))) + return err +} diff --git a/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go b/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go new file mode 100644 index 00000000000..c9139d0ca80 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/tc_solaris_cgo.go @@ -0,0 +1,63 @@ +// +build solaris,cgo + +package term + +import ( + "syscall" + "unsafe" +) + +// #include +import "C" + +// Termios is the Unix API for terminal I/O. +// It is passthrough for syscall.Termios in order to make it portable with +// other platforms where it is not available or handled differently. +type Termios syscall.Termios + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd uintptr) (*State, error) { + var oldState State + if err := tcget(fd, &oldState.termios); err != 0 { + return nil, err + } + + newState := oldState.termios + + newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON | syscall.IXANY) + newState.Oflag &^= syscall.OPOST + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) + newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) + newState.Cflag |= syscall.CS8 + + /* + VMIN is the minimum number of characters that needs to be read in non-canonical mode for it to be returned + Since VMIN is overloaded with another element in canonical mode when we switch modes it defaults to 4. It + needs to be explicitly set to 1. + */ + newState.Cc[C.VMIN] = 1 + newState.Cc[C.VTIME] = 0 + + if err := tcset(fd, &newState); err != 0 { + return nil, err + } + return &oldState, nil +} + +func tcget(fd uintptr, p *Termios) syscall.Errno { + ret, err := C.tcgetattr(C.int(fd), (*C.struct_termios)(unsafe.Pointer(p))) + if ret != 0 { + return err.(syscall.Errno) + } + return 0 +} + +func tcset(fd uintptr, p *Termios) syscall.Errno { + ret, err := C.tcsetattr(C.int(fd), C.TCSANOW, (*C.struct_termios)(unsafe.Pointer(p))) + if ret != 0 { + return err.(syscall.Errno) + } + return 0 +} diff --git a/vendor/github.com/docker/docker/pkg/term/term.go b/vendor/github.com/docker/docker/pkg/term/term.go new file mode 100644 index 00000000000..816f8d75240 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/term.go @@ -0,0 +1,123 @@ +// +build !windows + +// Package term provides structures and helper functions to work with +// terminal (state, sizes). +package term + +import ( + "errors" + "fmt" + "io" + "os" + "os/signal" + "syscall" +) + +var ( + // ErrInvalidState is returned if the state of the terminal is invalid. + ErrInvalidState = errors.New("Invalid terminal state") +) + +// State represents the state of the terminal. +type State struct { + termios Termios +} + +// Winsize represents the size of the terminal window. +type Winsize struct { + Height uint16 + Width uint16 + x uint16 + y uint16 +} + +// StdStreams returns the standard streams (stdin, stdout, stderr). +func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { + return os.Stdin, os.Stdout, os.Stderr +} + +// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. +func GetFdInfo(in interface{}) (uintptr, bool) { + var inFd uintptr + var isTerminalIn bool + if file, ok := in.(*os.File); ok { + inFd = file.Fd() + isTerminalIn = IsTerminal(inFd) + } + return inFd, isTerminalIn +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd uintptr) bool { + var termios Termios + return tcget(fd, &termios) == 0 +} + +// RestoreTerminal restores the terminal connected to the given file descriptor +// to a previous state. +func RestoreTerminal(fd uintptr, state *State) error { + if state == nil { + return ErrInvalidState + } + if err := tcset(fd, &state.termios); err != 0 { + return err + } + return nil +} + +// SaveState saves the state of the terminal connected to the given file descriptor. +func SaveState(fd uintptr) (*State, error) { + var oldState State + if err := tcget(fd, &oldState.termios); err != 0 { + return nil, err + } + + return &oldState, nil +} + +// DisableEcho applies the specified state to the terminal connected to the file +// descriptor, with echo disabled. +func DisableEcho(fd uintptr, state *State) error { + newState := state.termios + newState.Lflag &^= syscall.ECHO + + if err := tcset(fd, &newState); err != 0 { + return err + } + handleInterrupt(fd, state) + return nil +} + +// SetRawTerminal puts the terminal connected to the given file descriptor into +// raw mode and returns the previous state. On UNIX, this puts both the input +// and output into raw mode. On Windows, it only puts the input into raw mode. +func SetRawTerminal(fd uintptr) (*State, error) { + oldState, err := MakeRaw(fd) + if err != nil { + return nil, err + } + handleInterrupt(fd, oldState) + return oldState, err +} + +// SetRawTerminalOutput puts the output of terminal connected to the given file +// descriptor into raw mode. On UNIX, this does nothing and returns nil for the +// state. On Windows, it disables LF -> CRLF translation. +func SetRawTerminalOutput(fd uintptr) (*State, error) { + return nil, nil +} + +func handleInterrupt(fd uintptr, state *State) { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, os.Interrupt) + go func() { + for range sigchan { + // quit cleanly and the new terminal item is on a new line + fmt.Println() + signal.Stop(sigchan) + close(sigchan) + RestoreTerminal(fd, state) + os.Exit(1) + } + }() +} diff --git a/vendor/github.com/docker/docker/pkg/term/term_solaris.go b/vendor/github.com/docker/docker/pkg/term/term_solaris.go new file mode 100644 index 00000000000..112debbec56 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/term_solaris.go @@ -0,0 +1,41 @@ +// +build solaris + +package term + +import ( + "syscall" + "unsafe" +) + +/* +#include +#include +#include + +// Small wrapper to get rid of variadic args of ioctl() +int my_ioctl(int fd, int cmd, struct winsize *ws) { + return ioctl(fd, cmd, ws); +} +*/ +import "C" + +// GetWinsize returns the window size based on the specified file descriptor. +func GetWinsize(fd uintptr) (*Winsize, error) { + ws := &Winsize{} + ret, err := C.my_ioctl(C.int(fd), C.int(syscall.TIOCGWINSZ), (*C.struct_winsize)(unsafe.Pointer(ws))) + // Skip retval = 0 + if ret == 0 { + return ws, nil + } + return ws, err +} + +// SetWinsize tries to set the specified window size for the specified file descriptor. +func SetWinsize(fd uintptr, ws *Winsize) error { + ret, err := C.my_ioctl(C.int(fd), C.int(syscall.TIOCSWINSZ), (*C.struct_winsize)(unsafe.Pointer(ws))) + // Skip retval = 0 + if ret == 0 { + return nil + } + return err +} diff --git a/vendor/github.com/docker/docker/pkg/term/term_unix.go b/vendor/github.com/docker/docker/pkg/term/term_unix.go new file mode 100644 index 00000000000..ddf87a0e58a --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/term_unix.go @@ -0,0 +1,29 @@ +// +build !solaris,!windows + +package term + +import ( + "syscall" + "unsafe" +) + +// GetWinsize returns the window size based on the specified file descriptor. +func GetWinsize(fd uintptr) (*Winsize, error) { + ws := &Winsize{} + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(ws))) + // Skipp errno = 0 + if err == 0 { + return ws, nil + } + return ws, err +} + +// SetWinsize tries to set the specified window size for the specified file descriptor. +func SetWinsize(fd uintptr, ws *Winsize) error { + _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(syscall.TIOCSWINSZ), uintptr(unsafe.Pointer(ws))) + // Skipp errno = 0 + if err == 0 { + return nil + } + return err +} diff --git a/vendor/github.com/docker/docker/pkg/term/term_windows.go b/vendor/github.com/docker/docker/pkg/term/term_windows.go new file mode 100644 index 00000000000..62e9a9e6923 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/term_windows.go @@ -0,0 +1,233 @@ +// +build windows + +package term + +import ( + "io" + "os" + "os/signal" + "syscall" + + "github.com/Azure/go-ansiterm/winterm" + "github.com/docker/docker/pkg/term/windows" +) + +// State holds the console mode for the terminal. +type State struct { + mode uint32 +} + +// Winsize is used for window size. +type Winsize struct { + Height uint16 + Width uint16 +} + +const ( + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx + enableVirtualTerminalInput = 0x0200 + enableVirtualTerminalProcessing = 0x0004 + disableNewlineAutoReturn = 0x0008 +) + +// vtInputSupported is true if enableVirtualTerminalInput is supported by the console +var vtInputSupported bool + +// StdStreams returns the standard streams (stdin, stdout, stderr). +func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { + // Turn on VT handling on all std handles, if possible. This might + // fail, in which case we will fall back to terminal emulation. + var emulateStdin, emulateStdout, emulateStderr bool + fd := os.Stdin.Fd() + if mode, err := winterm.GetConsoleMode(fd); err == nil { + // Validate that enableVirtualTerminalInput is supported, but do not set it. + if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalInput); err != nil { + emulateStdin = true + } else { + vtInputSupported = true + } + // Unconditionally set the console mode back even on failure because SetConsoleMode + // remembers invalid bits on input handles. + winterm.SetConsoleMode(fd, mode) + } + + fd = os.Stdout.Fd() + if mode, err := winterm.GetConsoleMode(fd); err == nil { + // Validate disableNewlineAutoReturn is supported, but do not set it. + if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil { + emulateStdout = true + } else { + winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing) + } + } + + fd = os.Stderr.Fd() + if mode, err := winterm.GetConsoleMode(fd); err == nil { + // Validate disableNewlineAutoReturn is supported, but do not set it. + if err = winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing|disableNewlineAutoReturn); err != nil { + emulateStderr = true + } else { + winterm.SetConsoleMode(fd, mode|enableVirtualTerminalProcessing) + } + } + + if os.Getenv("ConEmuANSI") == "ON" || os.Getenv("ConsoleZVersion") != "" { + // The ConEmu and ConsoleZ terminals emulate ANSI on output streams well. + emulateStdin = true + emulateStdout = false + emulateStderr = false + } + + if emulateStdin { + stdIn = windows.NewAnsiReader(syscall.STD_INPUT_HANDLE) + } else { + stdIn = os.Stdin + } + + if emulateStdout { + stdOut = windows.NewAnsiWriter(syscall.STD_OUTPUT_HANDLE) + } else { + stdOut = os.Stdout + } + + if emulateStderr { + stdErr = windows.NewAnsiWriter(syscall.STD_ERROR_HANDLE) + } else { + stdErr = os.Stderr + } + + return +} + +// GetFdInfo returns the file descriptor for an os.File and indicates whether the file represents a terminal. +func GetFdInfo(in interface{}) (uintptr, bool) { + return windows.GetHandleInfo(in) +} + +// GetWinsize returns the window size based on the specified file descriptor. +func GetWinsize(fd uintptr) (*Winsize, error) { + info, err := winterm.GetConsoleScreenBufferInfo(fd) + if err != nil { + return nil, err + } + + winsize := &Winsize{ + Width: uint16(info.Window.Right - info.Window.Left + 1), + Height: uint16(info.Window.Bottom - info.Window.Top + 1), + } + + return winsize, nil +} + +// IsTerminal returns true if the given file descriptor is a terminal. +func IsTerminal(fd uintptr) bool { + return windows.IsConsole(fd) +} + +// RestoreTerminal restores the terminal connected to the given file descriptor +// to a previous state. +func RestoreTerminal(fd uintptr, state *State) error { + return winterm.SetConsoleMode(fd, state.mode) +} + +// SaveState saves the state of the terminal connected to the given file descriptor. +func SaveState(fd uintptr) (*State, error) { + mode, e := winterm.GetConsoleMode(fd) + if e != nil { + return nil, e + } + + return &State{mode: mode}, nil +} + +// DisableEcho disables echo for the terminal connected to the given file descriptor. +// -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx +func DisableEcho(fd uintptr, state *State) error { + mode := state.mode + mode &^= winterm.ENABLE_ECHO_INPUT + mode |= winterm.ENABLE_PROCESSED_INPUT | winterm.ENABLE_LINE_INPUT + err := winterm.SetConsoleMode(fd, mode) + if err != nil { + return err + } + + // Register an interrupt handler to catch and restore prior state + restoreAtInterrupt(fd, state) + return nil +} + +// SetRawTerminal puts the terminal connected to the given file descriptor into +// raw mode and returns the previous state. On UNIX, this puts both the input +// and output into raw mode. On Windows, it only puts the input into raw mode. +func SetRawTerminal(fd uintptr) (*State, error) { + state, err := MakeRaw(fd) + if err != nil { + return nil, err + } + + // Register an interrupt handler to catch and restore prior state + restoreAtInterrupt(fd, state) + return state, err +} + +// SetRawTerminalOutput puts the output of terminal connected to the given file +// descriptor into raw mode. On UNIX, this does nothing and returns nil for the +// state. On Windows, it disables LF -> CRLF translation. +func SetRawTerminalOutput(fd uintptr) (*State, error) { + state, err := SaveState(fd) + if err != nil { + return nil, err + } + + // Ignore failures, since disableNewlineAutoReturn might not be supported on this + // version of Windows. + winterm.SetConsoleMode(fd, state.mode|disableNewlineAutoReturn) + return state, err +} + +// MakeRaw puts the terminal (Windows Console) connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be restored. +func MakeRaw(fd uintptr) (*State, error) { + state, err := SaveState(fd) + if err != nil { + return nil, err + } + + mode := state.mode + + // See + // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx + // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx + + // Disable these modes + mode &^= winterm.ENABLE_ECHO_INPUT + mode &^= winterm.ENABLE_LINE_INPUT + mode &^= winterm.ENABLE_MOUSE_INPUT + mode &^= winterm.ENABLE_WINDOW_INPUT + mode &^= winterm.ENABLE_PROCESSED_INPUT + + // Enable these modes + mode |= winterm.ENABLE_EXTENDED_FLAGS + mode |= winterm.ENABLE_INSERT_MODE + mode |= winterm.ENABLE_QUICK_EDIT_MODE + if vtInputSupported { + mode |= enableVirtualTerminalInput + } + + err = winterm.SetConsoleMode(fd, mode) + if err != nil { + return nil, err + } + return state, nil +} + +func restoreAtInterrupt(fd uintptr, state *State) { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, os.Interrupt) + + go func() { + _ = <-sigchan + RestoreTerminal(fd, state) + os.Exit(0) + }() +} diff --git a/vendor/github.com/docker/docker/pkg/term/termios_darwin.go b/vendor/github.com/docker/docker/pkg/term/termios_darwin.go new file mode 100644 index 00000000000..480db900ac9 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/termios_darwin.go @@ -0,0 +1,69 @@ +package term + +import ( + "syscall" + "unsafe" +) + +const ( + getTermios = syscall.TIOCGETA + setTermios = syscall.TIOCSETA +) + +// Termios magic numbers, passthrough to the ones defined in syscall. +const ( + IGNBRK = syscall.IGNBRK + PARMRK = syscall.PARMRK + INLCR = syscall.INLCR + IGNCR = syscall.IGNCR + ECHONL = syscall.ECHONL + CSIZE = syscall.CSIZE + ICRNL = syscall.ICRNL + ISTRIP = syscall.ISTRIP + PARENB = syscall.PARENB + ECHO = syscall.ECHO + ICANON = syscall.ICANON + ISIG = syscall.ISIG + IXON = syscall.IXON + BRKINT = syscall.BRKINT + INPCK = syscall.INPCK + OPOST = syscall.OPOST + CS8 = syscall.CS8 + IEXTEN = syscall.IEXTEN +) + +// Termios is the Unix API for terminal I/O. +type Termios struct { + Iflag uint64 + Oflag uint64 + Cflag uint64 + Lflag uint64 + Cc [20]byte + Ispeed uint64 + Ospeed uint64 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd uintptr) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { + return nil, err + } + + newState := oldState.termios + newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON) + newState.Oflag &^= OPOST + newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN) + newState.Cflag &^= (CSIZE | PARENB) + newState.Cflag |= CS8 + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { + return nil, err + } + + return &oldState, nil +} diff --git a/vendor/github.com/docker/docker/pkg/term/termios_freebsd.go b/vendor/github.com/docker/docker/pkg/term/termios_freebsd.go new file mode 100644 index 00000000000..ed843ad69c9 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/termios_freebsd.go @@ -0,0 +1,69 @@ +package term + +import ( + "syscall" + "unsafe" +) + +const ( + getTermios = syscall.TIOCGETA + setTermios = syscall.TIOCSETA +) + +// Termios magic numbers, passthrough to the ones defined in syscall. +const ( + IGNBRK = syscall.IGNBRK + PARMRK = syscall.PARMRK + INLCR = syscall.INLCR + IGNCR = syscall.IGNCR + ECHONL = syscall.ECHONL + CSIZE = syscall.CSIZE + ICRNL = syscall.ICRNL + ISTRIP = syscall.ISTRIP + PARENB = syscall.PARENB + ECHO = syscall.ECHO + ICANON = syscall.ICANON + ISIG = syscall.ISIG + IXON = syscall.IXON + BRKINT = syscall.BRKINT + INPCK = syscall.INPCK + OPOST = syscall.OPOST + CS8 = syscall.CS8 + IEXTEN = syscall.IEXTEN +) + +// Termios is the Unix API for terminal I/O. +type Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]byte + Ispeed uint32 + Ospeed uint32 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd uintptr) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { + return nil, err + } + + newState := oldState.termios + newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON) + newState.Oflag &^= OPOST + newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN) + newState.Cflag &^= (CSIZE | PARENB) + newState.Cflag |= CS8 + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { + return nil, err + } + + return &oldState, nil +} diff --git a/vendor/github.com/docker/docker/pkg/term/termios_linux.go b/vendor/github.com/docker/docker/pkg/term/termios_linux.go new file mode 100644 index 00000000000..22921b6aef3 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/termios_linux.go @@ -0,0 +1,47 @@ +// +build !cgo + +package term + +import ( + "syscall" + "unsafe" +) + +const ( + getTermios = syscall.TCGETS + setTermios = syscall.TCSETS +) + +// Termios is the Unix API for terminal I/O. +type Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]byte + Ispeed uint32 + Ospeed uint32 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd uintptr) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { + return nil, err + } + + newState := oldState.termios + + newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) + newState.Oflag &^= syscall.OPOST + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) + newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) + newState.Cflag |= syscall.CS8 + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 { + return nil, err + } + return &oldState, nil +} diff --git a/vendor/github.com/docker/docker/pkg/term/termios_openbsd.go b/vendor/github.com/docker/docker/pkg/term/termios_openbsd.go new file mode 100644 index 00000000000..ed843ad69c9 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/termios_openbsd.go @@ -0,0 +1,69 @@ +package term + +import ( + "syscall" + "unsafe" +) + +const ( + getTermios = syscall.TIOCGETA + setTermios = syscall.TIOCSETA +) + +// Termios magic numbers, passthrough to the ones defined in syscall. +const ( + IGNBRK = syscall.IGNBRK + PARMRK = syscall.PARMRK + INLCR = syscall.INLCR + IGNCR = syscall.IGNCR + ECHONL = syscall.ECHONL + CSIZE = syscall.CSIZE + ICRNL = syscall.ICRNL + ISTRIP = syscall.ISTRIP + PARENB = syscall.PARENB + ECHO = syscall.ECHO + ICANON = syscall.ICANON + ISIG = syscall.ISIG + IXON = syscall.IXON + BRKINT = syscall.BRKINT + INPCK = syscall.INPCK + OPOST = syscall.OPOST + CS8 = syscall.CS8 + IEXTEN = syscall.IEXTEN +) + +// Termios is the Unix API for terminal I/O. +type Termios struct { + Iflag uint32 + Oflag uint32 + Cflag uint32 + Lflag uint32 + Cc [20]byte + Ispeed uint32 + Ospeed uint32 +} + +// MakeRaw put the terminal connected to the given file descriptor into raw +// mode and returns the previous state of the terminal so that it can be +// restored. +func MakeRaw(fd uintptr) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(getTermios), uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { + return nil, err + } + + newState := oldState.termios + newState.Iflag &^= (IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON) + newState.Oflag &^= OPOST + newState.Lflag &^= (ECHO | ECHONL | ICANON | ISIG | IEXTEN) + newState.Cflag &^= (CSIZE | PARENB) + newState.Cflag |= CS8 + newState.Cc[syscall.VMIN] = 1 + newState.Cc[syscall.VTIME] = 0 + + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { + return nil, err + } + + return &oldState, nil +} diff --git a/vendor/github.com/docker/docker/pkg/term/windows/ansi_reader.go b/vendor/github.com/docker/docker/pkg/term/windows/ansi_reader.go new file mode 100644 index 00000000000..cb0b88356df --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/windows/ansi_reader.go @@ -0,0 +1,263 @@ +// +build windows + +package windows + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "strings" + "unsafe" + + ansiterm "github.com/Azure/go-ansiterm" + "github.com/Azure/go-ansiterm/winterm" +) + +const ( + escapeSequence = ansiterm.KEY_ESC_CSI +) + +// ansiReader wraps a standard input file (e.g., os.Stdin) providing ANSI sequence translation. +type ansiReader struct { + file *os.File + fd uintptr + buffer []byte + cbBuffer int + command []byte +} + +// NewAnsiReader returns an io.ReadCloser that provides VT100 terminal emulation on top of a +// Windows console input handle. +func NewAnsiReader(nFile int) io.ReadCloser { + initLogger() + file, fd := winterm.GetStdFile(nFile) + return &ansiReader{ + file: file, + fd: fd, + command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH), + buffer: make([]byte, 0), + } +} + +// Close closes the wrapped file. +func (ar *ansiReader) Close() (err error) { + return ar.file.Close() +} + +// Fd returns the file descriptor of the wrapped file. +func (ar *ansiReader) Fd() uintptr { + return ar.fd +} + +// Read reads up to len(p) bytes of translated input events into p. +func (ar *ansiReader) Read(p []byte) (int, error) { + if len(p) == 0 { + return 0, nil + } + + // Previously read bytes exist, read as much as we can and return + if len(ar.buffer) > 0 { + logger.Debugf("Reading previously cached bytes") + + originalLength := len(ar.buffer) + copiedLength := copy(p, ar.buffer) + + if copiedLength == originalLength { + ar.buffer = make([]byte, 0, len(p)) + } else { + ar.buffer = ar.buffer[copiedLength:] + } + + logger.Debugf("Read from cache p[%d]: % x", copiedLength, p) + return copiedLength, nil + } + + // Read and translate key events + events, err := readInputEvents(ar.fd, len(p)) + if err != nil { + return 0, err + } else if len(events) == 0 { + logger.Debug("No input events detected") + return 0, nil + } + + keyBytes := translateKeyEvents(events, []byte(escapeSequence)) + + // Save excess bytes and right-size keyBytes + if len(keyBytes) > len(p) { + logger.Debugf("Received %d keyBytes, only room for %d bytes", len(keyBytes), len(p)) + ar.buffer = keyBytes[len(p):] + keyBytes = keyBytes[:len(p)] + } else if len(keyBytes) == 0 { + logger.Debug("No key bytes returned from the translator") + return 0, nil + } + + copiedLength := copy(p, keyBytes) + if copiedLength != len(keyBytes) { + return 0, errors.New("unexpected copy length encountered") + } + + logger.Debugf("Read p[%d]: % x", copiedLength, p) + logger.Debugf("Read keyBytes[%d]: % x", copiedLength, keyBytes) + return copiedLength, nil +} + +// readInputEvents polls until at least one event is available. +func readInputEvents(fd uintptr, maxBytes int) ([]winterm.INPUT_RECORD, error) { + // Determine the maximum number of records to retrieve + // -- Cast around the type system to obtain the size of a single INPUT_RECORD. + // unsafe.Sizeof requires an expression vs. a type-reference; the casting + // tricks the type system into believing it has such an expression. + recordSize := int(unsafe.Sizeof(*((*winterm.INPUT_RECORD)(unsafe.Pointer(&maxBytes))))) + countRecords := maxBytes / recordSize + if countRecords > ansiterm.MAX_INPUT_EVENTS { + countRecords = ansiterm.MAX_INPUT_EVENTS + } else if countRecords == 0 { + countRecords = 1 + } + logger.Debugf("[windows] readInputEvents: Reading %v records (buffer size %v, record size %v)", countRecords, maxBytes, recordSize) + + // Wait for and read input events + events := make([]winterm.INPUT_RECORD, countRecords) + nEvents := uint32(0) + eventsExist, err := winterm.WaitForSingleObject(fd, winterm.WAIT_INFINITE) + if err != nil { + return nil, err + } + + if eventsExist { + err = winterm.ReadConsoleInput(fd, events, &nEvents) + if err != nil { + return nil, err + } + } + + // Return a slice restricted to the number of returned records + logger.Debugf("[windows] readInputEvents: Read %v events", nEvents) + return events[:nEvents], nil +} + +// KeyEvent Translation Helpers + +var arrowKeyMapPrefix = map[uint16]string{ + winterm.VK_UP: "%s%sA", + winterm.VK_DOWN: "%s%sB", + winterm.VK_RIGHT: "%s%sC", + winterm.VK_LEFT: "%s%sD", +} + +var keyMapPrefix = map[uint16]string{ + winterm.VK_UP: "\x1B[%sA", + winterm.VK_DOWN: "\x1B[%sB", + winterm.VK_RIGHT: "\x1B[%sC", + winterm.VK_LEFT: "\x1B[%sD", + winterm.VK_HOME: "\x1B[1%s~", // showkey shows ^[[1 + winterm.VK_END: "\x1B[4%s~", // showkey shows ^[[4 + winterm.VK_INSERT: "\x1B[2%s~", + winterm.VK_DELETE: "\x1B[3%s~", + winterm.VK_PRIOR: "\x1B[5%s~", + winterm.VK_NEXT: "\x1B[6%s~", + winterm.VK_F1: "", + winterm.VK_F2: "", + winterm.VK_F3: "\x1B[13%s~", + winterm.VK_F4: "\x1B[14%s~", + winterm.VK_F5: "\x1B[15%s~", + winterm.VK_F6: "\x1B[17%s~", + winterm.VK_F7: "\x1B[18%s~", + winterm.VK_F8: "\x1B[19%s~", + winterm.VK_F9: "\x1B[20%s~", + winterm.VK_F10: "\x1B[21%s~", + winterm.VK_F11: "\x1B[23%s~", + winterm.VK_F12: "\x1B[24%s~", +} + +// translateKeyEvents converts the input events into the appropriate ANSI string. +func translateKeyEvents(events []winterm.INPUT_RECORD, escapeSequence []byte) []byte { + var buffer bytes.Buffer + for _, event := range events { + if event.EventType == winterm.KEY_EVENT && event.KeyEvent.KeyDown != 0 { + buffer.WriteString(keyToString(&event.KeyEvent, escapeSequence)) + } + } + + return buffer.Bytes() +} + +// keyToString maps the given input event record to the corresponding string. +func keyToString(keyEvent *winterm.KEY_EVENT_RECORD, escapeSequence []byte) string { + if keyEvent.UnicodeChar == 0 { + return formatVirtualKey(keyEvent.VirtualKeyCode, keyEvent.ControlKeyState, escapeSequence) + } + + _, alt, control := getControlKeys(keyEvent.ControlKeyState) + if control { + // TODO(azlinux): Implement following control sequences + // -D Signals the end of input from the keyboard; also exits current shell. + // -H Deletes the first character to the left of the cursor. Also called the ERASE key. + // -Q Restarts printing after it has been stopped with -s. + // -S Suspends printing on the screen (does not stop the program). + // -U Deletes all characters on the current line. Also called the KILL key. + // -E Quits current command and creates a core + + } + + // +Key generates ESC N Key + if !control && alt { + return ansiterm.KEY_ESC_N + strings.ToLower(string(keyEvent.UnicodeChar)) + } + + return string(keyEvent.UnicodeChar) +} + +// formatVirtualKey converts a virtual key (e.g., up arrow) into the appropriate ANSI string. +func formatVirtualKey(key uint16, controlState uint32, escapeSequence []byte) string { + shift, alt, control := getControlKeys(controlState) + modifier := getControlKeysModifier(shift, alt, control) + + if format, ok := arrowKeyMapPrefix[key]; ok { + return fmt.Sprintf(format, escapeSequence, modifier) + } + + if format, ok := keyMapPrefix[key]; ok { + return fmt.Sprintf(format, modifier) + } + + return "" +} + +// getControlKeys extracts the shift, alt, and ctrl key states. +func getControlKeys(controlState uint32) (shift, alt, control bool) { + shift = 0 != (controlState & winterm.SHIFT_PRESSED) + alt = 0 != (controlState & (winterm.LEFT_ALT_PRESSED | winterm.RIGHT_ALT_PRESSED)) + control = 0 != (controlState & (winterm.LEFT_CTRL_PRESSED | winterm.RIGHT_CTRL_PRESSED)) + return shift, alt, control +} + +// getControlKeysModifier returns the ANSI modifier for the given combination of control keys. +func getControlKeysModifier(shift, alt, control bool) string { + if shift && alt && control { + return ansiterm.KEY_CONTROL_PARAM_8 + } + if alt && control { + return ansiterm.KEY_CONTROL_PARAM_7 + } + if shift && control { + return ansiterm.KEY_CONTROL_PARAM_6 + } + if control { + return ansiterm.KEY_CONTROL_PARAM_5 + } + if shift && alt { + return ansiterm.KEY_CONTROL_PARAM_4 + } + if alt { + return ansiterm.KEY_CONTROL_PARAM_3 + } + if shift { + return ansiterm.KEY_CONTROL_PARAM_2 + } + return "" +} diff --git a/vendor/github.com/docker/docker/pkg/term/windows/ansi_writer.go b/vendor/github.com/docker/docker/pkg/term/windows/ansi_writer.go new file mode 100644 index 00000000000..a3ce5697d95 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/windows/ansi_writer.go @@ -0,0 +1,64 @@ +// +build windows + +package windows + +import ( + "io" + "os" + + ansiterm "github.com/Azure/go-ansiterm" + "github.com/Azure/go-ansiterm/winterm" +) + +// ansiWriter wraps a standard output file (e.g., os.Stdout) providing ANSI sequence translation. +type ansiWriter struct { + file *os.File + fd uintptr + infoReset *winterm.CONSOLE_SCREEN_BUFFER_INFO + command []byte + escapeSequence []byte + inAnsiSequence bool + parser *ansiterm.AnsiParser +} + +// NewAnsiWriter returns an io.Writer that provides VT100 terminal emulation on top of a +// Windows console output handle. +func NewAnsiWriter(nFile int) io.Writer { + initLogger() + file, fd := winterm.GetStdFile(nFile) + info, err := winterm.GetConsoleScreenBufferInfo(fd) + if err != nil { + return nil + } + + parser := ansiterm.CreateParser("Ground", winterm.CreateWinEventHandler(fd, file)) + logger.Infof("newAnsiWriter: parser %p", parser) + + aw := &ansiWriter{ + file: file, + fd: fd, + infoReset: info, + command: make([]byte, 0, ansiterm.ANSI_MAX_CMD_LENGTH), + escapeSequence: []byte(ansiterm.KEY_ESC_CSI), + parser: parser, + } + + logger.Infof("newAnsiWriter: aw.parser %p", aw.parser) + logger.Infof("newAnsiWriter: %v", aw) + return aw +} + +func (aw *ansiWriter) Fd() uintptr { + return aw.fd +} + +// Write writes len(p) bytes from p to the underlying data stream. +func (aw *ansiWriter) Write(p []byte) (total int, err error) { + if len(p) == 0 { + return 0, nil + } + + logger.Infof("Write: % x", p) + logger.Infof("Write: %s", string(p)) + return aw.parser.Parse(p) +} diff --git a/vendor/github.com/docker/docker/pkg/term/windows/console.go b/vendor/github.com/docker/docker/pkg/term/windows/console.go new file mode 100644 index 00000000000..ca5c3b2e535 --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/windows/console.go @@ -0,0 +1,35 @@ +// +build windows + +package windows + +import ( + "os" + + "github.com/Azure/go-ansiterm/winterm" +) + +// GetHandleInfo returns file descriptor and bool indicating whether the file is a console. +func GetHandleInfo(in interface{}) (uintptr, bool) { + switch t := in.(type) { + case *ansiReader: + return t.Fd(), true + case *ansiWriter: + return t.Fd(), true + } + + var inFd uintptr + var isTerminal bool + + if file, ok := in.(*os.File); ok { + inFd = file.Fd() + isTerminal = IsConsole(inFd) + } + return inFd, isTerminal +} + +// IsConsole returns true if the given file descriptor is a Windows Console. +// The code assumes that GetConsoleMode will return an error for file descriptors that are not a console. +func IsConsole(fd uintptr) bool { + _, e := winterm.GetConsoleMode(fd) + return e == nil +} diff --git a/vendor/github.com/docker/docker/pkg/term/windows/windows.go b/vendor/github.com/docker/docker/pkg/term/windows/windows.go new file mode 100644 index 00000000000..ce4cb5990ee --- /dev/null +++ b/vendor/github.com/docker/docker/pkg/term/windows/windows.go @@ -0,0 +1,33 @@ +// These files implement ANSI-aware input and output streams for use by the Docker Windows client. +// When asked for the set of standard streams (e.g., stdin, stdout, stderr), the code will create +// and return pseudo-streams that convert ANSI sequences to / from Windows Console API calls. + +package windows + +import ( + "io/ioutil" + "os" + "sync" + + ansiterm "github.com/Azure/go-ansiterm" + "github.com/Sirupsen/logrus" +) + +var logger *logrus.Logger +var initOnce sync.Once + +func initLogger() { + initOnce.Do(func() { + logFile := ioutil.Discard + + if isDebugEnv := os.Getenv(ansiterm.LogEnv); isDebugEnv == "1" { + logFile, _ = os.Create("ansiReaderWriter.log") + } + + logger = &logrus.Logger{ + Out: logFile, + Formatter: new(logrus.TextFormatter), + Level: logrus.DebugLevel, + } + }) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/LICENSE b/vendor/github.com/fsouza/go-dockerclient/LICENSE new file mode 100644 index 00000000000..545174c18f7 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2013-2017, go-dockerclient authors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/fsouza/go-dockerclient/auth.go b/vendor/github.com/fsouza/go-dockerclient/auth.go new file mode 100644 index 00000000000..03d192b7922 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/auth.go @@ -0,0 +1,182 @@ +// Copyright 2015 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path" + "strings" +) + +// ErrCannotParseDockercfg is the error returned by NewAuthConfigurations when the dockercfg cannot be parsed. +var ErrCannotParseDockercfg = errors.New("Failed to read authentication from dockercfg") + +// AuthConfiguration represents authentication options to use in the PushImage +// method. It represents the authentication in the Docker index server. +type AuthConfiguration struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Email string `json:"email,omitempty"` + ServerAddress string `json:"serveraddress,omitempty"` +} + +// AuthConfigurations represents authentication options to use for the +// PushImage method accommodating the new X-Registry-Config header +type AuthConfigurations struct { + Configs map[string]AuthConfiguration `json:"configs"` +} + +// AuthConfigurations119 is used to serialize a set of AuthConfigurations +// for Docker API >= 1.19. +type AuthConfigurations119 map[string]AuthConfiguration + +// dockerConfig represents a registry authentation configuration from the +// .dockercfg file. +type dockerConfig struct { + Auth string `json:"auth"` + Email string `json:"email"` +} + +// NewAuthConfigurationsFromFile returns AuthConfigurations from a path containing JSON +// in the same format as the .dockercfg file. +func NewAuthConfigurationsFromFile(path string) (*AuthConfigurations, error) { + r, err := os.Open(path) + if err != nil { + return nil, err + } + return NewAuthConfigurations(r) +} + +func cfgPaths(dockerConfigEnv string, homeEnv string) []string { + var paths []string + if dockerConfigEnv != "" { + paths = append(paths, path.Join(dockerConfigEnv, "config.json")) + } + if homeEnv != "" { + paths = append(paths, path.Join(homeEnv, ".docker", "config.json")) + paths = append(paths, path.Join(homeEnv, ".dockercfg")) + } + return paths +} + +// NewAuthConfigurationsFromDockerCfg returns AuthConfigurations from +// system config files. The following files are checked in the order listed: +// - $DOCKER_CONFIG/config.json if DOCKER_CONFIG set in the environment, +// - $HOME/.docker/config.json +// - $HOME/.dockercfg +func NewAuthConfigurationsFromDockerCfg() (*AuthConfigurations, error) { + err := fmt.Errorf("No docker configuration found") + var auths *AuthConfigurations + + pathsToTry := cfgPaths(os.Getenv("DOCKER_CONFIG"), os.Getenv("HOME")) + for _, path := range pathsToTry { + auths, err = NewAuthConfigurationsFromFile(path) + if err == nil { + return auths, nil + } + } + return auths, err +} + +// NewAuthConfigurations returns AuthConfigurations from a JSON encoded string in the +// same format as the .dockercfg file. +func NewAuthConfigurations(r io.Reader) (*AuthConfigurations, error) { + var auth *AuthConfigurations + confs, err := parseDockerConfig(r) + if err != nil { + return nil, err + } + auth, err = authConfigs(confs) + if err != nil { + return nil, err + } + return auth, nil +} + +func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) { + buf := new(bytes.Buffer) + buf.ReadFrom(r) + byteData := buf.Bytes() + + confsWrapper := struct { + Auths map[string]dockerConfig `json:"auths"` + }{} + if err := json.Unmarshal(byteData, &confsWrapper); err == nil { + if len(confsWrapper.Auths) > 0 { + return confsWrapper.Auths, nil + } + } + + var confs map[string]dockerConfig + if err := json.Unmarshal(byteData, &confs); err != nil { + return nil, err + } + return confs, nil +} + +// authConfigs converts a dockerConfigs map to a AuthConfigurations object. +func authConfigs(confs map[string]dockerConfig) (*AuthConfigurations, error) { + c := &AuthConfigurations{ + Configs: make(map[string]AuthConfiguration), + } + for reg, conf := range confs { + data, err := base64.StdEncoding.DecodeString(conf.Auth) + if err != nil { + return nil, err + } + userpass := strings.SplitN(string(data), ":", 2) + if len(userpass) != 2 { + return nil, ErrCannotParseDockercfg + } + c.Configs[reg] = AuthConfiguration{ + Email: conf.Email, + Username: userpass[0], + Password: userpass[1], + ServerAddress: reg, + } + } + return c, nil +} + +// AuthStatus returns the authentication status for Docker API versions >= 1.23. +type AuthStatus struct { + Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` + IdentityToken string `json:"IdentityToken,omitempty" yaml:"IdentityToken,omitempty" toml:"IdentityToken,omitempty"` +} + +// AuthCheck validates the given credentials. It returns nil if successful. +// +// For Docker API versions >= 1.23, the AuthStatus struct will be populated, otherwise it will be empty.` +// +// See https://goo.gl/6nsZkH for more details. +func (c *Client) AuthCheck(conf *AuthConfiguration) (AuthStatus, error) { + var authStatus AuthStatus + if conf == nil { + return authStatus, errors.New("conf is nil") + } + resp, err := c.do("POST", "/auth", doOptions{data: conf}) + if err != nil { + return authStatus, err + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return authStatus, err + } + if len(data) == 0 { + return authStatus, nil + } + if err := json.Unmarshal(data, &authStatus); err != nil { + return authStatus, err + } + return authStatus, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/change.go b/vendor/github.com/fsouza/go-dockerclient/change.go new file mode 100644 index 00000000000..3f936b22330 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/change.go @@ -0,0 +1,43 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import "fmt" + +// ChangeType is a type for constants indicating the type of change +// in a container +type ChangeType int + +const ( + // ChangeModify is the ChangeType for container modifications + ChangeModify ChangeType = iota + + // ChangeAdd is the ChangeType for additions to a container + ChangeAdd + + // ChangeDelete is the ChangeType for deletions from a container + ChangeDelete +) + +// Change represents a change in a container. +// +// See https://goo.gl/Wo0JJp for more details. +type Change struct { + Path string + Kind ChangeType +} + +func (change *Change) String() string { + var kind string + switch change.Kind { + case ChangeModify: + kind = "C" + case ChangeAdd: + kind = "A" + case ChangeDelete: + kind = "D" + } + return fmt.Sprintf("%s %s", kind, change.Path) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/client.go b/vendor/github.com/fsouza/go-dockerclient/client.go new file mode 100644 index 00000000000..2f5ed88306c --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/client.go @@ -0,0 +1,1034 @@ +// Copyright 2013 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package docker provides a client for the Docker remote API. +// +// See https://goo.gl/o2v3rk for more details on the remote API. +package docker + +import ( + "bufio" + "bytes" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + "sync/atomic" + "time" + + "github.com/docker/docker/opts" + "github.com/docker/docker/pkg/homedir" + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/stdcopy" + "github.com/hashicorp/go-cleanhttp" + "golang.org/x/net/context" + "golang.org/x/net/context/ctxhttp" +) + +const ( + userAgent = "go-dockerclient" + + unixProtocol = "unix" + namedPipeProtocol = "npipe" +) + +var ( + // ErrInvalidEndpoint is returned when the endpoint is not a valid HTTP URL. + ErrInvalidEndpoint = errors.New("invalid endpoint") + + // ErrConnectionRefused is returned when the client cannot connect to the given endpoint. + ErrConnectionRefused = errors.New("cannot connect to Docker endpoint") + + // ErrInactivityTimeout is returned when a streamable call has been inactive for some time. + ErrInactivityTimeout = errors.New("inactivity time exceeded timeout") + + apiVersion112, _ = NewAPIVersion("1.12") + apiVersion119, _ = NewAPIVersion("1.19") + apiVersion124, _ = NewAPIVersion("1.24") +) + +// APIVersion is an internal representation of a version of the Remote API. +type APIVersion []int + +// NewAPIVersion returns an instance of APIVersion for the given string. +// +// The given string must be in the form .., where , +// and are integer numbers. +func NewAPIVersion(input string) (APIVersion, error) { + if !strings.Contains(input, ".") { + return nil, fmt.Errorf("Unable to parse version %q", input) + } + raw := strings.Split(input, "-") + arr := strings.Split(raw[0], ".") + ret := make(APIVersion, len(arr)) + var err error + for i, val := range arr { + ret[i], err = strconv.Atoi(val) + if err != nil { + return nil, fmt.Errorf("Unable to parse version %q: %q is not an integer", input, val) + } + } + return ret, nil +} + +func (version APIVersion) String() string { + var str string + for i, val := range version { + str += strconv.Itoa(val) + if i < len(version)-1 { + str += "." + } + } + return str +} + +// LessThan is a function for comparing APIVersion structs +func (version APIVersion) LessThan(other APIVersion) bool { + return version.compare(other) < 0 +} + +// LessThanOrEqualTo is a function for comparing APIVersion structs +func (version APIVersion) LessThanOrEqualTo(other APIVersion) bool { + return version.compare(other) <= 0 +} + +// GreaterThan is a function for comparing APIVersion structs +func (version APIVersion) GreaterThan(other APIVersion) bool { + return version.compare(other) > 0 +} + +// GreaterThanOrEqualTo is a function for comparing APIVersion structs +func (version APIVersion) GreaterThanOrEqualTo(other APIVersion) bool { + return version.compare(other) >= 0 +} + +func (version APIVersion) compare(other APIVersion) int { + for i, v := range version { + if i <= len(other)-1 { + otherVersion := other[i] + + if v < otherVersion { + return -1 + } else if v > otherVersion { + return 1 + } + } + } + if len(version) > len(other) { + return 1 + } + if len(version) < len(other) { + return -1 + } + return 0 +} + +// Client is the basic type of this package. It provides methods for +// interaction with the API. +type Client struct { + SkipServerVersionCheck bool + HTTPClient *http.Client + TLSConfig *tls.Config + Dialer Dialer + + endpoint string + endpointURL *url.URL + eventMonitor *eventMonitoringState + requestedAPIVersion APIVersion + serverAPIVersion APIVersion + expectedAPIVersion APIVersion + nativeHTTPClient *http.Client +} + +// Dialer is an interface that allows network connections to be dialed +// (net.Dialer fulfills this interface) and named pipes (a shim using +// winio.DialPipe) +type Dialer interface { + Dial(network, address string) (net.Conn, error) +} + +// NewClient returns a Client instance ready for communication with the given +// server endpoint. It will use the latest remote API version available in the +// server. +func NewClient(endpoint string) (*Client, error) { + client, err := NewVersionedClient(endpoint, "") + if err != nil { + return nil, err + } + client.SkipServerVersionCheck = true + return client, nil +} + +// NewTLSClient returns a Client instance ready for TLS communications with the givens +// server endpoint, key and certificates . It will use the latest remote API version +// available in the server. +func NewTLSClient(endpoint string, cert, key, ca string) (*Client, error) { + client, err := NewVersionedTLSClient(endpoint, cert, key, ca, "") + if err != nil { + return nil, err + } + client.SkipServerVersionCheck = true + return client, nil +} + +// NewTLSClientFromBytes returns a Client instance ready for TLS communications with the givens +// server endpoint, key and certificates (passed inline to the function as opposed to being +// read from a local file). It will use the latest remote API version available in the server. +func NewTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte) (*Client, error) { + client, err := NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, "") + if err != nil { + return nil, err + } + client.SkipServerVersionCheck = true + return client, nil +} + +// NewVersionedClient returns a Client instance ready for communication with +// the given server endpoint, using a specific remote API version. +func NewVersionedClient(endpoint string, apiVersionString string) (*Client, error) { + u, err := parseEndpoint(endpoint, false) + if err != nil { + return nil, err + } + var requestedAPIVersion APIVersion + if strings.Contains(apiVersionString, ".") { + requestedAPIVersion, err = NewAPIVersion(apiVersionString) + if err != nil { + return nil, err + } + } + c := &Client{ + HTTPClient: cleanhttp.DefaultClient(), + Dialer: &net.Dialer{}, + endpoint: endpoint, + endpointURL: u, + eventMonitor: new(eventMonitoringState), + requestedAPIVersion: requestedAPIVersion, + } + c.initializeNativeClient() + return c, nil +} + +// NewVersionnedTLSClient has been DEPRECATED, please use NewVersionedTLSClient. +func NewVersionnedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { + return NewVersionedTLSClient(endpoint, cert, key, ca, apiVersionString) +} + +// NewVersionedTLSClient returns a Client instance ready for TLS communications with the givens +// server endpoint, key and certificates, using a specific remote API version. +func NewVersionedTLSClient(endpoint string, cert, key, ca, apiVersionString string) (*Client, error) { + var certPEMBlock []byte + var keyPEMBlock []byte + var caPEMCert []byte + if _, err := os.Stat(cert); !os.IsNotExist(err) { + certPEMBlock, err = ioutil.ReadFile(cert) + if err != nil { + return nil, err + } + } + if _, err := os.Stat(key); !os.IsNotExist(err) { + keyPEMBlock, err = ioutil.ReadFile(key) + if err != nil { + return nil, err + } + } + if _, err := os.Stat(ca); !os.IsNotExist(err) { + caPEMCert, err = ioutil.ReadFile(ca) + if err != nil { + return nil, err + } + } + return NewVersionedTLSClientFromBytes(endpoint, certPEMBlock, keyPEMBlock, caPEMCert, apiVersionString) +} + +// NewClientFromEnv returns a Client instance ready for communication created from +// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH. +// +// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68. +// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7. +func NewClientFromEnv() (*Client, error) { + client, err := NewVersionedClientFromEnv("") + if err != nil { + return nil, err + } + client.SkipServerVersionCheck = true + return client, nil +} + +// NewVersionedClientFromEnv returns a Client instance ready for TLS communications created from +// Docker's default logic for the environment variables DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT_PATH, +// and using a specific remote API version. +// +// See https://github.com/docker/docker/blob/1f963af697e8df3a78217f6fdbf67b8123a7db94/docker/docker.go#L68. +// See https://github.com/docker/compose/blob/81707ef1ad94403789166d2fe042c8a718a4c748/compose/cli/docker_client.py#L7. +func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) { + dockerEnv, err := getDockerEnv() + if err != nil { + return nil, err + } + dockerHost := dockerEnv.dockerHost + if dockerEnv.dockerTLSVerify { + parts := strings.SplitN(dockerEnv.dockerHost, "://", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("could not split %s into two parts by ://", dockerHost) + } + cert := filepath.Join(dockerEnv.dockerCertPath, "cert.pem") + key := filepath.Join(dockerEnv.dockerCertPath, "key.pem") + ca := filepath.Join(dockerEnv.dockerCertPath, "ca.pem") + return NewVersionedTLSClient(dockerEnv.dockerHost, cert, key, ca, apiVersionString) + } + return NewVersionedClient(dockerEnv.dockerHost, apiVersionString) +} + +// NewVersionedTLSClientFromBytes returns a Client instance ready for TLS communications with the givens +// server endpoint, key and certificates (passed inline to the function as opposed to being +// read from a local file), using a specific remote API version. +func NewVersionedTLSClientFromBytes(endpoint string, certPEMBlock, keyPEMBlock, caPEMCert []byte, apiVersionString string) (*Client, error) { + u, err := parseEndpoint(endpoint, true) + if err != nil { + return nil, err + } + var requestedAPIVersion APIVersion + if strings.Contains(apiVersionString, ".") { + requestedAPIVersion, err = NewAPIVersion(apiVersionString) + if err != nil { + return nil, err + } + } + tlsConfig := &tls.Config{} + if certPEMBlock != nil && keyPEMBlock != nil { + tlsCert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) + if err != nil { + return nil, err + } + tlsConfig.Certificates = []tls.Certificate{tlsCert} + } + if caPEMCert == nil { + tlsConfig.InsecureSkipVerify = true + } else { + caPool := x509.NewCertPool() + if !caPool.AppendCertsFromPEM(caPEMCert) { + return nil, errors.New("Could not add RootCA pem") + } + tlsConfig.RootCAs = caPool + } + tr := cleanhttp.DefaultTransport() + tr.TLSClientConfig = tlsConfig + if err != nil { + return nil, err + } + c := &Client{ + HTTPClient: &http.Client{Transport: tr}, + TLSConfig: tlsConfig, + Dialer: &net.Dialer{}, + endpoint: endpoint, + endpointURL: u, + eventMonitor: new(eventMonitoringState), + requestedAPIVersion: requestedAPIVersion, + } + c.initializeNativeClient() + return c, nil +} + +// SetTimeout takes a timeout and applies it to both the HTTPClient and +// nativeHTTPClient. It should not be called concurrently with any other Client +// methods. +func (c *Client) SetTimeout(t time.Duration) { + if c.HTTPClient != nil { + c.HTTPClient.Timeout = t + } + if c.nativeHTTPClient != nil { + c.nativeHTTPClient.Timeout = t + } +} + +func (c *Client) checkAPIVersion() error { + serverAPIVersionString, err := c.getServerAPIVersionString() + if err != nil { + return err + } + c.serverAPIVersion, err = NewAPIVersion(serverAPIVersionString) + if err != nil { + return err + } + if c.requestedAPIVersion == nil { + c.expectedAPIVersion = c.serverAPIVersion + } else { + c.expectedAPIVersion = c.requestedAPIVersion + } + return nil +} + +// Endpoint returns the current endpoint. It's useful for getting the endpoint +// when using functions that get this data from the environment (like +// NewClientFromEnv. +func (c *Client) Endpoint() string { + return c.endpoint +} + +// Ping pings the docker server +// +// See https://goo.gl/wYfgY1 for more details. +func (c *Client) Ping() error { + return c.PingWithContext(nil) +} + +// PingWithContext pings the docker server +// The context object can be used to cancel the ping request. +// +// See https://goo.gl/wYfgY1 for more details. +func (c *Client) PingWithContext(ctx context.Context) error { + path := "/_ping" + resp, err := c.do("GET", path, doOptions{context: ctx}) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return newError(resp) + } + resp.Body.Close() + return nil +} + +func (c *Client) getServerAPIVersionString() (version string, err error) { + resp, err := c.do("GET", "/version", doOptions{}) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("Received unexpected status %d while trying to retrieve the server version", resp.StatusCode) + } + var versionResponse map[string]interface{} + if err := json.NewDecoder(resp.Body).Decode(&versionResponse); err != nil { + return "", err + } + if version, ok := (versionResponse["ApiVersion"]).(string); ok { + return version, nil + } + return "", nil +} + +type doOptions struct { + data interface{} + forceJSON bool + headers map[string]string + context context.Context +} + +func (c *Client) do(method, path string, doOptions doOptions) (*http.Response, error) { + var params io.Reader + if doOptions.data != nil || doOptions.forceJSON { + buf, err := json.Marshal(doOptions.data) + if err != nil { + return nil, err + } + params = bytes.NewBuffer(buf) + } + if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { + err := c.checkAPIVersion() + if err != nil { + return nil, err + } + } + httpClient := c.HTTPClient + protocol := c.endpointURL.Scheme + var u string + switch protocol { + case unixProtocol, namedPipeProtocol: + httpClient = c.nativeHTTPClient + u = c.getFakeNativeURL(path) + default: + u = c.getURL(path) + } + + req, err := http.NewRequest(method, u, params) + if err != nil { + return nil, err + } + req.Header.Set("User-Agent", userAgent) + if doOptions.data != nil { + req.Header.Set("Content-Type", "application/json") + } else if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } + + for k, v := range doOptions.headers { + req.Header.Set(k, v) + } + + ctx := doOptions.context + if ctx == nil { + ctx = context.Background() + } + + resp, err := ctxhttp.Do(ctx, httpClient, req) + if err != nil { + if strings.Contains(err.Error(), "connection refused") { + return nil, ErrConnectionRefused + } + + return nil, chooseError(ctx, err) + } + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + return nil, newError(resp) + } + return resp, nil +} + +type streamOptions struct { + setRawTerminal bool + rawJSONStream bool + useJSONDecoder bool + headers map[string]string + in io.Reader + stdout io.Writer + stderr io.Writer + // timeout is the initial connection timeout + timeout time.Duration + // Timeout with no data is received, it's reset every time new data + // arrives + inactivityTimeout time.Duration + context context.Context +} + +// if error in context, return that instead of generic http error +func chooseError(ctx context.Context, err error) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + return err + } +} + +func (c *Client) stream(method, path string, streamOptions streamOptions) error { + if (method == "POST" || method == "PUT") && streamOptions.in == nil { + streamOptions.in = bytes.NewReader(nil) + } + if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { + err := c.checkAPIVersion() + if err != nil { + return err + } + } + req, err := http.NewRequest(method, c.getURL(path), streamOptions.in) + if err != nil { + return err + } + req.Header.Set("User-Agent", userAgent) + if method == "POST" { + req.Header.Set("Content-Type", "plain/text") + } + for key, val := range streamOptions.headers { + req.Header.Set(key, val) + } + var resp *http.Response + protocol := c.endpointURL.Scheme + address := c.endpointURL.Path + if streamOptions.stdout == nil { + streamOptions.stdout = ioutil.Discard + } + if streamOptions.stderr == nil { + streamOptions.stderr = ioutil.Discard + } + + // make a sub-context so that our active cancellation does not affect parent + ctx := streamOptions.context + if ctx == nil { + ctx = context.Background() + } + subCtx, cancelRequest := context.WithCancel(ctx) + defer cancelRequest() + + if protocol == unixProtocol || protocol == namedPipeProtocol { + var dial net.Conn + dial, err = c.Dialer.Dial(protocol, address) + if err != nil { + return err + } + go func() { + <-subCtx.Done() + dial.Close() + }() + breader := bufio.NewReader(dial) + err = req.Write(dial) + if err != nil { + return chooseError(subCtx, err) + } + + // ReadResponse may hang if server does not replay + if streamOptions.timeout > 0 { + dial.SetDeadline(time.Now().Add(streamOptions.timeout)) + } + + if resp, err = http.ReadResponse(breader, req); err != nil { + // Cancel timeout for future I/O operations + if streamOptions.timeout > 0 { + dial.SetDeadline(time.Time{}) + } + if strings.Contains(err.Error(), "connection refused") { + return ErrConnectionRefused + } + + return chooseError(subCtx, err) + } + } else { + if resp, err = ctxhttp.Do(subCtx, c.HTTPClient, req); err != nil { + if strings.Contains(err.Error(), "connection refused") { + return ErrConnectionRefused + } + return chooseError(subCtx, err) + } + } + defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 400 { + return newError(resp) + } + var canceled uint32 + if streamOptions.inactivityTimeout > 0 { + var ch chan<- struct{} + resp.Body, ch = handleInactivityTimeout(resp.Body, streamOptions.inactivityTimeout, cancelRequest, &canceled) + defer close(ch) + } + err = handleStreamResponse(resp, &streamOptions) + if err != nil { + if atomic.LoadUint32(&canceled) != 0 { + return ErrInactivityTimeout + } + return chooseError(subCtx, err) + } + return nil +} + +func handleStreamResponse(resp *http.Response, streamOptions *streamOptions) error { + var err error + if !streamOptions.useJSONDecoder && resp.Header.Get("Content-Type") != "application/json" { + if streamOptions.setRawTerminal { + _, err = io.Copy(streamOptions.stdout, resp.Body) + } else { + _, err = stdcopy.StdCopy(streamOptions.stdout, streamOptions.stderr, resp.Body) + } + return err + } + // if we want to get raw json stream, just copy it back to output + // without decoding it + if streamOptions.rawJSONStream { + _, err = io.Copy(streamOptions.stdout, resp.Body) + return err + } + if st, ok := streamOptions.stdout.(interface { + io.Writer + FD() uintptr + IsTerminal() bool + }); ok { + err = jsonmessage.DisplayJSONMessagesToStream(resp.Body, st, nil) + } else { + err = jsonmessage.DisplayJSONMessagesStream(resp.Body, streamOptions.stdout, 0, false, nil) + } + return err +} + +type proxyReader struct { + io.ReadCloser + calls uint64 +} + +func (p *proxyReader) callCount() uint64 { + return atomic.LoadUint64(&p.calls) +} + +func (p *proxyReader) Read(data []byte) (int, error) { + atomic.AddUint64(&p.calls, 1) + return p.ReadCloser.Read(data) +} + +func handleInactivityTimeout(reader io.ReadCloser, timeout time.Duration, cancelRequest func(), canceled *uint32) (io.ReadCloser, chan<- struct{}) { + done := make(chan struct{}) + proxyReader := &proxyReader{ReadCloser: reader} + go func() { + var lastCallCount uint64 + for { + select { + case <-time.After(timeout): + case <-done: + return + } + curCallCount := proxyReader.callCount() + if curCallCount == lastCallCount { + atomic.AddUint32(canceled, 1) + cancelRequest() + return + } + lastCallCount = curCallCount + } + }() + return proxyReader, done +} + +type hijackOptions struct { + success chan struct{} + setRawTerminal bool + in io.Reader + stdout io.Writer + stderr io.Writer + data interface{} +} + +// CloseWaiter is an interface with methods for closing the underlying resource +// and then waiting for it to finish processing. +type CloseWaiter interface { + io.Closer + Wait() error +} + +type waiterFunc func() error + +func (w waiterFunc) Wait() error { return w() } + +type closerFunc func() error + +func (c closerFunc) Close() error { return c() } + +func (c *Client) hijack(method, path string, hijackOptions hijackOptions) (CloseWaiter, error) { + if path != "/version" && !c.SkipServerVersionCheck && c.expectedAPIVersion == nil { + err := c.checkAPIVersion() + if err != nil { + return nil, err + } + } + var params io.Reader + if hijackOptions.data != nil { + buf, err := json.Marshal(hijackOptions.data) + if err != nil { + return nil, err + } + params = bytes.NewBuffer(buf) + } + req, err := http.NewRequest(method, c.getURL(path), params) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Connection", "Upgrade") + req.Header.Set("Upgrade", "tcp") + protocol := c.endpointURL.Scheme + address := c.endpointURL.Path + if protocol != unixProtocol && protocol != namedPipeProtocol { + protocol = "tcp" + address = c.endpointURL.Host + } + var dial net.Conn + if c.TLSConfig != nil && protocol != unixProtocol && protocol != namedPipeProtocol { + netDialer, ok := c.Dialer.(*net.Dialer) + if !ok { + return nil, ErrTLSNotSupported + } + dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig) + if err != nil { + return nil, err + } + } else { + dial, err = c.Dialer.Dial(protocol, address) + if err != nil { + return nil, err + } + } + + errs := make(chan error, 1) + quit := make(chan struct{}) + go func() { + clientconn := httputil.NewClientConn(dial, nil) + defer clientconn.Close() + clientconn.Do(req) + if hijackOptions.success != nil { + hijackOptions.success <- struct{}{} + <-hijackOptions.success + } + rwc, br := clientconn.Hijack() + defer rwc.Close() + + errChanOut := make(chan error, 1) + errChanIn := make(chan error, 2) + if hijackOptions.stdout == nil && hijackOptions.stderr == nil { + close(errChanOut) + } else { + // Only copy if hijackOptions.stdout and/or hijackOptions.stderr is actually set. + // Otherwise, if the only stream you care about is stdin, your attach session + // will "hang" until the container terminates, even though you're not reading + // stdout/stderr + if hijackOptions.stdout == nil { + hijackOptions.stdout = ioutil.Discard + } + if hijackOptions.stderr == nil { + hijackOptions.stderr = ioutil.Discard + } + + go func() { + defer func() { + if hijackOptions.in != nil { + if closer, ok := hijackOptions.in.(io.Closer); ok { + closer.Close() + } + errChanIn <- nil + } + }() + + var err error + if hijackOptions.setRawTerminal { + _, err = io.Copy(hijackOptions.stdout, br) + } else { + _, err = stdcopy.StdCopy(hijackOptions.stdout, hijackOptions.stderr, br) + } + errChanOut <- err + }() + } + + go func() { + var err error + if hijackOptions.in != nil { + _, err = io.Copy(rwc, hijackOptions.in) + } + errChanIn <- err + rwc.(interface { + CloseWrite() error + }).CloseWrite() + }() + + var errIn error + select { + case errIn = <-errChanIn: + case <-quit: + } + + var errOut error + select { + case errOut = <-errChanOut: + case <-quit: + } + + if errIn != nil { + errs <- errIn + } else { + errs <- errOut + } + }() + + return struct { + closerFunc + waiterFunc + }{ + closerFunc(func() error { close(quit); return nil }), + waiterFunc(func() error { return <-errs }), + }, nil +} + +func (c *Client) getURL(path string) string { + urlStr := strings.TrimRight(c.endpointURL.String(), "/") + if c.endpointURL.Scheme == unixProtocol || c.endpointURL.Scheme == namedPipeProtocol { + urlStr = "" + } + if c.requestedAPIVersion != nil { + return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path) + } + return fmt.Sprintf("%s%s", urlStr, path) +} + +// getFakeNativeURL returns the URL needed to make an HTTP request over a UNIX +// domain socket to the given path. +func (c *Client) getFakeNativeURL(path string) string { + u := *c.endpointURL // Copy. + + // Override URL so that net/http will not complain. + u.Scheme = "http" + u.Host = "unix.sock" // Doesn't matter what this is - it's not used. + u.Path = "" + urlStr := strings.TrimRight(u.String(), "/") + if c.requestedAPIVersion != nil { + return fmt.Sprintf("%s/v%s%s", urlStr, c.requestedAPIVersion, path) + } + return fmt.Sprintf("%s%s", urlStr, path) +} + +type jsonMessage struct { + Status string `json:"status,omitempty"` + Progress string `json:"progress,omitempty"` + Error string `json:"error,omitempty"` + Stream string `json:"stream,omitempty"` +} + +func queryString(opts interface{}) string { + if opts == nil { + return "" + } + value := reflect.ValueOf(opts) + if value.Kind() == reflect.Ptr { + value = value.Elem() + } + if value.Kind() != reflect.Struct { + return "" + } + items := url.Values(map[string][]string{}) + for i := 0; i < value.NumField(); i++ { + field := value.Type().Field(i) + if field.PkgPath != "" { + continue + } + key := field.Tag.Get("qs") + if key == "" { + key = strings.ToLower(field.Name) + } else if key == "-" { + continue + } + addQueryStringValue(items, key, value.Field(i)) + } + return items.Encode() +} + +func addQueryStringValue(items url.Values, key string, v reflect.Value) { + switch v.Kind() { + case reflect.Bool: + if v.Bool() { + items.Add(key, "1") + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if v.Int() > 0 { + items.Add(key, strconv.FormatInt(v.Int(), 10)) + } + case reflect.Float32, reflect.Float64: + if v.Float() > 0 { + items.Add(key, strconv.FormatFloat(v.Float(), 'f', -1, 64)) + } + case reflect.String: + if v.String() != "" { + items.Add(key, v.String()) + } + case reflect.Ptr: + if !v.IsNil() { + if b, err := json.Marshal(v.Interface()); err == nil { + items.Add(key, string(b)) + } + } + case reflect.Map: + if len(v.MapKeys()) > 0 { + if b, err := json.Marshal(v.Interface()); err == nil { + items.Add(key, string(b)) + } + } + case reflect.Array, reflect.Slice: + vLen := v.Len() + if vLen > 0 { + for i := 0; i < vLen; i++ { + addQueryStringValue(items, key, v.Index(i)) + } + } + } +} + +// Error represents failures in the API. It represents a failure from the API. +type Error struct { + Status int + Message string +} + +func newError(resp *http.Response) *Error { + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return &Error{Status: resp.StatusCode, Message: fmt.Sprintf("cannot read body, err: %v", err)} + } + return &Error{Status: resp.StatusCode, Message: string(data)} +} + +func (e *Error) Error() string { + return fmt.Sprintf("API error (%d): %s", e.Status, e.Message) +} + +func parseEndpoint(endpoint string, tls bool) (*url.URL, error) { + if endpoint != "" && !strings.Contains(endpoint, "://") { + endpoint = "tcp://" + endpoint + } + u, err := url.Parse(endpoint) + if err != nil { + return nil, ErrInvalidEndpoint + } + if tls && u.Scheme != "unix" { + u.Scheme = "https" + } + switch u.Scheme { + case unixProtocol, namedPipeProtocol: + return u, nil + case "http", "https", "tcp": + _, port, err := net.SplitHostPort(u.Host) + if err != nil { + if e, ok := err.(*net.AddrError); ok { + if e.Err == "missing port in address" { + return u, nil + } + } + return nil, ErrInvalidEndpoint + } + number, err := strconv.ParseInt(port, 10, 64) + if err == nil && number > 0 && number < 65536 { + if u.Scheme == "tcp" { + if tls { + u.Scheme = "https" + } else { + u.Scheme = "http" + } + } + return u, nil + } + return nil, ErrInvalidEndpoint + default: + return nil, ErrInvalidEndpoint + } +} + +type dockerEnv struct { + dockerHost string + dockerTLSVerify bool + dockerCertPath string +} + +func getDockerEnv() (*dockerEnv, error) { + dockerHost := os.Getenv("DOCKER_HOST") + var err error + if dockerHost == "" { + dockerHost = opts.DefaultHost + } + dockerTLSVerify := os.Getenv("DOCKER_TLS_VERIFY") != "" + var dockerCertPath string + if dockerTLSVerify { + dockerCertPath = os.Getenv("DOCKER_CERT_PATH") + if dockerCertPath == "" { + home := homedir.Get() + if home == "" { + return nil, errors.New("environment variable HOME must be set if DOCKER_CERT_PATH is not set") + } + dockerCertPath = filepath.Join(home, ".docker") + dockerCertPath, err = filepath.Abs(dockerCertPath) + if err != nil { + return nil, err + } + } + } + return &dockerEnv{ + dockerHost: dockerHost, + dockerTLSVerify: dockerTLSVerify, + dockerCertPath: dockerCertPath, + }, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/client_unix.go b/vendor/github.com/fsouza/go-dockerclient/client_unix.go new file mode 100644 index 00000000000..1bc6af6ed81 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/client_unix.go @@ -0,0 +1,31 @@ +// +build !windows +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "net" + "net/http" + + "github.com/hashicorp/go-cleanhttp" +) + +// initializeNativeClient initializes the native Unix domain socket client on +// Unix-style operating systems +func (c *Client) initializeNativeClient() { + if c.endpointURL.Scheme != unixProtocol { + return + } + socketPath := c.endpointURL.Path + tr := cleanhttp.DefaultTransport() + tr.Dial = func(network, addr string) (net.Conn, error) { + return c.Dialer.Dial(network, addr) + } + tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return c.Dialer.Dial(unixProtocol, socketPath) + } + c.nativeHTTPClient = &http.Client{Transport: tr} +} diff --git a/vendor/github.com/fsouza/go-dockerclient/client_windows.go b/vendor/github.com/fsouza/go-dockerclient/client_windows.go new file mode 100644 index 00000000000..95ef56b4c3c --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/client_windows.go @@ -0,0 +1,46 @@ +// +build windows + +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "context" + "net" + "net/http" + "time" + + "github.com/Microsoft/go-winio" + "github.com/hashicorp/go-cleanhttp" +) + +const namedPipeConnectTimeout = 2 * time.Second + +type pipeDialer struct { + dialFunc func(network, addr string) (net.Conn, error) +} + +func (p pipeDialer) Dial(network, address string) (net.Conn, error) { + return p.dialFunc(network, address) +} + +// initializeNativeClient initializes the native Named Pipe client for Windows +func (c *Client) initializeNativeClient() { + if c.endpointURL.Scheme != namedPipeProtocol { + return + } + namedPipePath := c.endpointURL.Path + dialFunc := func(network, addr string) (net.Conn, error) { + timeout := namedPipeConnectTimeout + return winio.DialPipe(namedPipePath, &timeout) + } + tr := cleanhttp.DefaultTransport() + tr.Dial = dialFunc + tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialFunc(network, addr) + } + c.Dialer = &pipeDialer{dialFunc} + c.nativeHTTPClient = &http.Client{Transport: tr} +} diff --git a/vendor/github.com/fsouza/go-dockerclient/container.go b/vendor/github.com/fsouza/go-dockerclient/container.go new file mode 100644 index 00000000000..a6734e3c3a7 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/container.go @@ -0,0 +1,1512 @@ +// Copyright 2013 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/docker/go-units" + "golang.org/x/net/context" +) + +// ErrContainerAlreadyExists is the error returned by CreateContainer when the +// container already exists. +var ErrContainerAlreadyExists = errors.New("container already exists") + +// ListContainersOptions specify parameters to the ListContainers function. +// +// See https://goo.gl/kaOHGw for more details. +type ListContainersOptions struct { + All bool + Size bool + Limit int + Since string + Before string + Filters map[string][]string + Context context.Context +} + +// APIPort is a type that represents a port mapping returned by the Docker API +type APIPort struct { + PrivatePort int64 `json:"PrivatePort,omitempty" yaml:"PrivatePort,omitempty" toml:"PrivatePort,omitempty"` + PublicPort int64 `json:"PublicPort,omitempty" yaml:"PublicPort,omitempty" toml:"PublicPort,omitempty"` + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` + IP string `json:"IP,omitempty" yaml:"IP,omitempty" toml:"IP,omitempty"` +} + +// APIMount represents a mount point for a container. +type APIMount struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Source string `json:"Source,omitempty" yaml:"Source,omitempty" toml:"Source,omitempty"` + Destination string `json:"Destination,omitempty" yaml:"Destination,omitempty" toml:"Destination,omitempty"` + Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` + Mode string `json:"Mode,omitempty" yaml:"Mode,omitempty" toml:"Mode,omitempty"` + RW bool `json:"RW,omitempty" yaml:"RW,omitempty" toml:"RW,omitempty"` + Propogation string `json:"Propogation,omitempty" yaml:"Propogation,omitempty" toml:"Propogation,omitempty"` +} + +// APIContainers represents each container in the list returned by +// ListContainers. +type APIContainers struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` + Command string `json:"Command,omitempty" yaml:"Command,omitempty" toml:"Command,omitempty"` + Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` + State string `json:"State,omitempty" yaml:"State,omitempty" toml:"State,omitempty"` + Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` + Ports []APIPort `json:"Ports,omitempty" yaml:"Ports,omitempty" toml:"Ports,omitempty"` + SizeRw int64 `json:"SizeRw,omitempty" yaml:"SizeRw,omitempty" toml:"SizeRw,omitempty"` + SizeRootFs int64 `json:"SizeRootFs,omitempty" yaml:"SizeRootFs,omitempty" toml:"SizeRootFs,omitempty"` + Names []string `json:"Names,omitempty" yaml:"Names,omitempty" toml:"Names,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` + Networks NetworkList `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty" toml:"NetworkSettings,omitempty"` + Mounts []APIMount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` +} + +// NetworkList encapsulates a map of networks, as returned by the Docker API in +// ListContainers. +type NetworkList struct { + Networks map[string]ContainerNetwork `json:"Networks" yaml:"Networks,omitempty" toml:"Networks,omitempty"` +} + +// ListContainers returns a slice of containers matching the given criteria. +// +// See https://goo.gl/kaOHGw for more details. +func (c *Client) ListContainers(opts ListContainersOptions) ([]APIContainers, error) { + path := "/containers/json?" + queryString(opts) + resp, err := c.do("GET", path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var containers []APIContainers + if err := json.NewDecoder(resp.Body).Decode(&containers); err != nil { + return nil, err + } + return containers, nil +} + +// Port represents the port number and the protocol, in the form +// /. For example: 80/tcp. +type Port string + +// Port returns the number of the port. +func (p Port) Port() string { + return strings.Split(string(p), "/")[0] +} + +// Proto returns the name of the protocol. +func (p Port) Proto() string { + parts := strings.Split(string(p), "/") + if len(parts) == 1 { + return "tcp" + } + return parts[1] +} + +// HealthCheck represents one check of health. +type HealthCheck struct { + Start time.Time `json:"Start,omitempty" yaml:"Start,omitempty" toml:"Start,omitempty"` + End time.Time `json:"End,omitempty" yaml:"End,omitempty" toml:"End,omitempty"` + ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` + Output string `json:"Output,omitempty" yaml:"Output,omitempty" toml:"Output,omitempty"` +} + +// Health represents the health of a container. +type Health struct { + Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` + FailingStreak int `json:"FailingStreak,omitempty" yaml:"FailingStreak,omitempty" toml:"FailingStreak,omitempty"` + Log []HealthCheck `json:"Log,omitempty" yaml:"Log,omitempty" toml:"Log,omitempty"` +} + +// State represents the state of a container. +type State struct { + Status string `json:"Status,omitempty" yaml:"Status,omitempty" toml:"Status,omitempty"` + Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"` + Paused bool `json:"Paused,omitempty" yaml:"Paused,omitempty" toml:"Paused,omitempty"` + Restarting bool `json:"Restarting,omitempty" yaml:"Restarting,omitempty" toml:"Restarting,omitempty"` + OOMKilled bool `json:"OOMKilled,omitempty" yaml:"OOMKilled,omitempty" toml:"OOMKilled,omitempty"` + RemovalInProgress bool `json:"RemovalInProgress,omitempty" yaml:"RemovalInProgress,omitempty" toml:"RemovalInProgress,omitempty"` + Dead bool `json:"Dead,omitempty" yaml:"Dead,omitempty" toml:"Dead,omitempty"` + Pid int `json:"Pid,omitempty" yaml:"Pid,omitempty" toml:"Pid,omitempty"` + ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` + Error string `json:"Error,omitempty" yaml:"Error,omitempty" toml:"Error,omitempty"` + StartedAt time.Time `json:"StartedAt,omitempty" yaml:"StartedAt,omitempty" toml:"StartedAt,omitempty"` + FinishedAt time.Time `json:"FinishedAt,omitempty" yaml:"FinishedAt,omitempty" toml:"FinishedAt,omitempty"` + Health Health `json:"Health,omitempty" yaml:"Health,omitempty" toml:"Health,omitempty"` +} + +// String returns a human-readable description of the state +func (s *State) String() string { + if s.Running { + if s.Paused { + return fmt.Sprintf("Up %s (Paused)", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + } + if s.Restarting { + return fmt.Sprintf("Restarting (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) + } + + return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) + } + + if s.RemovalInProgress { + return "Removal In Progress" + } + + if s.Dead { + return "Dead" + } + + if s.StartedAt.IsZero() { + return "Created" + } + + if s.FinishedAt.IsZero() { + return "" + } + + return fmt.Sprintf("Exited (%d) %s ago", s.ExitCode, units.HumanDuration(time.Now().UTC().Sub(s.FinishedAt))) +} + +// StateString returns a single string to describe state +func (s *State) StateString() string { + if s.Running { + if s.Paused { + return "paused" + } + if s.Restarting { + return "restarting" + } + return "running" + } + + if s.Dead { + return "dead" + } + + if s.StartedAt.IsZero() { + return "created" + } + + return "exited" +} + +// PortBinding represents the host/container port mapping as returned in the +// `docker inspect` json +type PortBinding struct { + HostIP string `json:"HostIP,omitempty" yaml:"HostIP,omitempty" toml:"HostIP,omitempty"` + HostPort string `json:"HostPort,omitempty" yaml:"HostPort,omitempty" toml:"HostPort,omitempty"` +} + +// PortMapping represents a deprecated field in the `docker inspect` output, +// and its value as found in NetworkSettings should always be nil +type PortMapping map[string]string + +// ContainerNetwork represents the networking settings of a container per network. +type ContainerNetwork struct { + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"` + GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"` + IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"` + IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"` + IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"` + Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"` + EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"` + NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"` +} + +// NetworkSettings contains network-related information about a container +type NetworkSettings struct { + Networks map[string]ContainerNetwork `json:"Networks,omitempty" yaml:"Networks,omitempty" toml:"Networks,omitempty"` + IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"` + IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` + Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"` + Bridge string `json:"Bridge,omitempty" yaml:"Bridge,omitempty" toml:"Bridge,omitempty"` + PortMapping map[string]PortMapping `json:"PortMapping,omitempty" yaml:"PortMapping,omitempty" toml:"PortMapping,omitempty"` + Ports map[Port][]PortBinding `json:"Ports,omitempty" yaml:"Ports,omitempty" toml:"Ports,omitempty"` + NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"` + EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"` + SandboxKey string `json:"SandboxKey,omitempty" yaml:"SandboxKey,omitempty" toml:"SandboxKey,omitempty"` + GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"` + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"` + IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"` + LinkLocalIPv6Address string `json:"LinkLocalIPv6Address,omitempty" yaml:"LinkLocalIPv6Address,omitempty" toml:"LinkLocalIPv6Address,omitempty"` + LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen,omitempty" yaml:"LinkLocalIPv6PrefixLen,omitempty" toml:"LinkLocalIPv6PrefixLen,omitempty"` + SecondaryIPAddresses []string `json:"SecondaryIPAddresses,omitempty" yaml:"SecondaryIPAddresses,omitempty" toml:"SecondaryIPAddresses,omitempty"` + SecondaryIPv6Addresses []string `json:"SecondaryIPv6Addresses,omitempty" yaml:"SecondaryIPv6Addresses,omitempty" toml:"SecondaryIPv6Addresses,omitempty"` +} + +// PortMappingAPI translates the port mappings as contained in NetworkSettings +// into the format in which they would appear when returned by the API +func (settings *NetworkSettings) PortMappingAPI() []APIPort { + var mapping []APIPort + for port, bindings := range settings.Ports { + p, _ := parsePort(port.Port()) + if len(bindings) == 0 { + mapping = append(mapping, APIPort{ + PrivatePort: int64(p), + Type: port.Proto(), + }) + continue + } + for _, binding := range bindings { + p, _ := parsePort(port.Port()) + h, _ := parsePort(binding.HostPort) + mapping = append(mapping, APIPort{ + PrivatePort: int64(p), + PublicPort: int64(h), + Type: port.Proto(), + IP: binding.HostIP, + }) + } + } + return mapping +} + +func parsePort(rawPort string) (int, error) { + port, err := strconv.ParseUint(rawPort, 10, 16) + if err != nil { + return 0, err + } + return int(port), nil +} + +// Config is the list of configuration options used when creating a container. +// Config does not contain the options that are specific to starting a container on a +// given host. Those are contained in HostConfig +type Config struct { + Hostname string `json:"Hostname,omitempty" yaml:"Hostname,omitempty" toml:"Hostname,omitempty"` + Domainname string `json:"Domainname,omitempty" yaml:"Domainname,omitempty" toml:"Domainname,omitempty"` + User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` + Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"` + MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty" toml:"MemorySwap,omitempty"` + MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty" toml:"MemoryReservation,omitempty"` + KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty" toml:"KernelMemory,omitempty"` + CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty" toml:"CpuShares,omitempty"` + CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty" toml:"Cpuset,omitempty"` + PortSpecs []string `json:"PortSpecs,omitempty" yaml:"PortSpecs,omitempty" toml:"PortSpecs,omitempty"` + ExposedPorts map[Port]struct{} `json:"ExposedPorts,omitempty" yaml:"ExposedPorts,omitempty" toml:"ExposedPorts,omitempty"` + PublishService string `json:"PublishService,omitempty" yaml:"PublishService,omitempty" toml:"PublishService,omitempty"` + StopSignal string `json:"StopSignal,omitempty" yaml:"StopSignal,omitempty" toml:"StopSignal,omitempty"` + Env []string `json:"Env,omitempty" yaml:"Env,omitempty" toml:"Env,omitempty"` + Cmd []string `json:"Cmd" yaml:"Cmd" toml:"Cmd"` + Healthcheck *HealthConfig `json:"Healthcheck,omitempty" yaml:"Healthcheck,omitempty" toml:"Healthcheck,omitempty"` + DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty" toml:"Dns,omitempty"` // For Docker API v1.9 and below only + Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` + Volumes map[string]struct{} `json:"Volumes,omitempty" yaml:"Volumes,omitempty" toml:"Volumes,omitempty"` + VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty" toml:"VolumeDriver,omitempty"` + WorkingDir string `json:"WorkingDir,omitempty" yaml:"WorkingDir,omitempty" toml:"WorkingDir,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` + Entrypoint []string `json:"Entrypoint" yaml:"Entrypoint" toml:"Entrypoint"` + SecurityOpts []string `json:"SecurityOpts,omitempty" yaml:"SecurityOpts,omitempty" toml:"SecurityOpts,omitempty"` + OnBuild []string `json:"OnBuild,omitempty" yaml:"OnBuild,omitempty" toml:"OnBuild,omitempty"` + Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` + AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"` + AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"` + AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"` + ArgsEscaped bool `json:"ArgsEscaped,omitempty" yaml:"ArgsEscaped,omitempty" toml:"ArgsEscaped,omitempty"` + Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` + OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"` + StdinOnce bool `json:"StdinOnce,omitempty" yaml:"StdinOnce,omitempty" toml:"StdinOnce,omitempty"` + NetworkDisabled bool `json:"NetworkDisabled,omitempty" yaml:"NetworkDisabled,omitempty" toml:"NetworkDisabled,omitempty"` + + // This is no longer used and has been kept here for backward + // compatibility, please use HostConfig.VolumesFrom. + VolumesFrom string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty" toml:"VolumesFrom,omitempty"` +} + +// Mount represents a mount point in the container. +// +// It has been added in the version 1.20 of the Docker API, available since +// Docker 1.8. +type Mount struct { + Name string + Source string + Destination string + Driver string + Mode string + RW bool +} + +// LogConfig defines the log driver type and the configuration for it. +type LogConfig struct { + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` + Config map[string]string `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` +} + +// ULimit defines system-wide resource limitations This can help a lot in +// system administration, e.g. when a user starts too many processes and +// therefore makes the system unresponsive for other users. +type ULimit struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Soft int64 `json:"Soft,omitempty" yaml:"Soft,omitempty" toml:"Soft,omitempty"` + Hard int64 `json:"Hard,omitempty" yaml:"Hard,omitempty" toml:"Hard,omitempty"` +} + +// SwarmNode containers information about which Swarm node the container is on. +type SwarmNode struct { + ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"` + IP string `json:"IP,omitempty" yaml:"IP,omitempty" toml:"IP,omitempty"` + Addr string `json:"Addr,omitempty" yaml:"Addr,omitempty" toml:"Addr,omitempty"` + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + CPUs int64 `json:"CPUs,omitempty" yaml:"CPUs,omitempty" toml:"CPUs,omitempty"` + Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` +} + +// GraphDriver contains information about the GraphDriver used by the +// container. +type GraphDriver struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Data map[string]string `json:"Data,omitempty" yaml:"Data,omitempty" toml:"Data,omitempty"` +} + +// HealthConfig holds configuration settings for the HEALTHCHECK feature +// +// It has been added in the version 1.24 of the Docker API, available since +// Docker 1.12. +type HealthConfig struct { + // Test is the test to perform to check that the container is healthy. + // An empty slice means to inherit the default. + // The options are: + // {} : inherit healthcheck + // {"NONE"} : disable healthcheck + // {"CMD", args...} : exec arguments directly + // {"CMD-SHELL", command} : run command with system's default shell + Test []string `json:"Test,omitempty" yaml:"Test,omitempty" toml:"Test,omitempty"` + + // Zero means to inherit. Durations are expressed as integer nanoseconds. + Interval time.Duration `json:"Interval,omitempty" yaml:"Interval,omitempty" toml:"Interval,omitempty"` // Interval is the time to wait between checks. + Timeout time.Duration `json:"Timeout,omitempty" yaml:"Timeout,omitempty" toml:"Timeout,omitempty"` // Timeout is the time to wait before considering the check to have hung. + + // Retries is the number of consecutive failures needed to consider a container as unhealthy. + // Zero means inherit. + Retries int `json:"Retries,omitempty" yaml:"Retries,omitempty" toml:"Retries,omitempty"` +} + +// Container is the type encompasing everything about a container - its config, +// hostconfig, etc. +type Container struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + + Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` + + Path string `json:"Path,omitempty" yaml:"Path,omitempty" toml:"Path,omitempty"` + Args []string `json:"Args,omitempty" yaml:"Args,omitempty" toml:"Args,omitempty"` + + Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` + State State `json:"State,omitempty" yaml:"State,omitempty" toml:"State,omitempty"` + Image string `json:"Image,omitempty" yaml:"Image,omitempty" toml:"Image,omitempty"` + + Node *SwarmNode `json:"Node,omitempty" yaml:"Node,omitempty" toml:"Node,omitempty"` + + NetworkSettings *NetworkSettings `json:"NetworkSettings,omitempty" yaml:"NetworkSettings,omitempty" toml:"NetworkSettings,omitempty"` + + SysInitPath string `json:"SysInitPath,omitempty" yaml:"SysInitPath,omitempty" toml:"SysInitPath,omitempty"` + ResolvConfPath string `json:"ResolvConfPath,omitempty" yaml:"ResolvConfPath,omitempty" toml:"ResolvConfPath,omitempty"` + HostnamePath string `json:"HostnamePath,omitempty" yaml:"HostnamePath,omitempty" toml:"HostnamePath,omitempty"` + HostsPath string `json:"HostsPath,omitempty" yaml:"HostsPath,omitempty" toml:"HostsPath,omitempty"` + LogPath string `json:"LogPath,omitempty" yaml:"LogPath,omitempty" toml:"LogPath,omitempty"` + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` + Mounts []Mount `json:"Mounts,omitempty" yaml:"Mounts,omitempty" toml:"Mounts,omitempty"` + + Volumes map[string]string `json:"Volumes,omitempty" yaml:"Volumes,omitempty" toml:"Volumes,omitempty"` + VolumesRW map[string]bool `json:"VolumesRW,omitempty" yaml:"VolumesRW,omitempty" toml:"VolumesRW,omitempty"` + HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty" toml:"HostConfig,omitempty"` + ExecIDs []string `json:"ExecIDs,omitempty" yaml:"ExecIDs,omitempty" toml:"ExecIDs,omitempty"` + GraphDriver *GraphDriver `json:"GraphDriver,omitempty" yaml:"GraphDriver,omitempty" toml:"GraphDriver,omitempty"` + + RestartCount int `json:"RestartCount,omitempty" yaml:"RestartCount,omitempty" toml:"RestartCount,omitempty"` + + AppArmorProfile string `json:"AppArmorProfile,omitempty" yaml:"AppArmorProfile,omitempty" toml:"AppArmorProfile,omitempty"` +} + +// UpdateContainerOptions specify parameters to the UpdateContainer function. +// +// See https://goo.gl/Y6fXUy for more details. +type UpdateContainerOptions struct { + BlkioWeight int `json:"BlkioWeight"` + CPUShares int `json:"CpuShares"` + CPUPeriod int `json:"CpuPeriod"` + CPURealtimePeriod int64 `json:"CpuRealtimePeriod"` + CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime"` + CPUQuota int `json:"CpuQuota"` + CpusetCpus string `json:"CpusetCpus"` + CpusetMems string `json:"CpusetMems"` + Memory int `json:"Memory"` + MemorySwap int `json:"MemorySwap"` + MemoryReservation int `json:"MemoryReservation"` + KernelMemory int `json:"KernelMemory"` + RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty"` + Context context.Context +} + +// UpdateContainer updates the container at ID with the options +// +// See https://goo.gl/Y6fXUy for more details. +func (c *Client) UpdateContainer(id string, opts UpdateContainerOptions) error { + resp, err := c.do("POST", fmt.Sprintf("/containers/"+id+"/update"), doOptions{ + data: opts, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + return err + } + defer resp.Body.Close() + return nil +} + +// RenameContainerOptions specify parameters to the RenameContainer function. +// +// See https://goo.gl/46inai for more details. +type RenameContainerOptions struct { + // ID of container to rename + ID string `qs:"-"` + + // New name + Name string `json:"name,omitempty" yaml:"name,omitempty"` + Context context.Context +} + +// RenameContainer updates and existing containers name +// +// See https://goo.gl/46inai for more details. +func (c *Client) RenameContainer(opts RenameContainerOptions) error { + resp, err := c.do("POST", fmt.Sprintf("/containers/"+opts.ID+"/rename?%s", queryString(opts)), doOptions{ + context: opts.Context, + }) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + +// InspectContainer returns information about a container by its ID. +// +// See https://goo.gl/FaI5JT for more details. +func (c *Client) InspectContainer(id string) (*Container, error) { + return c.inspectContainer(id, doOptions{}) +} + +// InspectContainerWithContext returns information about a container by its ID. +// The context object can be used to cancel the inspect request. +// +// See https://goo.gl/FaI5JT for more details. +func (c *Client) InspectContainerWithContext(id string, ctx context.Context) (*Container, error) { + return c.inspectContainer(id, doOptions{context: ctx}) +} + +func (c *Client) inspectContainer(id string, opts doOptions) (*Container, error) { + path := "/containers/" + id + "/json" + resp, err := c.do("GET", path, opts) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var container Container + if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { + return nil, err + } + return &container, nil +} + +// ContainerChanges returns changes in the filesystem of the given container. +// +// See https://goo.gl/15KKzh for more details. +func (c *Client) ContainerChanges(id string) ([]Change, error) { + path := "/containers/" + id + "/changes" + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var changes []Change + if err := json.NewDecoder(resp.Body).Decode(&changes); err != nil { + return nil, err + } + return changes, nil +} + +// CreateContainerOptions specify parameters to the CreateContainer function. +// +// See https://goo.gl/tyzwVM for more details. +type CreateContainerOptions struct { + Name string + Config *Config `qs:"-"` + HostConfig *HostConfig `qs:"-"` + NetworkingConfig *NetworkingConfig `qs:"-"` + Context context.Context +} + +// CreateContainer creates a new container, returning the container instance, +// or an error in case of failure. +// +// The returned container instance contains only the container ID. To get more +// details about the container after creating it, use InspectContainer. +// +// See https://goo.gl/tyzwVM for more details. +func (c *Client) CreateContainer(opts CreateContainerOptions) (*Container, error) { + path := "/containers/create?" + queryString(opts) + resp, err := c.do( + "POST", + path, + doOptions{ + data: struct { + *Config + HostConfig *HostConfig `json:"HostConfig,omitempty" yaml:"HostConfig,omitempty" toml:"HostConfig,omitempty"` + NetworkingConfig *NetworkingConfig `json:"NetworkingConfig,omitempty" yaml:"NetworkingConfig,omitempty" toml:"NetworkingConfig,omitempty"` + }{ + opts.Config, + opts.HostConfig, + opts.NetworkingConfig, + }, + context: opts.Context, + }, + ) + + if e, ok := err.(*Error); ok { + if e.Status == http.StatusNotFound { + return nil, ErrNoSuchImage + } + if e.Status == http.StatusConflict { + return nil, ErrContainerAlreadyExists + } + } + + if err != nil { + return nil, err + } + defer resp.Body.Close() + var container Container + if err := json.NewDecoder(resp.Body).Decode(&container); err != nil { + return nil, err + } + + container.Name = opts.Name + + return &container, nil +} + +// KeyValuePair is a type for generic key/value pairs as used in the Lxc +// configuration +type KeyValuePair struct { + Key string `json:"Key,omitempty" yaml:"Key,omitempty" toml:"Key,omitempty"` + Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// RestartPolicy represents the policy for automatically restarting a container. +// +// Possible values are: +// +// - always: the docker daemon will always restart the container +// - on-failure: the docker daemon will restart the container on failures, at +// most MaximumRetryCount times +// - unless-stopped: the docker daemon will always restart the container except +// when user has manually stopped the container +// - no: the docker daemon will not restart the container automatically +type RestartPolicy struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + MaximumRetryCount int `json:"MaximumRetryCount,omitempty" yaml:"MaximumRetryCount,omitempty" toml:"MaximumRetryCount,omitempty"` +} + +// AlwaysRestart returns a restart policy that tells the Docker daemon to +// always restart the container. +func AlwaysRestart() RestartPolicy { + return RestartPolicy{Name: "always"} +} + +// RestartOnFailure returns a restart policy that tells the Docker daemon to +// restart the container on failures, trying at most maxRetry times. +func RestartOnFailure(maxRetry int) RestartPolicy { + return RestartPolicy{Name: "on-failure", MaximumRetryCount: maxRetry} +} + +// RestartUnlessStopped returns a restart policy that tells the Docker daemon to +// always restart the container except when user has manually stopped the container. +func RestartUnlessStopped() RestartPolicy { + return RestartPolicy{Name: "unless-stopped"} +} + +// NeverRestart returns a restart policy that tells the Docker daemon to never +// restart the container on failures. +func NeverRestart() RestartPolicy { + return RestartPolicy{Name: "no"} +} + +// Device represents a device mapping between the Docker host and the +// container. +type Device struct { + PathOnHost string `json:"PathOnHost,omitempty" yaml:"PathOnHost,omitempty" toml:"PathOnHost,omitempty"` + PathInContainer string `json:"PathInContainer,omitempty" yaml:"PathInContainer,omitempty" toml:"PathInContainer,omitempty"` + CgroupPermissions string `json:"CgroupPermissions,omitempty" yaml:"CgroupPermissions,omitempty" toml:"CgroupPermissions,omitempty"` +} + +// BlockWeight represents a relative device weight for an individual device inside +// of a container +type BlockWeight struct { + Path string `json:"Path,omitempty"` + Weight string `json:"Weight,omitempty"` +} + +// BlockLimit represents a read/write limit in IOPS or Bandwidth for a device +// inside of a container +type BlockLimit struct { + Path string `json:"Path,omitempty"` + Rate int64 `json:"Rate,omitempty"` +} + +// HostConfig contains the container options related to starting a container on +// a given host +type HostConfig struct { + Binds []string `json:"Binds,omitempty" yaml:"Binds,omitempty" toml:"Binds,omitempty"` + CapAdd []string `json:"CapAdd,omitempty" yaml:"CapAdd,omitempty" toml:"CapAdd,omitempty"` + CapDrop []string `json:"CapDrop,omitempty" yaml:"CapDrop,omitempty" toml:"CapDrop,omitempty"` + GroupAdd []string `json:"GroupAdd,omitempty" yaml:"GroupAdd,omitempty" toml:"GroupAdd,omitempty"` + ContainerIDFile string `json:"ContainerIDFile,omitempty" yaml:"ContainerIDFile,omitempty" toml:"ContainerIDFile,omitempty"` + LxcConf []KeyValuePair `json:"LxcConf,omitempty" yaml:"LxcConf,omitempty" toml:"LxcConf,omitempty"` + PortBindings map[Port][]PortBinding `json:"PortBindings,omitempty" yaml:"PortBindings,omitempty" toml:"PortBindings,omitempty"` + Links []string `json:"Links,omitempty" yaml:"Links,omitempty" toml:"Links,omitempty"` + DNS []string `json:"Dns,omitempty" yaml:"Dns,omitempty" toml:"Dns,omitempty"` // For Docker API v1.10 and above only + DNSOptions []string `json:"DnsOptions,omitempty" yaml:"DnsOptions,omitempty" toml:"DnsOptions,omitempty"` + DNSSearch []string `json:"DnsSearch,omitempty" yaml:"DnsSearch,omitempty" toml:"DnsSearch,omitempty"` + ExtraHosts []string `json:"ExtraHosts,omitempty" yaml:"ExtraHosts,omitempty" toml:"ExtraHosts,omitempty"` + VolumesFrom []string `json:"VolumesFrom,omitempty" yaml:"VolumesFrom,omitempty" toml:"VolumesFrom,omitempty"` + UsernsMode string `json:"UsernsMode,omitempty" yaml:"UsernsMode,omitempty" toml:"UsernsMode,omitempty"` + NetworkMode string `json:"NetworkMode,omitempty" yaml:"NetworkMode,omitempty" toml:"NetworkMode,omitempty"` + IpcMode string `json:"IpcMode,omitempty" yaml:"IpcMode,omitempty" toml:"IpcMode,omitempty"` + PidMode string `json:"PidMode,omitempty" yaml:"PidMode,omitempty" toml:"PidMode,omitempty"` + UTSMode string `json:"UTSMode,omitempty" yaml:"UTSMode,omitempty" toml:"UTSMode,omitempty"` + RestartPolicy RestartPolicy `json:"RestartPolicy,omitempty" yaml:"RestartPolicy,omitempty" toml:"RestartPolicy,omitempty"` + Devices []Device `json:"Devices,omitempty" yaml:"Devices,omitempty" toml:"Devices,omitempty"` + LogConfig LogConfig `json:"LogConfig,omitempty" yaml:"LogConfig,omitempty" toml:"LogConfig,omitempty"` + SecurityOpt []string `json:"SecurityOpt,omitempty" yaml:"SecurityOpt,omitempty" toml:"SecurityOpt,omitempty"` + Cgroup string `json:"Cgroup,omitempty" yaml:"Cgroup,omitempty" toml:"Cgroup,omitempty"` + CgroupParent string `json:"CgroupParent,omitempty" yaml:"CgroupParent,omitempty" toml:"CgroupParent,omitempty"` + Memory int64 `json:"Memory,omitempty" yaml:"Memory,omitempty" toml:"Memory,omitempty"` + MemoryReservation int64 `json:"MemoryReservation,omitempty" yaml:"MemoryReservation,omitempty" toml:"MemoryReservation,omitempty"` + KernelMemory int64 `json:"KernelMemory,omitempty" yaml:"KernelMemory,omitempty" toml:"KernelMemory,omitempty"` + MemorySwap int64 `json:"MemorySwap,omitempty" yaml:"MemorySwap,omitempty" toml:"MemorySwap,omitempty"` + MemorySwappiness int64 `json:"MemorySwappiness,omitempty" yaml:"MemorySwappiness,omitempty" toml:"MemorySwappiness,omitempty"` + CPUShares int64 `json:"CpuShares,omitempty" yaml:"CpuShares,omitempty" toml:"CpuShares,omitempty"` + CPUSet string `json:"Cpuset,omitempty" yaml:"Cpuset,omitempty" toml:"Cpuset,omitempty"` + CPUSetCPUs string `json:"CpusetCpus,omitempty" yaml:"CpusetCpus,omitempty" toml:"CpusetCpus,omitempty"` + CPUSetMEMs string `json:"CpusetMems,omitempty" yaml:"CpusetMems,omitempty" toml:"CpusetMems,omitempty"` + CPUQuota int64 `json:"CpuQuota,omitempty" yaml:"CpuQuota,omitempty" toml:"CpuQuota,omitempty"` + CPUPeriod int64 `json:"CpuPeriod,omitempty" yaml:"CpuPeriod,omitempty" toml:"CpuPeriod,omitempty"` + CPURealtimePeriod int64 `json:"CpuRealtimePeriod,omitempty" yaml:"CpuRealtimePeriod,omitempty" toml:"CpuRealtimePeriod,omitempty"` + CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime,omitempty" yaml:"CpuRealtimeRuntime,omitempty" toml:"CpuRealtimeRuntime,omitempty"` + BlkioWeight int64 `json:"BlkioWeight,omitempty" yaml:"BlkioWeight,omitempty" toml:"BlkioWeight,omitempty"` + BlkioWeightDevice []BlockWeight `json:"BlkioWeightDevice,omitempty" yaml:"BlkioWeightDevice,omitempty" toml:"BlkioWeightDevice,omitempty"` + BlkioDeviceReadBps []BlockLimit `json:"BlkioDeviceReadBps,omitempty" yaml:"BlkioDeviceReadBps,omitempty" toml:"BlkioDeviceReadBps,omitempty"` + BlkioDeviceReadIOps []BlockLimit `json:"BlkioDeviceReadIOps,omitempty" yaml:"BlkioDeviceReadIOps,omitempty" toml:"BlkioDeviceReadIOps,omitempty"` + BlkioDeviceWriteBps []BlockLimit `json:"BlkioDeviceWriteBps,omitempty" yaml:"BlkioDeviceWriteBps,omitempty" toml:"BlkioDeviceWriteBps,omitempty"` + BlkioDeviceWriteIOps []BlockLimit `json:"BlkioDeviceWriteIOps,omitempty" yaml:"BlkioDeviceWriteIOps,omitempty" toml:"BlkioDeviceWriteIOps,omitempty"` + Ulimits []ULimit `json:"Ulimits,omitempty" yaml:"Ulimits,omitempty" toml:"Ulimits,omitempty"` + VolumeDriver string `json:"VolumeDriver,omitempty" yaml:"VolumeDriver,omitempty" toml:"VolumeDriver,omitempty"` + OomScoreAdj int `json:"OomScoreAdj,omitempty" yaml:"OomScoreAdj,omitempty" toml:"OomScoreAdj,omitempty"` + PidsLimit int64 `json:"PidsLimit,omitempty" yaml:"PidsLimit,omitempty" toml:"PidsLimit,omitempty"` + ShmSize int64 `json:"ShmSize,omitempty" yaml:"ShmSize,omitempty" toml:"ShmSize,omitempty"` + Tmpfs map[string]string `json:"Tmpfs,omitempty" yaml:"Tmpfs,omitempty" toml:"Tmpfs,omitempty"` + Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"` + PublishAllPorts bool `json:"PublishAllPorts,omitempty" yaml:"PublishAllPorts,omitempty" toml:"PublishAllPorts,omitempty"` + ReadonlyRootfs bool `json:"ReadonlyRootfs,omitempty" yaml:"ReadonlyRootfs,omitempty" toml:"ReadonlyRootfs,omitempty"` + OOMKillDisable bool `json:"OomKillDisable,omitempty" yaml:"OomKillDisable,omitempty" toml:"OomKillDisable,omitempty"` + AutoRemove bool `json:"AutoRemove,omitempty" yaml:"AutoRemove,omitempty" toml:"AutoRemove,omitempty"` + StorageOpt map[string]string `json:"StorageOpt,omitempty" yaml:"StorageOpt,omitempty" toml:"StorageOpt,omitempty"` + Sysctls map[string]string `json:"Sysctls,omitempty" yaml:"Sysctls,omitempty" toml:"Sysctls,omitempty"` +} + +// NetworkingConfig represents the container's networking configuration for each of its interfaces +// Carries the networking configs specified in the `docker run` and `docker network connect` commands +type NetworkingConfig struct { + EndpointsConfig map[string]*EndpointConfig `json:"EndpointsConfig" yaml:"EndpointsConfig" toml:"EndpointsConfig"` // Endpoint configs for each connecting network +} + +// StartContainer starts a container, returning an error in case of failure. +// +// Passing the HostConfig to this method has been deprecated in Docker API 1.22 +// (Docker Engine 1.10.x) and totally removed in Docker API 1.24 (Docker Engine +// 1.12.x). The client will ignore the parameter when communicating with Docker +// API 1.24 or greater. +// +// See https://goo.gl/fbOSZy for more details. +func (c *Client) StartContainer(id string, hostConfig *HostConfig) error { + return c.startContainer(id, hostConfig, doOptions{}) +} + +// StartContainerWithContext starts a container, returning an error in case of +// failure. The context can be used to cancel the outstanding start container +// request. +// +// Passing the HostConfig to this method has been deprecated in Docker API 1.22 +// (Docker Engine 1.10.x) and totally removed in Docker API 1.24 (Docker Engine +// 1.12.x). The client will ignore the parameter when communicating with Docker +// API 1.24 or greater. +// +// See https://goo.gl/fbOSZy for more details. +func (c *Client) StartContainerWithContext(id string, hostConfig *HostConfig, ctx context.Context) error { + return c.startContainer(id, hostConfig, doOptions{context: ctx}) +} + +func (c *Client) startContainer(id string, hostConfig *HostConfig, opts doOptions) error { + path := "/containers/" + id + "/start" + if c.serverAPIVersion == nil { + c.checkAPIVersion() + } + if c.serverAPIVersion != nil && c.serverAPIVersion.LessThan(apiVersion124) { + opts.data = hostConfig + opts.forceJSON = true + } + resp, err := c.do("POST", path, opts) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id, Err: err} + } + return err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNotModified { + return &ContainerAlreadyRunning{ID: id} + } + return nil +} + +// StopContainer stops a container, killing it after the given timeout (in +// seconds). +// +// See https://goo.gl/R9dZcV for more details. +func (c *Client) StopContainer(id string, timeout uint) error { + return c.stopContainer(id, timeout, doOptions{}) +} + +// StopContainerWithContext stops a container, killing it after the given +// timeout (in seconds). The context can be used to cancel the stop +// container request. +// +// See https://goo.gl/R9dZcV for more details. +func (c *Client) StopContainerWithContext(id string, timeout uint, ctx context.Context) error { + return c.stopContainer(id, timeout, doOptions{context: ctx}) +} + +func (c *Client) stopContainer(id string, timeout uint, opts doOptions) error { + path := fmt.Sprintf("/containers/%s/stop?t=%d", id, timeout) + resp, err := c.do("POST", path, opts) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + return err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusNotModified { + return &ContainerNotRunning{ID: id} + } + return nil +} + +// RestartContainer stops a container, killing it after the given timeout (in +// seconds), during the stop process. +// +// See https://goo.gl/MrAKQ5 for more details. +func (c *Client) RestartContainer(id string, timeout uint) error { + path := fmt.Sprintf("/containers/%s/restart?t=%d", id, timeout) + resp, err := c.do("POST", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + return err + } + resp.Body.Close() + return nil +} + +// PauseContainer pauses the given container. +// +// See https://goo.gl/D1Yaii for more details. +func (c *Client) PauseContainer(id string) error { + path := fmt.Sprintf("/containers/%s/pause", id) + resp, err := c.do("POST", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + return err + } + resp.Body.Close() + return nil +} + +// UnpauseContainer unpauses the given container. +// +// See https://goo.gl/sZ2faO for more details. +func (c *Client) UnpauseContainer(id string) error { + path := fmt.Sprintf("/containers/%s/unpause", id) + resp, err := c.do("POST", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: id} + } + return err + } + resp.Body.Close() + return nil +} + +// TopResult represents the list of processes running in a container, as +// returned by /containers//top. +// +// See https://goo.gl/FLwpPl for more details. +type TopResult struct { + Titles []string + Processes [][]string +} + +// TopContainer returns processes running inside a container +// +// See https://goo.gl/FLwpPl for more details. +func (c *Client) TopContainer(id string, psArgs string) (TopResult, error) { + var args string + var result TopResult + if psArgs != "" { + args = fmt.Sprintf("?ps_args=%s", psArgs) + } + path := fmt.Sprintf("/containers/%s/top%s", id, args) + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return result, &NoSuchContainer{ID: id} + } + return result, err + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&result) + return result, err +} + +// Stats represents container statistics, returned by /containers//stats. +// +// See https://goo.gl/Dk3Xio for more details. +type Stats struct { + Read time.Time `json:"read,omitempty" yaml:"read,omitempty" toml:"read,omitempty"` + PidsStats struct { + Current uint64 `json:"current,omitempty" yaml:"current,omitempty"` + } `json:"pids_stats,omitempty" yaml:"pids_stats,omitempty" toml:"pids_stats,omitempty"` + Network NetworkStats `json:"network,omitempty" yaml:"network,omitempty" toml:"network,omitempty"` + Networks map[string]NetworkStats `json:"networks,omitempty" yaml:"networks,omitempty" toml:"networks,omitempty"` + MemoryStats struct { + Stats struct { + TotalPgmafault uint64 `json:"total_pgmafault,omitempty" yaml:"total_pgmafault,omitempty" toml:"total_pgmafault,omitempty"` + Cache uint64 `json:"cache,omitempty" yaml:"cache,omitempty" toml:"cache,omitempty"` + MappedFile uint64 `json:"mapped_file,omitempty" yaml:"mapped_file,omitempty" toml:"mapped_file,omitempty"` + TotalInactiveFile uint64 `json:"total_inactive_file,omitempty" yaml:"total_inactive_file,omitempty" toml:"total_inactive_file,omitempty"` + Pgpgout uint64 `json:"pgpgout,omitempty" yaml:"pgpgout,omitempty" toml:"pgpgout,omitempty"` + Rss uint64 `json:"rss,omitempty" yaml:"rss,omitempty" toml:"rss,omitempty"` + TotalMappedFile uint64 `json:"total_mapped_file,omitempty" yaml:"total_mapped_file,omitempty" toml:"total_mapped_file,omitempty"` + Writeback uint64 `json:"writeback,omitempty" yaml:"writeback,omitempty" toml:"writeback,omitempty"` + Unevictable uint64 `json:"unevictable,omitempty" yaml:"unevictable,omitempty" toml:"unevictable,omitempty"` + Pgpgin uint64 `json:"pgpgin,omitempty" yaml:"pgpgin,omitempty" toml:"pgpgin,omitempty"` + TotalUnevictable uint64 `json:"total_unevictable,omitempty" yaml:"total_unevictable,omitempty" toml:"total_unevictable,omitempty"` + Pgmajfault uint64 `json:"pgmajfault,omitempty" yaml:"pgmajfault,omitempty" toml:"pgmajfault,omitempty"` + TotalRss uint64 `json:"total_rss,omitempty" yaml:"total_rss,omitempty" toml:"total_rss,omitempty"` + TotalRssHuge uint64 `json:"total_rss_huge,omitempty" yaml:"total_rss_huge,omitempty" toml:"total_rss_huge,omitempty"` + TotalWriteback uint64 `json:"total_writeback,omitempty" yaml:"total_writeback,omitempty" toml:"total_writeback,omitempty"` + TotalInactiveAnon uint64 `json:"total_inactive_anon,omitempty" yaml:"total_inactive_anon,omitempty" toml:"total_inactive_anon,omitempty"` + RssHuge uint64 `json:"rss_huge,omitempty" yaml:"rss_huge,omitempty" toml:"rss_huge,omitempty"` + HierarchicalMemoryLimit uint64 `json:"hierarchical_memory_limit,omitempty" yaml:"hierarchical_memory_limit,omitempty" toml:"hierarchical_memory_limit,omitempty"` + TotalPgfault uint64 `json:"total_pgfault,omitempty" yaml:"total_pgfault,omitempty" toml:"total_pgfault,omitempty"` + TotalActiveFile uint64 `json:"total_active_file,omitempty" yaml:"total_active_file,omitempty" toml:"total_active_file,omitempty"` + ActiveAnon uint64 `json:"active_anon,omitempty" yaml:"active_anon,omitempty" toml:"active_anon,omitempty"` + TotalActiveAnon uint64 `json:"total_active_anon,omitempty" yaml:"total_active_anon,omitempty" toml:"total_active_anon,omitempty"` + TotalPgpgout uint64 `json:"total_pgpgout,omitempty" yaml:"total_pgpgout,omitempty" toml:"total_pgpgout,omitempty"` + TotalCache uint64 `json:"total_cache,omitempty" yaml:"total_cache,omitempty" toml:"total_cache,omitempty"` + InactiveAnon uint64 `json:"inactive_anon,omitempty" yaml:"inactive_anon,omitempty" toml:"inactive_anon,omitempty"` + ActiveFile uint64 `json:"active_file,omitempty" yaml:"active_file,omitempty" toml:"active_file,omitempty"` + Pgfault uint64 `json:"pgfault,omitempty" yaml:"pgfault,omitempty" toml:"pgfault,omitempty"` + InactiveFile uint64 `json:"inactive_file,omitempty" yaml:"inactive_file,omitempty" toml:"inactive_file,omitempty"` + TotalPgpgin uint64 `json:"total_pgpgin,omitempty" yaml:"total_pgpgin,omitempty" toml:"total_pgpgin,omitempty"` + HierarchicalMemswLimit uint64 `json:"hierarchical_memsw_limit,omitempty" yaml:"hierarchical_memsw_limit,omitempty" toml:"hierarchical_memsw_limit,omitempty"` + Swap uint64 `json:"swap,omitempty" yaml:"swap,omitempty" toml:"swap,omitempty"` + } `json:"stats,omitempty" yaml:"stats,omitempty" toml:"stats,omitempty"` + MaxUsage uint64 `json:"max_usage,omitempty" yaml:"max_usage,omitempty" toml:"max_usage,omitempty"` + Usage uint64 `json:"usage,omitempty" yaml:"usage,omitempty" toml:"usage,omitempty"` + Failcnt uint64 `json:"failcnt,omitempty" yaml:"failcnt,omitempty" toml:"failcnt,omitempty"` + Limit uint64 `json:"limit,omitempty" yaml:"limit,omitempty" toml:"limit,omitempty"` + } `json:"memory_stats,omitempty" yaml:"memory_stats,omitempty" toml:"memory_stats,omitempty"` + BlkioStats struct { + IOServiceBytesRecursive []BlkioStatsEntry `json:"io_service_bytes_recursive,omitempty" yaml:"io_service_bytes_recursive,omitempty" toml:"io_service_bytes_recursive,omitempty"` + IOServicedRecursive []BlkioStatsEntry `json:"io_serviced_recursive,omitempty" yaml:"io_serviced_recursive,omitempty" toml:"io_serviced_recursive,omitempty"` + IOQueueRecursive []BlkioStatsEntry `json:"io_queue_recursive,omitempty" yaml:"io_queue_recursive,omitempty" toml:"io_queue_recursive,omitempty"` + IOServiceTimeRecursive []BlkioStatsEntry `json:"io_service_time_recursive,omitempty" yaml:"io_service_time_recursive,omitempty" toml:"io_service_time_recursive,omitempty"` + IOWaitTimeRecursive []BlkioStatsEntry `json:"io_wait_time_recursive,omitempty" yaml:"io_wait_time_recursive,omitempty" toml:"io_wait_time_recursive,omitempty"` + IOMergedRecursive []BlkioStatsEntry `json:"io_merged_recursive,omitempty" yaml:"io_merged_recursive,omitempty" toml:"io_merged_recursive,omitempty"` + IOTimeRecursive []BlkioStatsEntry `json:"io_time_recursive,omitempty" yaml:"io_time_recursive,omitempty" toml:"io_time_recursive,omitempty"` + SectorsRecursive []BlkioStatsEntry `json:"sectors_recursive,omitempty" yaml:"sectors_recursive,omitempty" toml:"sectors_recursive,omitempty"` + } `json:"blkio_stats,omitempty" yaml:"blkio_stats,omitempty" toml:"blkio_stats,omitempty"` + CPUStats CPUStats `json:"cpu_stats,omitempty" yaml:"cpu_stats,omitempty" toml:"cpu_stats,omitempty"` + PreCPUStats CPUStats `json:"precpu_stats,omitempty"` +} + +// NetworkStats is a stats entry for network stats +type NetworkStats struct { + RxDropped uint64 `json:"rx_dropped,omitempty" yaml:"rx_dropped,omitempty" toml:"rx_dropped,omitempty"` + RxBytes uint64 `json:"rx_bytes,omitempty" yaml:"rx_bytes,omitempty" toml:"rx_bytes,omitempty"` + RxErrors uint64 `json:"rx_errors,omitempty" yaml:"rx_errors,omitempty" toml:"rx_errors,omitempty"` + TxPackets uint64 `json:"tx_packets,omitempty" yaml:"tx_packets,omitempty" toml:"tx_packets,omitempty"` + TxDropped uint64 `json:"tx_dropped,omitempty" yaml:"tx_dropped,omitempty" toml:"tx_dropped,omitempty"` + RxPackets uint64 `json:"rx_packets,omitempty" yaml:"rx_packets,omitempty" toml:"rx_packets,omitempty"` + TxErrors uint64 `json:"tx_errors,omitempty" yaml:"tx_errors,omitempty" toml:"tx_errors,omitempty"` + TxBytes uint64 `json:"tx_bytes,omitempty" yaml:"tx_bytes,omitempty" toml:"tx_bytes,omitempty"` +} + +// CPUStats is a stats entry for cpu stats +type CPUStats struct { + CPUUsage struct { + PercpuUsage []uint64 `json:"percpu_usage,omitempty" yaml:"percpu_usage,omitempty" toml:"percpu_usage,omitempty"` + UsageInUsermode uint64 `json:"usage_in_usermode,omitempty" yaml:"usage_in_usermode,omitempty" toml:"usage_in_usermode,omitempty"` + TotalUsage uint64 `json:"total_usage,omitempty" yaml:"total_usage,omitempty" toml:"total_usage,omitempty"` + UsageInKernelmode uint64 `json:"usage_in_kernelmode,omitempty" yaml:"usage_in_kernelmode,omitempty" toml:"usage_in_kernelmode,omitempty"` + } `json:"cpu_usage,omitempty" yaml:"cpu_usage,omitempty" toml:"cpu_usage,omitempty"` + SystemCPUUsage uint64 `json:"system_cpu_usage,omitempty" yaml:"system_cpu_usage,omitempty" toml:"system_cpu_usage,omitempty"` + ThrottlingData struct { + Periods uint64 `json:"periods,omitempty"` + ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` + ThrottledTime uint64 `json:"throttled_time,omitempty"` + } `json:"throttling_data,omitempty" yaml:"throttling_data,omitempty" toml:"throttling_data,omitempty"` +} + +// BlkioStatsEntry is a stats entry for blkio_stats +type BlkioStatsEntry struct { + Major uint64 `json:"major,omitempty" yaml:"major,omitempty" toml:"major,omitempty"` + Minor uint64 `json:"minor,omitempty" yaml:"minor,omitempty" toml:"minor,omitempty"` + Op string `json:"op,omitempty" yaml:"op,omitempty" toml:"op,omitempty"` + Value uint64 `json:"value,omitempty" yaml:"value,omitempty" toml:"value,omitempty"` +} + +// StatsOptions specify parameters to the Stats function. +// +// See https://goo.gl/Dk3Xio for more details. +type StatsOptions struct { + ID string + Stats chan<- *Stats + Stream bool + // A flag that enables stopping the stats operation + Done <-chan bool + // Initial connection timeout + Timeout time.Duration + // Timeout with no data is received, it's reset every time new data + // arrives + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// Stats sends container statistics for the given container to the given channel. +// +// This function is blocking, similar to a streaming call for logs, and should be run +// on a separate goroutine from the caller. Note that this function will block until +// the given container is removed, not just exited. When finished, this function +// will close the given channel. Alternatively, function can be stopped by +// signaling on the Done channel. +// +// See https://goo.gl/Dk3Xio for more details. +func (c *Client) Stats(opts StatsOptions) (retErr error) { + errC := make(chan error, 1) + readCloser, writeCloser := io.Pipe() + + defer func() { + close(opts.Stats) + + select { + case err := <-errC: + if err != nil && retErr == nil { + retErr = err + } + default: + // No errors + } + + if err := readCloser.Close(); err != nil && retErr == nil { + retErr = err + } + }() + + go func() { + err := c.stream("GET", fmt.Sprintf("/containers/%s/stats?stream=%v", opts.ID, opts.Stream), streamOptions{ + rawJSONStream: true, + useJSONDecoder: true, + stdout: writeCloser, + timeout: opts.Timeout, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) + if err != nil { + dockerError, ok := err.(*Error) + if ok { + if dockerError.Status == http.StatusNotFound { + err = &NoSuchContainer{ID: opts.ID} + } + } + } + if closeErr := writeCloser.Close(); closeErr != nil && err == nil { + err = closeErr + } + errC <- err + close(errC) + }() + + quit := make(chan struct{}) + defer close(quit) + go func() { + // block here waiting for the signal to stop function + select { + case <-opts.Done: + readCloser.Close() + case <-quit: + return + } + }() + + decoder := json.NewDecoder(readCloser) + stats := new(Stats) + for err := decoder.Decode(stats); err != io.EOF; err = decoder.Decode(stats) { + if err != nil { + return err + } + opts.Stats <- stats + stats = new(Stats) + } + return nil +} + +// KillContainerOptions represents the set of options that can be used in a +// call to KillContainer. +// +// See https://goo.gl/JnTxXZ for more details. +type KillContainerOptions struct { + // The ID of the container. + ID string `qs:"-"` + + // The signal to send to the container. When omitted, Docker server + // will assume SIGKILL. + Signal Signal + Context context.Context +} + +// KillContainer sends a signal to a container, returning an error in case of +// failure. +// +// See https://goo.gl/JnTxXZ for more details. +func (c *Client) KillContainer(opts KillContainerOptions) error { + path := "/containers/" + opts.ID + "/kill" + "?" + queryString(opts) + resp, err := c.do("POST", path, doOptions{context: opts.Context}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} + +// RemoveContainerOptions encapsulates options to remove a container. +// +// See https://goo.gl/hL5IPC for more details. +type RemoveContainerOptions struct { + // The ID of the container. + ID string `qs:"-"` + + // A flag that indicates whether Docker should remove the volumes + // associated to the container. + RemoveVolumes bool `qs:"v"` + + // A flag that indicates whether Docker should remove the container + // even if it is currently running. + Force bool + Context context.Context +} + +// RemoveContainer removes a container, returning an error in case of failure. +// +// See https://goo.gl/hL5IPC for more details. +func (c *Client) RemoveContainer(opts RemoveContainerOptions) error { + path := "/containers/" + opts.ID + "?" + queryString(opts) + resp, err := c.do("DELETE", path, doOptions{context: opts.Context}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} + +// UploadToContainerOptions is the set of options that can be used when +// uploading an archive into a container. +// +// See https://goo.gl/g25o7u for more details. +type UploadToContainerOptions struct { + InputStream io.Reader `json:"-" qs:"-"` + Path string `qs:"path"` + NoOverwriteDirNonDir bool `qs:"noOverwriteDirNonDir"` + Context context.Context +} + +// UploadToContainer uploads a tar archive to be extracted to a path in the +// filesystem of the container. +// +// See https://goo.gl/g25o7u for more details. +func (c *Client) UploadToContainer(id string, opts UploadToContainerOptions) error { + url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) + + return c.stream("PUT", url, streamOptions{ + in: opts.InputStream, + context: opts.Context, + }) +} + +// DownloadFromContainerOptions is the set of options that can be used when +// downloading resources from a container. +// +// See https://goo.gl/W49jxK for more details. +type DownloadFromContainerOptions struct { + OutputStream io.Writer `json:"-" qs:"-"` + Path string `qs:"path"` + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// DownloadFromContainer downloads a tar archive of files or folders in a container. +// +// See https://goo.gl/W49jxK for more details. +func (c *Client) DownloadFromContainer(id string, opts DownloadFromContainerOptions) error { + url := fmt.Sprintf("/containers/%s/archive?", id) + queryString(opts) + + return c.stream("GET", url, streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} + +// CopyFromContainerOptions has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer. +// +// See https://goo.gl/nWk2YQ for more details. +type CopyFromContainerOptions struct { + OutputStream io.Writer `json:"-"` + Container string `json:"-"` + Resource string + Context context.Context `json:"-"` +} + +// CopyFromContainer has been DEPRECATED, please use DownloadFromContainerOptions along with DownloadFromContainer. +// +// See https://goo.gl/nWk2YQ for more details. +func (c *Client) CopyFromContainer(opts CopyFromContainerOptions) error { + if opts.Container == "" { + return &NoSuchContainer{ID: opts.Container} + } + if c.serverAPIVersion == nil { + c.checkAPIVersion() + } + if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion124) { + return errors.New("go-dockerclient: CopyFromContainer is no longer available in Docker >= 1.12, use DownloadFromContainer instead") + } + url := fmt.Sprintf("/containers/%s/copy", opts.Container) + resp, err := c.do("POST", url, doOptions{ + data: opts, + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchContainer{ID: opts.Container} + } + return err + } + defer resp.Body.Close() + _, err = io.Copy(opts.OutputStream, resp.Body) + return err +} + +// WaitContainer blocks until the given container stops, return the exit code +// of the container status. +// +// See https://goo.gl/4AGweZ for more details. +func (c *Client) WaitContainer(id string) (int, error) { + return c.waitContainer(id, doOptions{}) +} + +// WaitContainerWithContext blocks until the given container stops, return the exit code +// of the container status. The context object can be used to cancel the +// inspect request. +// +// See https://goo.gl/4AGweZ for more details. +func (c *Client) WaitContainerWithContext(id string, ctx context.Context) (int, error) { + return c.waitContainer(id, doOptions{context: ctx}) +} + +func (c *Client) waitContainer(id string, opts doOptions) (int, error) { + resp, err := c.do("POST", "/containers/"+id+"/wait", opts) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return 0, &NoSuchContainer{ID: id} + } + return 0, err + } + defer resp.Body.Close() + var r struct{ StatusCode int } + if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { + return 0, err + } + return r.StatusCode, nil +} + +// CommitContainerOptions aggregates parameters to the CommitContainer method. +// +// See https://goo.gl/CzIguf for more details. +type CommitContainerOptions struct { + Container string + Repository string `qs:"repo"` + Tag string + Message string `qs:"comment"` + Author string + Run *Config `qs:"-"` + Context context.Context +} + +// CommitContainer creates a new image from a container's changes. +// +// See https://goo.gl/CzIguf for more details. +func (c *Client) CommitContainer(opts CommitContainerOptions) (*Image, error) { + path := "/commit?" + queryString(opts) + resp, err := c.do("POST", path, doOptions{ + data: opts.Run, + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: opts.Container} + } + return nil, err + } + defer resp.Body.Close() + var image Image + if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { + return nil, err + } + return &image, nil +} + +// AttachToContainerOptions is the set of options that can be used when +// attaching to a container. +// +// See https://goo.gl/JF10Zk for more details. +type AttachToContainerOptions struct { + Container string `qs:"-"` + InputStream io.Reader `qs:"-"` + OutputStream io.Writer `qs:"-"` + ErrorStream io.Writer `qs:"-"` + + // If set, after a successful connect, a sentinel will be sent and then the + // client will block on receive before continuing. + // + // It must be an unbuffered channel. Using a buffered channel can lead + // to unexpected behavior. + Success chan struct{} + + // Use raw terminal? Usually true when the container contains a TTY. + RawTerminal bool `qs:"-"` + + // Get container logs, sending it to OutputStream. + Logs bool + + // Stream the response? + Stream bool + + // Attach to stdin, and use InputStream. + Stdin bool + + // Attach to stdout, and use OutputStream. + Stdout bool + + // Attach to stderr, and use ErrorStream. + Stderr bool +} + +// AttachToContainer attaches to a container, using the given options. +// +// See https://goo.gl/JF10Zk for more details. +func (c *Client) AttachToContainer(opts AttachToContainerOptions) error { + cw, err := c.AttachToContainerNonBlocking(opts) + if err != nil { + return err + } + return cw.Wait() +} + +// AttachToContainerNonBlocking attaches to a container, using the given options. +// This function does not block. +// +// See https://goo.gl/NKpkFk for more details. +func (c *Client) AttachToContainerNonBlocking(opts AttachToContainerOptions) (CloseWaiter, error) { + if opts.Container == "" { + return nil, &NoSuchContainer{ID: opts.Container} + } + path := "/containers/" + opts.Container + "/attach?" + queryString(opts) + return c.hijack("POST", path, hijackOptions{ + success: opts.Success, + setRawTerminal: opts.RawTerminal, + in: opts.InputStream, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + }) +} + +// LogsOptions represents the set of options used when getting logs from a +// container. +// +// See https://goo.gl/krK0ZH for more details. +type LogsOptions struct { + Context context.Context + Container string `qs:"-"` + OutputStream io.Writer `qs:"-"` + ErrorStream io.Writer `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + Tail string + + Since int64 + Follow bool + Stdout bool + Stderr bool + Timestamps bool + + // Use raw terminal? Usually true when the container contains a TTY. + RawTerminal bool `qs:"-"` +} + +// Logs gets stdout and stderr logs from the specified container. +// +// When LogsOptions.RawTerminal is set to false, go-dockerclient will multiplex +// the streams and send the containers stdout to LogsOptions.OutputStream, and +// stderr to LogsOptions.ErrorStream. +// +// When LogsOptions.RawTerminal is true, callers will get the raw stream on +// LogOptions.OutputStream. The caller can use libraries such as dlog +// (github.com/ahmetalpbalkan/dlog). +// +// See https://goo.gl/krK0ZH for more details. +func (c *Client) Logs(opts LogsOptions) error { + if opts.Container == "" { + return &NoSuchContainer{ID: opts.Container} + } + if opts.Tail == "" { + opts.Tail = "all" + } + path := "/containers/" + opts.Container + "/logs?" + queryString(opts) + return c.stream("GET", path, streamOptions{ + setRawTerminal: opts.RawTerminal, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} + +// ResizeContainerTTY resizes the terminal to the given height and width. +// +// See https://goo.gl/FImjeq for more details. +func (c *Client) ResizeContainerTTY(id string, height, width int) error { + params := make(url.Values) + params.Set("h", strconv.Itoa(height)) + params.Set("w", strconv.Itoa(width)) + resp, err := c.do("POST", "/containers/"+id+"/resize?"+params.Encode(), doOptions{}) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + +// ExportContainerOptions is the set of parameters to the ExportContainer +// method. +// +// See https://goo.gl/yGJCIh for more details. +type ExportContainerOptions struct { + ID string + OutputStream io.Writer + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// ExportContainer export the contents of container id as tar archive +// and prints the exported contents to stdout. +// +// See https://goo.gl/yGJCIh for more details. +func (c *Client) ExportContainer(opts ExportContainerOptions) error { + if opts.ID == "" { + return &NoSuchContainer{ID: opts.ID} + } + url := fmt.Sprintf("/containers/%s/export", opts.ID) + return c.stream("GET", url, streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} + +// NoSuchContainer is the error returned when a given container does not exist. +type NoSuchContainer struct { + ID string + Err error +} + +func (err *NoSuchContainer) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such container: " + err.ID +} + +// ContainerAlreadyRunning is the error returned when a given container is +// already running. +type ContainerAlreadyRunning struct { + ID string +} + +func (err *ContainerAlreadyRunning) Error() string { + return "Container already running: " + err.ID +} + +// ContainerNotRunning is the error returned when a given container is not +// running. +type ContainerNotRunning struct { + ID string +} + +func (err *ContainerNotRunning) Error() string { + return "Container not running: " + err.ID +} diff --git a/vendor/github.com/fsouza/go-dockerclient/env.go b/vendor/github.com/fsouza/go-dockerclient/env.go new file mode 100644 index 00000000000..c54b0b0e802 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/env.go @@ -0,0 +1,168 @@ +// Copyright 2014 Docker authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the DOCKER-LICENSE file. + +package docker + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "strings" +) + +// Env represents a list of key-pair represented in the form KEY=VALUE. +type Env []string + +// Get returns the string value of the given key. +func (env *Env) Get(key string) (value string) { + return env.Map()[key] +} + +// Exists checks whether the given key is defined in the internal Env +// representation. +func (env *Env) Exists(key string) bool { + _, exists := env.Map()[key] + return exists +} + +// GetBool returns a boolean representation of the given key. The key is false +// whenever its value if 0, no, false, none or an empty string. Any other value +// will be interpreted as true. +func (env *Env) GetBool(key string) (value bool) { + s := strings.ToLower(strings.Trim(env.Get(key), " \t")) + if s == "" || s == "0" || s == "no" || s == "false" || s == "none" { + return false + } + return true +} + +// SetBool defines a boolean value to the given key. +func (env *Env) SetBool(key string, value bool) { + if value { + env.Set(key, "1") + } else { + env.Set(key, "0") + } +} + +// GetInt returns the value of the provided key, converted to int. +// +// It the value cannot be represented as an integer, it returns -1. +func (env *Env) GetInt(key string) int { + return int(env.GetInt64(key)) +} + +// SetInt defines an integer value to the given key. +func (env *Env) SetInt(key string, value int) { + env.Set(key, strconv.Itoa(value)) +} + +// GetInt64 returns the value of the provided key, converted to int64. +// +// It the value cannot be represented as an integer, it returns -1. +func (env *Env) GetInt64(key string) int64 { + s := strings.Trim(env.Get(key), " \t") + val, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return -1 + } + return val +} + +// SetInt64 defines an integer (64-bit wide) value to the given key. +func (env *Env) SetInt64(key string, value int64) { + env.Set(key, strconv.FormatInt(value, 10)) +} + +// GetJSON unmarshals the value of the provided key in the provided iface. +// +// iface is a value that can be provided to the json.Unmarshal function. +func (env *Env) GetJSON(key string, iface interface{}) error { + sval := env.Get(key) + if sval == "" { + return nil + } + return json.Unmarshal([]byte(sval), iface) +} + +// SetJSON marshals the given value to JSON format and stores it using the +// provided key. +func (env *Env) SetJSON(key string, value interface{}) error { + sval, err := json.Marshal(value) + if err != nil { + return err + } + env.Set(key, string(sval)) + return nil +} + +// GetList returns a list of strings matching the provided key. It handles the +// list as a JSON representation of a list of strings. +// +// If the given key matches to a single string, it will return a list +// containing only the value that matches the key. +func (env *Env) GetList(key string) []string { + sval := env.Get(key) + if sval == "" { + return nil + } + var l []string + if err := json.Unmarshal([]byte(sval), &l); err != nil { + l = append(l, sval) + } + return l +} + +// SetList stores the given list in the provided key, after serializing it to +// JSON format. +func (env *Env) SetList(key string, value []string) error { + return env.SetJSON(key, value) +} + +// Set defines the value of a key to the given string. +func (env *Env) Set(key, value string) { + *env = append(*env, key+"="+value) +} + +// Decode decodes `src` as a json dictionary, and adds each decoded key-value +// pair to the environment. +// +// If `src` cannot be decoded as a json dictionary, an error is returned. +func (env *Env) Decode(src io.Reader) error { + m := make(map[string]interface{}) + if err := json.NewDecoder(src).Decode(&m); err != nil { + return err + } + for k, v := range m { + env.SetAuto(k, v) + } + return nil +} + +// SetAuto will try to define the Set* method to call based on the given value. +func (env *Env) SetAuto(key string, value interface{}) { + if fval, ok := value.(float64); ok { + env.SetInt64(key, int64(fval)) + } else if sval, ok := value.(string); ok { + env.Set(key, sval) + } else if val, err := json.Marshal(value); err == nil { + env.Set(key, string(val)) + } else { + env.Set(key, fmt.Sprintf("%v", value)) + } +} + +// Map returns the map representation of the env. +func (env *Env) Map() map[string]string { + if len(*env) == 0 { + return nil + } + m := make(map[string]string) + for _, kv := range *env { + parts := strings.SplitN(kv, "=", 2) + m[parts[0]] = parts[1] + } + return m +} diff --git a/vendor/github.com/fsouza/go-dockerclient/event.go b/vendor/github.com/fsouza/go-dockerclient/event.go new file mode 100644 index 00000000000..007d2b22bc4 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/event.go @@ -0,0 +1,391 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "math" + "net" + "net/http" + "net/http/httputil" + "sync" + "sync/atomic" + "time" +) + +// APIEvents represents events coming from the Docker API +// The fields in the Docker API changed in API version 1.22, and +// events for more than images and containers are now fired off. +// To maintain forward and backward compatibility, go-dockerclient +// replicates the event in both the new and old format as faithfully as possible. +// +// For events that only exist in 1.22 in later, `Status` is filled in as +// `"Type:Action"` instead of just `Action` to allow for older clients to +// differentiate and not break if they rely on the pre-1.22 Status types. +// +// The transformEvent method can be consulted for more information about how +// events are translated from new/old API formats +type APIEvents struct { + // New API Fields in 1.22 + Action string `json:"action,omitempty"` + Type string `json:"type,omitempty"` + Actor APIActor `json:"actor,omitempty"` + + // Old API fields for < 1.22 + Status string `json:"status,omitempty"` + ID string `json:"id,omitempty"` + From string `json:"from,omitempty"` + + // Fields in both + Time int64 `json:"time,omitempty"` + TimeNano int64 `json:"timeNano,omitempty"` +} + +// APIActor represents an actor that accomplishes something for an event +type APIActor struct { + ID string `json:"id,omitempty"` + Attributes map[string]string `json:"attributes,omitempty"` +} + +type eventMonitoringState struct { + // `sync/atomic` expects the first word in an allocated struct to be 64-bit + // aligned on both ARM and x86-32. See https://goo.gl/zW7dgq for more details. + lastSeen int64 + sync.RWMutex + sync.WaitGroup + enabled bool + C chan *APIEvents + errC chan error + listeners []chan<- *APIEvents +} + +const ( + maxMonitorConnRetries = 5 + retryInitialWaitTime = 10. +) + +var ( + // ErrNoListeners is the error returned when no listeners are available + // to receive an event. + ErrNoListeners = errors.New("no listeners present to receive event") + + // ErrListenerAlreadyExists is the error returned when the listerner already + // exists. + ErrListenerAlreadyExists = errors.New("listener already exists for docker events") + + // ErrTLSNotSupported is the error returned when the client does not support + // TLS (this applies to the Windows named pipe client). + ErrTLSNotSupported = errors.New("tls not supported by this client") + + // EOFEvent is sent when the event listener receives an EOF error. + EOFEvent = &APIEvents{ + Type: "EOF", + Status: "EOF", + } +) + +// AddEventListener adds a new listener to container events in the Docker API. +// +// The parameter is a channel through which events will be sent. +func (c *Client) AddEventListener(listener chan<- *APIEvents) error { + var err error + if !c.eventMonitor.isEnabled() { + err = c.eventMonitor.enableEventMonitoring(c) + if err != nil { + return err + } + } + return c.eventMonitor.addListener(listener) +} + +// RemoveEventListener removes a listener from the monitor. +func (c *Client) RemoveEventListener(listener chan *APIEvents) error { + err := c.eventMonitor.removeListener(listener) + if err != nil { + return err + } + if c.eventMonitor.listernersCount() == 0 { + c.eventMonitor.disableEventMonitoring() + } + return nil +} + +func (eventState *eventMonitoringState) addListener(listener chan<- *APIEvents) error { + eventState.Lock() + defer eventState.Unlock() + if listenerExists(listener, &eventState.listeners) { + return ErrListenerAlreadyExists + } + eventState.Add(1) + eventState.listeners = append(eventState.listeners, listener) + return nil +} + +func (eventState *eventMonitoringState) removeListener(listener chan<- *APIEvents) error { + eventState.Lock() + defer eventState.Unlock() + if listenerExists(listener, &eventState.listeners) { + var newListeners []chan<- *APIEvents + for _, l := range eventState.listeners { + if l != listener { + newListeners = append(newListeners, l) + } + } + eventState.listeners = newListeners + eventState.Add(-1) + } + return nil +} + +func (eventState *eventMonitoringState) closeListeners() { + for _, l := range eventState.listeners { + close(l) + eventState.Add(-1) + } + eventState.listeners = nil +} + +func (eventState *eventMonitoringState) listernersCount() int { + eventState.RLock() + defer eventState.RUnlock() + return len(eventState.listeners) +} + +func listenerExists(a chan<- *APIEvents, list *[]chan<- *APIEvents) bool { + for _, b := range *list { + if b == a { + return true + } + } + return false +} + +func (eventState *eventMonitoringState) enableEventMonitoring(c *Client) error { + eventState.Lock() + defer eventState.Unlock() + if !eventState.enabled { + eventState.enabled = true + atomic.StoreInt64(&eventState.lastSeen, 0) + eventState.C = make(chan *APIEvents, 100) + eventState.errC = make(chan error, 1) + go eventState.monitorEvents(c) + } + return nil +} + +func (eventState *eventMonitoringState) disableEventMonitoring() error { + eventState.Lock() + defer eventState.Unlock() + + eventState.closeListeners() + + eventState.Wait() + + if eventState.enabled { + eventState.enabled = false + close(eventState.C) + close(eventState.errC) + } + return nil +} + +func (eventState *eventMonitoringState) monitorEvents(c *Client) { + var err error + for eventState.noListeners() { + time.Sleep(10 * time.Millisecond) + } + if err = eventState.connectWithRetry(c); err != nil { + // terminate if connect failed + eventState.disableEventMonitoring() + return + } + for eventState.isEnabled() { + timeout := time.After(100 * time.Millisecond) + select { + case ev, ok := <-eventState.C: + if !ok { + return + } + if ev == EOFEvent { + eventState.disableEventMonitoring() + return + } + eventState.updateLastSeen(ev) + go eventState.sendEvent(ev) + case err = <-eventState.errC: + if err == ErrNoListeners { + eventState.disableEventMonitoring() + return + } else if err != nil { + defer func() { go eventState.monitorEvents(c) }() + return + } + case <-timeout: + continue + } + } +} + +func (eventState *eventMonitoringState) connectWithRetry(c *Client) error { + var retries int + eventState.RLock() + eventChan := eventState.C + errChan := eventState.errC + eventState.RUnlock() + err := c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan) + for ; err != nil && retries < maxMonitorConnRetries; retries++ { + waitTime := int64(retryInitialWaitTime * math.Pow(2, float64(retries))) + time.Sleep(time.Duration(waitTime) * time.Millisecond) + eventState.RLock() + eventChan = eventState.C + errChan = eventState.errC + eventState.RUnlock() + err = c.eventHijack(atomic.LoadInt64(&eventState.lastSeen), eventChan, errChan) + } + return err +} + +func (eventState *eventMonitoringState) noListeners() bool { + eventState.RLock() + defer eventState.RUnlock() + return len(eventState.listeners) == 0 +} + +func (eventState *eventMonitoringState) isEnabled() bool { + eventState.RLock() + defer eventState.RUnlock() + return eventState.enabled +} + +func (eventState *eventMonitoringState) sendEvent(event *APIEvents) { + eventState.RLock() + defer eventState.RUnlock() + eventState.Add(1) + defer eventState.Done() + if eventState.enabled { + if len(eventState.listeners) == 0 { + eventState.errC <- ErrNoListeners + return + } + + for _, listener := range eventState.listeners { + listener <- event + } + } +} + +func (eventState *eventMonitoringState) updateLastSeen(e *APIEvents) { + eventState.Lock() + defer eventState.Unlock() + if atomic.LoadInt64(&eventState.lastSeen) < e.Time { + atomic.StoreInt64(&eventState.lastSeen, e.Time) + } +} + +func (c *Client) eventHijack(startTime int64, eventChan chan *APIEvents, errChan chan error) error { + uri := "/events" + if startTime != 0 { + uri += fmt.Sprintf("?since=%d", startTime) + } + protocol := c.endpointURL.Scheme + address := c.endpointURL.Path + if protocol != "unix" && protocol != "npipe" { + protocol = "tcp" + address = c.endpointURL.Host + } + var dial net.Conn + var err error + if c.TLSConfig == nil { + dial, err = c.Dialer.Dial(protocol, address) + } else { + netDialer, ok := c.Dialer.(*net.Dialer) + if !ok { + return ErrTLSNotSupported + } + dial, err = tlsDialWithDialer(netDialer, protocol, address, c.TLSConfig) + } + if err != nil { + return err + } + conn := httputil.NewClientConn(dial, nil) + req, err := http.NewRequest("GET", uri, nil) + if err != nil { + return err + } + res, err := conn.Do(req) + if err != nil { + return err + } + go func(res *http.Response, conn *httputil.ClientConn) { + defer conn.Close() + defer res.Body.Close() + decoder := json.NewDecoder(res.Body) + for { + var event APIEvents + if err = decoder.Decode(&event); err != nil { + if err == io.EOF || err == io.ErrUnexpectedEOF { + c.eventMonitor.RLock() + if c.eventMonitor.enabled && c.eventMonitor.C == eventChan { + // Signal that we're exiting. + eventChan <- EOFEvent + } + c.eventMonitor.RUnlock() + break + } + errChan <- err + } + if event.Time == 0 { + continue + } + if !c.eventMonitor.isEnabled() || c.eventMonitor.C != eventChan { + return + } + transformEvent(&event) + eventChan <- &event + } + }(res, conn) + return nil +} + +// transformEvent takes an event and determines what version it is from +// then populates both versions of the event +func transformEvent(event *APIEvents) { + // if event version is <= 1.21 there will be no Action and no Type + if event.Action == "" && event.Type == "" { + event.Action = event.Status + event.Actor.ID = event.ID + event.Actor.Attributes = map[string]string{} + switch event.Status { + case "delete", "import", "pull", "push", "tag", "untag": + event.Type = "image" + default: + event.Type = "container" + if event.From != "" { + event.Actor.Attributes["image"] = event.From + } + } + } else { + if event.Status == "" { + if event.Type == "image" || event.Type == "container" { + event.Status = event.Action + } else { + // Because just the Status has been overloaded with different Types + // if an event is not for an image or a container, we prepend the type + // to avoid problems for people relying on actions being only for + // images and containers + event.Status = event.Type + ":" + event.Action + } + } + if event.ID == "" { + event.ID = event.Actor.ID + } + if event.From == "" { + event.From = event.Actor.Attributes["image"] + } + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/exec.go b/vendor/github.com/fsouza/go-dockerclient/exec.go new file mode 100644 index 00000000000..4b1c48637e2 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/exec.go @@ -0,0 +1,207 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + + "golang.org/x/net/context" +) + +// Exec is the type representing a `docker exec` instance and containing the +// instance ID +type Exec struct { + ID string `json:"Id,omitempty" yaml:"Id,omitempty"` +} + +// CreateExecOptions specify parameters to the CreateExecContainer function. +// +// See https://goo.gl/60TeBP for more details +type CreateExecOptions struct { + AttachStdin bool `json:"AttachStdin,omitempty" yaml:"AttachStdin,omitempty" toml:"AttachStdin,omitempty"` + AttachStdout bool `json:"AttachStdout,omitempty" yaml:"AttachStdout,omitempty" toml:"AttachStdout,omitempty"` + AttachStderr bool `json:"AttachStderr,omitempty" yaml:"AttachStderr,omitempty" toml:"AttachStderr,omitempty"` + Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` + Cmd []string `json:"Cmd,omitempty" yaml:"Cmd,omitempty" toml:"Cmd,omitempty"` + Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"` + User string `json:"User,omitempty" yaml:"User,omitempty" toml:"User,omitempty"` + Context context.Context `json:"-"` + Privileged bool `json:"Privileged,omitempty" yaml:"Privileged,omitempty" toml:"Privileged,omitempty"` +} + +// CreateExec sets up an exec instance in a running container `id`, returning the exec +// instance, or an error in case of failure. +// +// See https://goo.gl/60TeBP for more details +func (c *Client) CreateExec(opts CreateExecOptions) (*Exec, error) { + path := fmt.Sprintf("/containers/%s/exec", opts.Container) + resp, err := c.do("POST", path, doOptions{data: opts, context: opts.Context}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchContainer{ID: opts.Container} + } + return nil, err + } + defer resp.Body.Close() + var exec Exec + if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { + return nil, err + } + + return &exec, nil +} + +// StartExecOptions specify parameters to the StartExecContainer function. +// +// See https://goo.gl/1EeDWi for more details +type StartExecOptions struct { + InputStream io.Reader `qs:"-"` + OutputStream io.Writer `qs:"-"` + ErrorStream io.Writer `qs:"-"` + + Detach bool `json:"Detach,omitempty" yaml:"Detach,omitempty" toml:"Detach,omitempty"` + Tty bool `json:"Tty,omitempty" yaml:"Tty,omitempty" toml:"Tty,omitempty"` + + // Use raw terminal? Usually true when the container contains a TTY. + RawTerminal bool `qs:"-"` + + // If set, after a successful connect, a sentinel will be sent and then the + // client will block on receive before continuing. + // + // It must be an unbuffered channel. Using a buffered channel can lead + // to unexpected behavior. + Success chan struct{} `json:"-"` + + Context context.Context `json:"-"` +} + +// StartExec starts a previously set up exec instance id. If opts.Detach is +// true, it returns after starting the exec command. Otherwise, it sets up an +// interactive session with the exec command. +// +// See https://goo.gl/1EeDWi for more details +func (c *Client) StartExec(id string, opts StartExecOptions) error { + cw, err := c.StartExecNonBlocking(id, opts) + if err != nil { + return err + } + if cw != nil { + return cw.Wait() + } + return nil +} + +// StartExecNonBlocking starts a previously set up exec instance id. If opts.Detach is +// true, it returns after starting the exec command. Otherwise, it sets up an +// interactive session with the exec command. +// +// See https://goo.gl/1EeDWi for more details +func (c *Client) StartExecNonBlocking(id string, opts StartExecOptions) (CloseWaiter, error) { + if id == "" { + return nil, &NoSuchExec{ID: id} + } + + path := fmt.Sprintf("/exec/%s/start", id) + + if opts.Detach { + resp, err := c.do("POST", path, doOptions{data: opts, context: opts.Context}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchExec{ID: id} + } + return nil, err + } + defer resp.Body.Close() + return nil, nil + } + + return c.hijack("POST", path, hijackOptions{ + success: opts.Success, + setRawTerminal: opts.RawTerminal, + in: opts.InputStream, + stdout: opts.OutputStream, + stderr: opts.ErrorStream, + data: opts, + }) +} + +// ResizeExecTTY resizes the tty session used by the exec command id. This API +// is valid only if Tty was specified as part of creating and starting the exec +// command. +// +// See https://goo.gl/Mo5bxx for more details +func (c *Client) ResizeExecTTY(id string, height, width int) error { + params := make(url.Values) + params.Set("h", strconv.Itoa(height)) + params.Set("w", strconv.Itoa(width)) + + path := fmt.Sprintf("/exec/%s/resize?%s", id, params.Encode()) + resp, err := c.do("POST", path, doOptions{}) + if err != nil { + return err + } + resp.Body.Close() + return nil +} + +// ExecProcessConfig is a type describing the command associated to a Exec +// instance. It's used in the ExecInspect type. +type ExecProcessConfig struct { + User string `json:"user,omitempty" yaml:"user,omitempty" toml:"user,omitempty"` + Privileged bool `json:"privileged,omitempty" yaml:"privileged,omitempty" toml:"privileged,omitempty"` + Tty bool `json:"tty,omitempty" yaml:"tty,omitempty" toml:"tty,omitempty"` + EntryPoint string `json:"entrypoint,omitempty" yaml:"entrypoint,omitempty" toml:"entrypoint,omitempty"` + Arguments []string `json:"arguments,omitempty" yaml:"arguments,omitempty" toml:"arguments,omitempty"` +} + +// ExecInspect is a type with details about a exec instance, including the +// exit code if the command has finished running. It's returned by a api +// call to /exec/(id)/json +// +// See https://goo.gl/ctMUiW for more details +type ExecInspect struct { + ID string `json:"ID,omitempty" yaml:"ID,omitempty" toml:"ID,omitempty"` + ExitCode int `json:"ExitCode,omitempty" yaml:"ExitCode,omitempty" toml:"ExitCode,omitempty"` + Running bool `json:"Running,omitempty" yaml:"Running,omitempty" toml:"Running,omitempty"` + OpenStdin bool `json:"OpenStdin,omitempty" yaml:"OpenStdin,omitempty" toml:"OpenStdin,omitempty"` + OpenStderr bool `json:"OpenStderr,omitempty" yaml:"OpenStderr,omitempty" toml:"OpenStderr,omitempty"` + OpenStdout bool `json:"OpenStdout,omitempty" yaml:"OpenStdout,omitempty" toml:"OpenStdout,omitempty"` + ProcessConfig ExecProcessConfig `json:"ProcessConfig,omitempty" yaml:"ProcessConfig,omitempty" toml:"ProcessConfig,omitempty"` + Container Container `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"` +} + +// InspectExec returns low-level information about the exec command id. +// +// See https://goo.gl/ctMUiW for more details +func (c *Client) InspectExec(id string) (*ExecInspect, error) { + path := fmt.Sprintf("/exec/%s/json", id) + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchExec{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var exec ExecInspect + if err := json.NewDecoder(resp.Body).Decode(&exec); err != nil { + return nil, err + } + return &exec, nil +} + +// NoSuchExec is the error returned when a given exec instance does not exist. +type NoSuchExec struct { + ID string +} + +func (err *NoSuchExec) Error() string { + return "No such exec instance: " + err.ID +} diff --git a/vendor/github.com/fsouza/go-dockerclient/image.go b/vendor/github.com/fsouza/go-dockerclient/image.go new file mode 100644 index 00000000000..f530bbe803d --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/image.go @@ -0,0 +1,668 @@ +// Copyright 2013 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "time" + + "golang.org/x/net/context" +) + +// APIImages represent an image returned in the ListImages call. +type APIImages struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"` + Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` + Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"` + VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"` + ParentID string `json:"ParentId,omitempty" yaml:"ParentId,omitempty" toml:"ParentId,omitempty"` + RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` +} + +// RootFS represents the underlying layers used by an image +type RootFS struct { + Type string `json:"Type,omitempty" yaml:"Type,omitempty" toml:"Type,omitempty"` + Layers []string `json:"Layers,omitempty" yaml:"Layers,omitempty" toml:"Layers,omitempty"` +} + +// Image is the type representing a docker image and its various properties +type Image struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + RepoTags []string `json:"RepoTags,omitempty" yaml:"RepoTags,omitempty" toml:"RepoTags,omitempty"` + Parent string `json:"Parent,omitempty" yaml:"Parent,omitempty" toml:"Parent,omitempty"` + Comment string `json:"Comment,omitempty" yaml:"Comment,omitempty" toml:"Comment,omitempty"` + Created time.Time `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Created,omitempty"` + Container string `json:"Container,omitempty" yaml:"Container,omitempty" toml:"Container,omitempty"` + ContainerConfig Config `json:"ContainerConfig,omitempty" yaml:"ContainerConfig,omitempty" toml:"ContainerConfig,omitempty"` + DockerVersion string `json:"DockerVersion,omitempty" yaml:"DockerVersion,omitempty" toml:"DockerVersion,omitempty"` + Author string `json:"Author,omitempty" yaml:"Author,omitempty" toml:"Author,omitempty"` + Config *Config `json:"Config,omitempty" yaml:"Config,omitempty" toml:"Config,omitempty"` + Architecture string `json:"Architecture,omitempty" yaml:"Architecture,omitempty"` + Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"` + VirtualSize int64 `json:"VirtualSize,omitempty" yaml:"VirtualSize,omitempty" toml:"VirtualSize,omitempty"` + RepoDigests []string `json:"RepoDigests,omitempty" yaml:"RepoDigests,omitempty" toml:"RepoDigests,omitempty"` + RootFS *RootFS `json:"RootFS,omitempty" yaml:"RootFS,omitempty" toml:"RootFS,omitempty"` +} + +// ImagePre012 serves the same purpose as the Image type except that it is for +// earlier versions of the Docker API (pre-012 to be specific) +type ImagePre012 struct { + ID string `json:"id"` + Parent string `json:"parent,omitempty"` + Comment string `json:"comment,omitempty"` + Created time.Time `json:"created"` + Container string `json:"container,omitempty"` + ContainerConfig Config `json:"container_config,omitempty"` + DockerVersion string `json:"docker_version,omitempty"` + Author string `json:"author,omitempty"` + Config *Config `json:"config,omitempty"` + Architecture string `json:"architecture,omitempty"` + Size int64 `json:"size,omitempty"` +} + +var ( + // ErrNoSuchImage is the error returned when the image does not exist. + ErrNoSuchImage = errors.New("no such image") + + // ErrMissingRepo is the error returned when the remote repository is + // missing. + ErrMissingRepo = errors.New("missing remote repository e.g. 'github.com/user/repo'") + + // ErrMissingOutputStream is the error returned when no output stream + // is provided to some calls, like BuildImage. + ErrMissingOutputStream = errors.New("missing output stream") + + // ErrMultipleContexts is the error returned when both a ContextDir and + // InputStream are provided in BuildImageOptions + ErrMultipleContexts = errors.New("image build may not be provided BOTH context dir and input stream") + + // ErrMustSpecifyNames is the error rreturned when the Names field on + // ExportImagesOptions is nil or empty + ErrMustSpecifyNames = errors.New("must specify at least one name to export") +) + +// ListImagesOptions specify parameters to the ListImages function. +// +// See https://goo.gl/BVzauZ for more details. +type ListImagesOptions struct { + Filters map[string][]string + All bool + Digests bool + Filter string + Context context.Context +} + +// ListImages returns the list of available images in the server. +// +// See https://goo.gl/BVzauZ for more details. +func (c *Client) ListImages(opts ListImagesOptions) ([]APIImages, error) { + path := "/images/json?" + queryString(opts) + resp, err := c.do("GET", path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var images []APIImages + if err := json.NewDecoder(resp.Body).Decode(&images); err != nil { + return nil, err + } + return images, nil +} + +// ImageHistory represent a layer in an image's history returned by the +// ImageHistory call. +type ImageHistory struct { + ID string `json:"Id" yaml:"Id" toml:"Id"` + Tags []string `json:"Tags,omitempty" yaml:"Tags,omitempty" toml:"Tags,omitempty"` + Created int64 `json:"Created,omitempty" yaml:"Created,omitempty" toml:"Tags,omitempty"` + CreatedBy string `json:"CreatedBy,omitempty" yaml:"CreatedBy,omitempty" toml:"CreatedBy,omitempty"` + Size int64 `json:"Size,omitempty" yaml:"Size,omitempty" toml:"Size,omitempty"` +} + +// ImageHistory returns the history of the image by its name or ID. +// +// See https://goo.gl/fYtxQa for more details. +func (c *Client) ImageHistory(name string) ([]ImageHistory, error) { + resp, err := c.do("GET", "/images/"+name+"/history", doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, ErrNoSuchImage + } + return nil, err + } + defer resp.Body.Close() + var history []ImageHistory + if err := json.NewDecoder(resp.Body).Decode(&history); err != nil { + return nil, err + } + return history, nil +} + +// RemoveImage removes an image by its name or ID. +// +// See https://goo.gl/Vd2Pck for more details. +func (c *Client) RemoveImage(name string) error { + resp, err := c.do("DELETE", "/images/"+name, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return ErrNoSuchImage + } + return err + } + resp.Body.Close() + return nil +} + +// RemoveImageOptions present the set of options available for removing an image +// from a registry. +// +// See https://goo.gl/Vd2Pck for more details. +type RemoveImageOptions struct { + Force bool `qs:"force"` + NoPrune bool `qs:"noprune"` + Context context.Context +} + +// RemoveImageExtended removes an image by its name or ID. +// Extra params can be passed, see RemoveImageOptions +// +// See https://goo.gl/Vd2Pck for more details. +func (c *Client) RemoveImageExtended(name string, opts RemoveImageOptions) error { + uri := fmt.Sprintf("/images/%s?%s", name, queryString(&opts)) + resp, err := c.do("DELETE", uri, doOptions{context: opts.Context}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return ErrNoSuchImage + } + return err + } + resp.Body.Close() + return nil +} + +// InspectImage returns an image by its name or ID. +// +// See https://goo.gl/ncLTG8 for more details. +func (c *Client) InspectImage(name string) (*Image, error) { + resp, err := c.do("GET", "/images/"+name+"/json", doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, ErrNoSuchImage + } + return nil, err + } + defer resp.Body.Close() + + var image Image + + // if the caller elected to skip checking the server's version, assume it's the latest + if c.SkipServerVersionCheck || c.expectedAPIVersion.GreaterThanOrEqualTo(apiVersion112) { + if err := json.NewDecoder(resp.Body).Decode(&image); err != nil { + return nil, err + } + } else { + var imagePre012 ImagePre012 + if err := json.NewDecoder(resp.Body).Decode(&imagePre012); err != nil { + return nil, err + } + + image.ID = imagePre012.ID + image.Parent = imagePre012.Parent + image.Comment = imagePre012.Comment + image.Created = imagePre012.Created + image.Container = imagePre012.Container + image.ContainerConfig = imagePre012.ContainerConfig + image.DockerVersion = imagePre012.DockerVersion + image.Author = imagePre012.Author + image.Config = imagePre012.Config + image.Architecture = imagePre012.Architecture + image.Size = imagePre012.Size + } + + return &image, nil +} + +// PushImageOptions represents options to use in the PushImage method. +// +// See https://goo.gl/BZemGg for more details. +type PushImageOptions struct { + // Name of the image + Name string + + // Tag of the image + Tag string + + // Registry server to push the image + Registry string + + OutputStream io.Writer `qs:"-"` + RawJSONStream bool `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + + Context context.Context +} + +// PushImage pushes an image to a remote registry, logging progress to w. +// +// An empty instance of AuthConfiguration may be used for unauthenticated +// pushes. +// +// See https://goo.gl/BZemGg for more details. +func (c *Client) PushImage(opts PushImageOptions, auth AuthConfiguration) error { + if opts.Name == "" { + return ErrNoSuchImage + } + headers, err := headersWithAuth(auth) + if err != nil { + return err + } + name := opts.Name + opts.Name = "" + path := "/images/" + name + "/push?" + queryString(&opts) + return c.stream("POST", path, streamOptions{ + setRawTerminal: true, + rawJSONStream: opts.RawJSONStream, + headers: headers, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} + +// PullImageOptions present the set of options available for pulling an image +// from a registry. +// +// See https://goo.gl/qkoSsn for more details. +type PullImageOptions struct { + Repository string `qs:"fromImage"` + Tag string + + // Only required for Docker Engine 1.9 or 1.10 w/ Remote API < 1.21 + // and Docker Engine < 1.9 + // This parameter was removed in Docker Engine 1.11 + Registry string + + OutputStream io.Writer `qs:"-"` + RawJSONStream bool `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// PullImage pulls an image from a remote registry, logging progress to +// opts.OutputStream. +// +// See https://goo.gl/qkoSsn for more details. +func (c *Client) PullImage(opts PullImageOptions, auth AuthConfiguration) error { + if opts.Repository == "" { + return ErrNoSuchImage + } + + headers, err := headersWithAuth(auth) + if err != nil { + return err + } + return c.createImage(queryString(&opts), headers, nil, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context) +} + +func (c *Client) createImage(qs string, headers map[string]string, in io.Reader, w io.Writer, rawJSONStream bool, timeout time.Duration, context context.Context) error { + path := "/images/create?" + qs + return c.stream("POST", path, streamOptions{ + setRawTerminal: true, + headers: headers, + in: in, + stdout: w, + rawJSONStream: rawJSONStream, + inactivityTimeout: timeout, + context: context, + }) +} + +// LoadImageOptions represents the options for LoadImage Docker API Call +// +// See https://goo.gl/rEsBV3 for more details. +type LoadImageOptions struct { + InputStream io.Reader + Context context.Context +} + +// LoadImage imports a tarball docker image +// +// See https://goo.gl/rEsBV3 for more details. +func (c *Client) LoadImage(opts LoadImageOptions) error { + return c.stream("POST", "/images/load", streamOptions{ + setRawTerminal: true, + in: opts.InputStream, + context: opts.Context, + }) +} + +// ExportImageOptions represent the options for ExportImage Docker API call. +// +// See https://goo.gl/AuySaA for more details. +type ExportImageOptions struct { + Name string + OutputStream io.Writer + InactivityTimeout time.Duration + Context context.Context +} + +// ExportImage exports an image (as a tar file) into the stream. +// +// See https://goo.gl/AuySaA for more details. +func (c *Client) ExportImage(opts ExportImageOptions) error { + return c.stream("GET", fmt.Sprintf("/images/%s/get", opts.Name), streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} + +// ExportImagesOptions represent the options for ExportImages Docker API call +// +// See https://goo.gl/N9XlDn for more details. +type ExportImagesOptions struct { + Names []string + OutputStream io.Writer `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// ExportImages exports one or more images (as a tar file) into the stream +// +// See https://goo.gl/N9XlDn for more details. +func (c *Client) ExportImages(opts ExportImagesOptions) error { + if opts.Names == nil || len(opts.Names) == 0 { + return ErrMustSpecifyNames + } + return c.stream("GET", "/images/get?"+queryString(&opts), streamOptions{ + setRawTerminal: true, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + }) +} + +// ImportImageOptions present the set of informations available for importing +// an image from a source file or the stdin. +// +// See https://goo.gl/qkoSsn for more details. +type ImportImageOptions struct { + Repository string `qs:"repo"` + Source string `qs:"fromSrc"` + Tag string `qs:"tag"` + + InputStream io.Reader `qs:"-"` + OutputStream io.Writer `qs:"-"` + RawJSONStream bool `qs:"-"` + InactivityTimeout time.Duration `qs:"-"` + Context context.Context +} + +// ImportImage imports an image from a url, a file or stdin +// +// See https://goo.gl/qkoSsn for more details. +func (c *Client) ImportImage(opts ImportImageOptions) error { + if opts.Repository == "" { + return ErrNoSuchImage + } + if opts.Source != "-" { + opts.InputStream = nil + } + if opts.Source != "-" && !isURL(opts.Source) { + f, err := os.Open(opts.Source) + if err != nil { + return err + } + opts.InputStream = f + opts.Source = "-" + } + return c.createImage(queryString(&opts), nil, opts.InputStream, opts.OutputStream, opts.RawJSONStream, opts.InactivityTimeout, opts.Context) +} + +// BuildImageOptions present the set of informations available for building an +// image from a tarfile with a Dockerfile in it. +// +// For more details about the Docker building process, see +// https://goo.gl/4nYHwV. +type BuildImageOptions struct { + Name string `qs:"t"` + Dockerfile string `qs:"dockerfile"` + NoCache bool `qs:"nocache"` + SuppressOutput bool `qs:"q"` + Pull bool `qs:"pull"` + RmTmpContainer bool `qs:"rm"` + ForceRmTmpContainer bool `qs:"forcerm"` + RawJSONStream bool `qs:"-"` + Memory int64 `qs:"memory"` + Memswap int64 `qs:"memswap"` + CPUShares int64 `qs:"cpushares"` + CPUQuota int64 `qs:"cpuquota"` + CPUPeriod int64 `qs:"cpuperiod"` + CPUSetCPUs string `qs:"cpusetcpus"` + Labels map[string]string `qs:"labels"` + InputStream io.Reader `qs:"-"` + OutputStream io.Writer `qs:"-"` + Remote string `qs:"remote"` + Auth AuthConfiguration `qs:"-"` // for older docker X-Registry-Auth header + AuthConfigs AuthConfigurations `qs:"-"` // for newer docker X-Registry-Config header + ContextDir string `qs:"-"` + Ulimits []ULimit `qs:"-"` + BuildArgs []BuildArg `qs:"-"` + NetworkMode string `qs:"networkmode"` + InactivityTimeout time.Duration `qs:"-"` + CgroupParent string `qs:"cgroupparent"` + Context context.Context +} + +// BuildArg represents arguments that can be passed to the image when building +// it from a Dockerfile. +// +// For more details about the Docker building process, see +// https://goo.gl/4nYHwV. +type BuildArg struct { + Name string `json:"Name,omitempty" yaml:"Name,omitempty" toml:"Name,omitempty"` + Value string `json:"Value,omitempty" yaml:"Value,omitempty" toml:"Value,omitempty"` +} + +// BuildImage builds an image from a tarball's url or a Dockerfile in the input +// stream. +// +// See https://goo.gl/4nYHwV for more details. +func (c *Client) BuildImage(opts BuildImageOptions) error { + if opts.OutputStream == nil { + return ErrMissingOutputStream + } + headers, err := headersWithAuth(opts.Auth, c.versionedAuthConfigs(opts.AuthConfigs)) + if err != nil { + return err + } + + if opts.Remote != "" && opts.Name == "" { + opts.Name = opts.Remote + } + if opts.InputStream != nil || opts.ContextDir != "" { + headers["Content-Type"] = "application/tar" + } else if opts.Remote == "" { + return ErrMissingRepo + } + if opts.ContextDir != "" { + if opts.InputStream != nil { + return ErrMultipleContexts + } + var err error + if opts.InputStream, err = createTarStream(opts.ContextDir, opts.Dockerfile); err != nil { + return err + } + } + + qs := queryString(&opts) + if len(opts.Ulimits) > 0 { + if b, err := json.Marshal(opts.Ulimits); err == nil { + item := url.Values(map[string][]string{}) + item.Add("ulimits", string(b)) + qs = fmt.Sprintf("%s&%s", qs, item.Encode()) + } + } + + if len(opts.BuildArgs) > 0 { + v := make(map[string]string) + for _, arg := range opts.BuildArgs { + v[arg.Name] = arg.Value + } + if b, err := json.Marshal(v); err == nil { + item := url.Values(map[string][]string{}) + item.Add("buildargs", string(b)) + qs = fmt.Sprintf("%s&%s", qs, item.Encode()) + } + } + + return c.stream("POST", fmt.Sprintf("/build?%s", qs), streamOptions{ + setRawTerminal: true, + rawJSONStream: opts.RawJSONStream, + headers: headers, + in: opts.InputStream, + stdout: opts.OutputStream, + inactivityTimeout: opts.InactivityTimeout, + context: opts.Context, + }) +} + +func (c *Client) versionedAuthConfigs(authConfigs AuthConfigurations) interface{} { + if c.serverAPIVersion == nil { + c.checkAPIVersion() + } + if c.serverAPIVersion != nil && c.serverAPIVersion.GreaterThanOrEqualTo(apiVersion119) { + return AuthConfigurations119(authConfigs.Configs) + } + return authConfigs +} + +// TagImageOptions present the set of options to tag an image. +// +// See https://goo.gl/prHrvo for more details. +type TagImageOptions struct { + Repo string + Tag string + Force bool + Context context.Context +} + +// TagImage adds a tag to the image identified by the given name. +// +// See https://goo.gl/prHrvo for more details. +func (c *Client) TagImage(name string, opts TagImageOptions) error { + if name == "" { + return ErrNoSuchImage + } + resp, err := c.do("POST", "/images/"+name+"/tag?"+queryString(&opts), doOptions{ + context: opts.Context, + }) + + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusNotFound { + return ErrNoSuchImage + } + + return err +} + +func isURL(u string) bool { + p, err := url.Parse(u) + if err != nil { + return false + } + return p.Scheme == "http" || p.Scheme == "https" +} + +func headersWithAuth(auths ...interface{}) (map[string]string, error) { + var headers = make(map[string]string) + + for _, auth := range auths { + switch auth.(type) { + case AuthConfiguration: + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(auth); err != nil { + return nil, err + } + headers["X-Registry-Auth"] = base64.URLEncoding.EncodeToString(buf.Bytes()) + case AuthConfigurations, AuthConfigurations119: + var buf bytes.Buffer + if err := json.NewEncoder(&buf).Encode(auth); err != nil { + return nil, err + } + headers["X-Registry-Config"] = base64.URLEncoding.EncodeToString(buf.Bytes()) + } + } + + return headers, nil +} + +// APIImageSearch reflect the result of a search on the Docker Hub. +// +// See https://goo.gl/KLO9IZ for more details. +type APIImageSearch struct { + Description string `json:"description,omitempty" yaml:"description,omitempty" toml:"description,omitempty"` + IsOfficial bool `json:"is_official,omitempty" yaml:"is_official,omitempty" toml:"is_official,omitempty"` + IsAutomated bool `json:"is_automated,omitempty" yaml:"is_automated,omitempty" toml:"is_automated,omitempty"` + Name string `json:"name,omitempty" yaml:"name,omitempty" toml:"name,omitempty"` + StarCount int `json:"star_count,omitempty" yaml:"star_count,omitempty" toml:"star_count,omitempty"` +} + +// SearchImages search the docker hub with a specific given term. +// +// See https://goo.gl/KLO9IZ for more details. +func (c *Client) SearchImages(term string) ([]APIImageSearch, error) { + resp, err := c.do("GET", "/images/search?term="+term, doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var searchResult []APIImageSearch + if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil { + return nil, err + } + return searchResult, nil +} + +// SearchImagesEx search the docker hub with a specific given term and authentication. +// +// See https://goo.gl/KLO9IZ for more details. +func (c *Client) SearchImagesEx(term string, auth AuthConfiguration) ([]APIImageSearch, error) { + headers, err := headersWithAuth(auth) + if err != nil { + return nil, err + } + + resp, err := c.do("GET", "/images/search?term="+term, doOptions{ + headers: headers, + }) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + var searchResult []APIImageSearch + if err := json.NewDecoder(resp.Body).Decode(&searchResult); err != nil { + return nil, err + } + + return searchResult, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/misc.go b/vendor/github.com/fsouza/go-dockerclient/misc.go new file mode 100644 index 00000000000..aef595fe1ee --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/misc.go @@ -0,0 +1,177 @@ +// Copyright 2013 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "net" + "strings" + + "github.com/docker/docker/api/types/swarm" +) + +// Version returns version information about the docker server. +// +// See https://goo.gl/mU7yje for more details. +func (c *Client) Version() (*Env, error) { + resp, err := c.do("GET", "/version", doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var env Env + if err := env.Decode(resp.Body); err != nil { + return nil, err + } + return &env, nil +} + +// DockerInfo contains information about the Docker server +// +// See https://goo.gl/bHUoz9 for more details. +type DockerInfo struct { + ID string + Containers int + ContainersRunning int + ContainersPaused int + ContainersStopped int + Images int + Driver string + DriverStatus [][2]string + SystemStatus [][2]string + Plugins PluginsInfo + MemoryLimit bool + SwapLimit bool + KernelMemory bool + CPUCfsPeriod bool `json:"CpuCfsPeriod"` + CPUCfsQuota bool `json:"CpuCfsQuota"` + CPUShares bool + CPUSet bool + IPv4Forwarding bool + BridgeNfIptables bool + BridgeNfIP6tables bool `json:"BridgeNfIp6tables"` + Debug bool + OomKillDisable bool + ExperimentalBuild bool + NFd int + NGoroutines int + SystemTime string + ExecutionDriver string + LoggingDriver string + CgroupDriver string + NEventsListener int + KernelVersion string + OperatingSystem string + OSType string + Architecture string + IndexServerAddress string + RegistryConfig *ServiceConfig + NCPU int + MemTotal int64 + DockerRootDir string + HTTPProxy string `json:"HttpProxy"` + HTTPSProxy string `json:"HttpsProxy"` + NoProxy string + Name string + Labels []string + ServerVersion string + ClusterStore string + ClusterAdvertise string + Swarm swarm.Info +} + +// PluginsInfo is a struct with the plugins registered with the docker daemon +// +// for more information, see: https://goo.gl/bHUoz9 +type PluginsInfo struct { + // List of Volume plugins registered + Volume []string + // List of Network plugins registered + Network []string + // List of Authorization plugins registered + Authorization []string +} + +// ServiceConfig stores daemon registry services configuration. +// +// for more information, see: https://goo.gl/7iFFDz +type ServiceConfig struct { + InsecureRegistryCIDRs []*NetIPNet + IndexConfigs map[string]*IndexInfo + Mirrors []string +} + +// NetIPNet is the net.IPNet type, which can be marshalled and +// unmarshalled to JSON. +// +// for more information, see: https://goo.gl/7iFFDz +type NetIPNet net.IPNet + +// MarshalJSON returns the JSON representation of the IPNet. +// +func (ipnet *NetIPNet) MarshalJSON() ([]byte, error) { + return json.Marshal((*net.IPNet)(ipnet).String()) +} + +// UnmarshalJSON sets the IPNet from a byte array of JSON. +// +func (ipnet *NetIPNet) UnmarshalJSON(b []byte) (err error) { + var ipnetStr string + if err = json.Unmarshal(b, &ipnetStr); err == nil { + var cidr *net.IPNet + if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil { + *ipnet = NetIPNet(*cidr) + } + } + return +} + +// IndexInfo contains information about a registry. +// +// for more information, see: https://goo.gl/7iFFDz +type IndexInfo struct { + Name string + Mirrors []string + Secure bool + Official bool +} + +// Info returns system-wide information about the Docker server. +// +// See https://goo.gl/ElTHi2 for more details. +func (c *Client) Info() (*DockerInfo, error) { + resp, err := c.do("GET", "/info", doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var info DockerInfo + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { + return nil, err + } + return &info, nil +} + +// ParseRepositoryTag gets the name of the repository and returns it splitted +// in two parts: the repository and the tag. It ignores the digest when it is +// present. +// +// Some examples: +// +// localhost.localdomain:5000/samalba/hipache:latest -> localhost.localdomain:5000/samalba/hipache, latest +// localhost.localdomain:5000/samalba/hipache -> localhost.localdomain:5000/samalba/hipache, "" +// busybox:latest@sha256:4a731fb46adc5cefe3ae374a8b6020fc1b6ad667a279647766e9a3cd89f6fa92 -> busybox, latest +func ParseRepositoryTag(repoTag string) (repository string, tag string) { + parts := strings.SplitN(repoTag, "@", 2) + repoTag = parts[0] + n := strings.LastIndex(repoTag, ":") + if n < 0 { + return repoTag, "" + } + if tag := repoTag[n+1:]; !strings.Contains(tag, "/") { + return repoTag[:n], tag + } + return repoTag, "" +} diff --git a/vendor/github.com/fsouza/go-dockerclient/network.go b/vendor/github.com/fsouza/go-dockerclient/network.go new file mode 100644 index 00000000000..691bbe4119e --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/network.go @@ -0,0 +1,289 @@ +// Copyright 2015 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "golang.org/x/net/context" +) + +// ErrNetworkAlreadyExists is the error returned by CreateNetwork when the +// network already exists. +var ErrNetworkAlreadyExists = errors.New("network already exists") + +// Network represents a network. +// +// See https://goo.gl/6GugX3 for more details. +type Network struct { + Name string + ID string `json:"Id"` + Scope string + Driver string + IPAM IPAMOptions + Containers map[string]Endpoint + Options map[string]string + Internal bool + EnableIPv6 bool `json:"EnableIPv6"` + Labels map[string]string +} + +// Endpoint contains network resources allocated and used for a container in a network +// +// See https://goo.gl/6GugX3 for more details. +type Endpoint struct { + Name string + ID string `json:"EndpointID"` + MacAddress string + IPv4Address string + IPv6Address string +} + +// ListNetworks returns all networks. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) ListNetworks() ([]Network, error) { + resp, err := c.do("GET", "/networks", doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var networks []Network + if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil { + return nil, err + } + return networks, nil +} + +// NetworkFilterOpts is an aggregation of key=value that Docker +// uses to filter networks +type NetworkFilterOpts map[string]map[string]bool + +// FilteredListNetworks returns all networks with the filters applied +// +// See goo.gl/zd2mx4 for more details. +func (c *Client) FilteredListNetworks(opts NetworkFilterOpts) ([]Network, error) { + params, err := json.Marshal(opts) + if err != nil { + return nil, err + } + path := "/networks?filters=" + string(params) + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var networks []Network + if err := json.NewDecoder(resp.Body).Decode(&networks); err != nil { + return nil, err + } + return networks, nil +} + +// NetworkInfo returns information about a network by its ID. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) NetworkInfo(id string) (*Network, error) { + path := "/networks/" + id + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchNetwork{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var network Network + if err := json.NewDecoder(resp.Body).Decode(&network); err != nil { + return nil, err + } + return &network, nil +} + +// CreateNetworkOptions specify parameters to the CreateNetwork function and +// (for now) is the expected body of the "create network" http request message +// +// See https://goo.gl/6GugX3 for more details. +type CreateNetworkOptions struct { + Name string `json:"Name" yaml:"Name" toml:"Name"` + Driver string `json:"Driver" yaml:"Driver" toml:"Driver"` + IPAM IPAMOptions `json:"IPAM" yaml:"IPAM" toml:"IPAM"` + Options map[string]interface{} `json:"Options" yaml:"Options" toml:"Options"` + Labels map[string]string `json:"Labels" yaml:"Labels" toml:"Labels"` + CheckDuplicate bool `json:"CheckDuplicate" yaml:"CheckDuplicate" toml:"CheckDuplicate"` + Internal bool `json:"Internal" yaml:"Internal" toml:"Internal"` + EnableIPv6 bool `json:"EnableIPv6" yaml:"EnableIPv6" toml:"EnableIPv6"` + Context context.Context `json:"-"` +} + +// IPAMOptions controls IP Address Management when creating a network +// +// See https://goo.gl/T8kRVH for more details. +type IPAMOptions struct { + Driver string `json:"Driver" yaml:"Driver" toml:"Driver"` + Config []IPAMConfig `json:"Config" yaml:"Config" toml:"Config"` +} + +// IPAMConfig represents IPAM configurations +// +// See https://goo.gl/T8kRVH for more details. +type IPAMConfig struct { + Subnet string `json:",omitempty"` + IPRange string `json:",omitempty"` + Gateway string `json:",omitempty"` + AuxAddress map[string]string `json:"AuxiliaryAddresses,omitempty"` +} + +// CreateNetwork creates a new network, returning the network instance, +// or an error in case of failure. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) CreateNetwork(opts CreateNetworkOptions) (*Network, error) { + resp, err := c.do( + "POST", + "/networks/create", + doOptions{ + data: opts, + context: opts.Context, + }, + ) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + type createNetworkResponse struct { + ID string + } + var ( + network Network + cnr createNetworkResponse + ) + if err := json.NewDecoder(resp.Body).Decode(&cnr); err != nil { + return nil, err + } + + network.Name = opts.Name + network.ID = cnr.ID + network.Driver = opts.Driver + + return &network, nil +} + +// RemoveNetwork removes a network or returns an error in case of failure. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) RemoveNetwork(id string) error { + resp, err := c.do("DELETE", "/networks/"+id, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchNetwork{ID: id} + } + return err + } + resp.Body.Close() + return nil +} + +// NetworkConnectionOptions specify parameters to the ConnectNetwork and +// DisconnectNetwork function. +// +// See https://goo.gl/RV7BJU for more details. +type NetworkConnectionOptions struct { + Container string + + // EndpointConfig is only applicable to the ConnectNetwork call + EndpointConfig *EndpointConfig `json:"EndpointConfig,omitempty"` + + // Force is only applicable to the DisconnectNetwork call + Force bool + + Context context.Context `json:"-"` +} + +// EndpointConfig stores network endpoint details +// +// See https://goo.gl/RV7BJU for more details. +type EndpointConfig struct { + IPAMConfig *EndpointIPAMConfig `json:"IPAMConfig,omitempty" yaml:"IPAMConfig,omitempty" toml:"IPAMConfig,omitempty"` + Links []string `json:"Links,omitempty" yaml:"Links,omitempty" toml:"Links,omitempty"` + Aliases []string `json:"Aliases,omitempty" yaml:"Aliases,omitempty" toml:"Aliases,omitempty"` + NetworkID string `json:"NetworkID,omitempty" yaml:"NetworkID,omitempty" toml:"NetworkID,omitempty"` + EndpointID string `json:"EndpointID,omitempty" yaml:"EndpointID,omitempty" toml:"EndpointID,omitempty"` + Gateway string `json:"Gateway,omitempty" yaml:"Gateway,omitempty" toml:"Gateway,omitempty"` + IPAddress string `json:"IPAddress,omitempty" yaml:"IPAddress,omitempty" toml:"IPAddress,omitempty"` + IPPrefixLen int `json:"IPPrefixLen,omitempty" yaml:"IPPrefixLen,omitempty" toml:"IPPrefixLen,omitempty"` + IPv6Gateway string `json:"IPv6Gateway,omitempty" yaml:"IPv6Gateway,omitempty" toml:"IPv6Gateway,omitempty"` + GlobalIPv6Address string `json:"GlobalIPv6Address,omitempty" yaml:"GlobalIPv6Address,omitempty" toml:"GlobalIPv6Address,omitempty"` + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen,omitempty" yaml:"GlobalIPv6PrefixLen,omitempty" toml:"GlobalIPv6PrefixLen,omitempty"` + MacAddress string `json:"MacAddress,omitempty" yaml:"MacAddress,omitempty" toml:"MacAddress,omitempty"` +} + +// EndpointIPAMConfig represents IPAM configurations for an +// endpoint +// +// See https://goo.gl/RV7BJU for more details. +type EndpointIPAMConfig struct { + IPv4Address string `json:",omitempty"` + IPv6Address string `json:",omitempty"` +} + +// ConnectNetwork adds a container to a network or returns an error in case of +// failure. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) ConnectNetwork(id string, opts NetworkConnectionOptions) error { + resp, err := c.do("POST", "/networks/"+id+"/connect", doOptions{ + data: opts, + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container} + } + return err + } + resp.Body.Close() + return nil +} + +// DisconnectNetwork removes a container from a network or returns an error in +// case of failure. +// +// See https://goo.gl/6GugX3 for more details. +func (c *Client) DisconnectNetwork(id string, opts NetworkConnectionOptions) error { + resp, err := c.do("POST", "/networks/"+id+"/disconnect", doOptions{data: opts}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchNetworkOrContainer{NetworkID: id, ContainerID: opts.Container} + } + return err + } + resp.Body.Close() + return nil +} + +// NoSuchNetwork is the error returned when a given network does not exist. +type NoSuchNetwork struct { + ID string +} + +func (err *NoSuchNetwork) Error() string { + return fmt.Sprintf("No such network: %s", err.ID) +} + +// NoSuchNetworkOrContainer is the error returned when a given network or +// container does not exist. +type NoSuchNetworkOrContainer struct { + NetworkID string + ContainerID string +} + +func (err *NoSuchNetworkOrContainer) Error() string { + return fmt.Sprintf("No such network (%s) or container (%s)", err.NetworkID, err.ContainerID) +} diff --git a/vendor/github.com/fsouza/go-dockerclient/node.go b/vendor/github.com/fsouza/go-dockerclient/node.go new file mode 100644 index 00000000000..84340254133 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/node.go @@ -0,0 +1,130 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// NoSuchNode is the error returned when a given node does not exist. +type NoSuchNode struct { + ID string + Err error +} + +func (err *NoSuchNode) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such node: " + err.ID +} + +// ListNodesOptions specify parameters to the ListNodes function. +// +// See http://goo.gl/3K4GwU for more details. +type ListNodesOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListNodes returns a slice of nodes matching the given criteria. +// +// See http://goo.gl/3K4GwU for more details. +func (c *Client) ListNodes(opts ListNodesOptions) ([]swarm.Node, error) { + path := "/nodes?" + queryString(opts) + resp, err := c.do("GET", path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var nodes []swarm.Node + if err := json.NewDecoder(resp.Body).Decode(&nodes); err != nil { + return nil, err + } + return nodes, nil +} + +// InspectNode returns information about a node by its ID. +// +// See http://goo.gl/WjkTOk for more details. +func (c *Client) InspectNode(id string) (*swarm.Node, error) { + resp, err := c.do("GET", "/nodes/"+id, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchNode{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var node swarm.Node + if err := json.NewDecoder(resp.Body).Decode(&node); err != nil { + return nil, err + } + return &node, nil +} + +// UpdateNodeOptions specify parameters to the NodeUpdate function. +// +// See http://goo.gl/VPBFgA for more details. +type UpdateNodeOptions struct { + swarm.NodeSpec + Version uint64 + Context context.Context +} + +// UpdateNode updates a node. +// +// See http://goo.gl/VPBFgA for more details. +func (c *Client) UpdateNode(id string, opts UpdateNodeOptions) error { + params := make(url.Values) + params.Set("version", strconv.FormatUint(opts.Version, 10)) + path := "/nodes/" + id + "/update?" + params.Encode() + resp, err := c.do("POST", path, doOptions{ + context: opts.Context, + forceJSON: true, + data: opts.NodeSpec, + }) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchNode{ID: id} + } + return err + } + resp.Body.Close() + return nil +} + +// RemoveNodeOptions specify parameters to the RemoveNode function. +// +// See http://goo.gl/0SNvYg for more details. +type RemoveNodeOptions struct { + ID string + Force bool + Context context.Context +} + +// RemoveNode removes a node. +// +// See http://goo.gl/0SNvYg for more details. +func (c *Client) RemoveNode(opts RemoveNodeOptions) error { + params := make(url.Values) + params.Set("force", strconv.FormatBool(opts.Force)) + path := "/nodes/" + opts.ID + "?" + params.Encode() + resp, err := c.do("DELETE", path, doOptions{context: opts.Context}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchNode{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/service.go b/vendor/github.com/fsouza/go-dockerclient/service.go new file mode 100644 index 00000000000..be6035bc3ed --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/service.go @@ -0,0 +1,163 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// NoSuchService is the error returned when a given service does not exist. +type NoSuchService struct { + ID string + Err error +} + +func (err *NoSuchService) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such service: " + err.ID +} + +// CreateServiceOptions specify parameters to the CreateService function. +// +// See https://goo.gl/KrVjHz for more details. +type CreateServiceOptions struct { + Auth AuthConfiguration `qs:"-"` + swarm.ServiceSpec + Context context.Context +} + +// CreateService creates a new service, returning the service instance +// or an error in case of failure. +// +// See https://goo.gl/KrVjHz for more details. +func (c *Client) CreateService(opts CreateServiceOptions) (*swarm.Service, error) { + headers, err := headersWithAuth(opts.Auth) + if err != nil { + return nil, err + } + path := "/services/create?" + queryString(opts) + resp, err := c.do("POST", path, doOptions{ + headers: headers, + data: opts.ServiceSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var service swarm.Service + if err := json.NewDecoder(resp.Body).Decode(&service); err != nil { + return nil, err + } + return &service, nil +} + +// RemoveServiceOptions encapsulates options to remove a service. +// +// See https://goo.gl/Tqrtya for more details. +type RemoveServiceOptions struct { + ID string `qs:"-"` + Context context.Context +} + +// RemoveService removes a service, returning an error in case of failure. +// +// See https://goo.gl/Tqrtya for more details. +func (c *Client) RemoveService(opts RemoveServiceOptions) error { + path := "/services/" + opts.ID + resp, err := c.do("DELETE", path, doOptions{context: opts.Context}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchService{ID: opts.ID} + } + return err + } + resp.Body.Close() + return nil +} + +// UpdateServiceOptions specify parameters to the UpdateService function. +// +// See https://goo.gl/wu3MmS for more details. +type UpdateServiceOptions struct { + swarm.ServiceSpec + Context context.Context + Version uint64 +} + +// UpdateService updates the service at ID with the options +// +// See https://goo.gl/wu3MmS for more details. +func (c *Client) UpdateService(id string, opts UpdateServiceOptions) error { + params := make(url.Values) + params.Set("version", strconv.FormatUint(opts.Version, 10)) + resp, err := c.do("POST", "/services/"+id+"/update?"+params.Encode(), doOptions{ + data: opts.ServiceSpec, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return &NoSuchService{ID: id} + } + return err + } + defer resp.Body.Close() + return nil +} + +// InspectService returns information about a service by its ID. +// +// See https://goo.gl/dHmr75 for more details. +func (c *Client) InspectService(id string) (*swarm.Service, error) { + path := "/services/" + id + resp, err := c.do("GET", path, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchService{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var service swarm.Service + if err := json.NewDecoder(resp.Body).Decode(&service); err != nil { + return nil, err + } + return &service, nil +} + +// ListServicesOptions specify parameters to the ListServices function. +// +// See https://goo.gl/DwvNMd for more details. +type ListServicesOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListServices returns a slice of services matching the given criteria. +// +// See https://goo.gl/DwvNMd for more details. +func (c *Client) ListServices(opts ListServicesOptions) ([]swarm.Service, error) { + path := "/services?" + queryString(opts) + resp, err := c.do("GET", path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var services []swarm.Service + if err := json.NewDecoder(resp.Body).Decode(&services); err != nil { + return nil, err + } + return services, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/signal.go b/vendor/github.com/fsouza/go-dockerclient/signal.go new file mode 100644 index 00000000000..16aa00388fd --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/signal.go @@ -0,0 +1,49 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +// Signal represents a signal that can be send to the container on +// KillContainer call. +type Signal int + +// These values represent all signals available on Linux, where containers will +// be running. +const ( + SIGABRT = Signal(0x6) + SIGALRM = Signal(0xe) + SIGBUS = Signal(0x7) + SIGCHLD = Signal(0x11) + SIGCLD = Signal(0x11) + SIGCONT = Signal(0x12) + SIGFPE = Signal(0x8) + SIGHUP = Signal(0x1) + SIGILL = Signal(0x4) + SIGINT = Signal(0x2) + SIGIO = Signal(0x1d) + SIGIOT = Signal(0x6) + SIGKILL = Signal(0x9) + SIGPIPE = Signal(0xd) + SIGPOLL = Signal(0x1d) + SIGPROF = Signal(0x1b) + SIGPWR = Signal(0x1e) + SIGQUIT = Signal(0x3) + SIGSEGV = Signal(0xb) + SIGSTKFLT = Signal(0x10) + SIGSTOP = Signal(0x13) + SIGSYS = Signal(0x1f) + SIGTERM = Signal(0xf) + SIGTRAP = Signal(0x5) + SIGTSTP = Signal(0x14) + SIGTTIN = Signal(0x15) + SIGTTOU = Signal(0x16) + SIGUNUSED = Signal(0x1f) + SIGURG = Signal(0x17) + SIGUSR1 = Signal(0xa) + SIGUSR2 = Signal(0xc) + SIGVTALRM = Signal(0x1a) + SIGWINCH = Signal(0x1c) + SIGXCPU = Signal(0x18) + SIGXFSZ = Signal(0x19) +) diff --git a/vendor/github.com/fsouza/go-dockerclient/swarm.go b/vendor/github.com/fsouza/go-dockerclient/swarm.go new file mode 100644 index 00000000000..6d9086a5522 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/swarm.go @@ -0,0 +1,156 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "errors" + "net/http" + "net/url" + "strconv" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +var ( + // ErrNodeAlreadyInSwarm is the error returned by InitSwarm and JoinSwarm + // when the node is already part of a Swarm. + ErrNodeAlreadyInSwarm = errors.New("node already in a Swarm") + + // ErrNodeNotInSwarm is the error returned by LeaveSwarm and UpdateSwarm + // when the node is not part of a Swarm. + ErrNodeNotInSwarm = errors.New("node is not in a Swarm") +) + +// InitSwarmOptions specify parameters to the InitSwarm function. +// See https://goo.gl/hzkgWu for more details. +type InitSwarmOptions struct { + swarm.InitRequest + Context context.Context +} + +// InitSwarm initializes a new Swarm and returns the node ID. +// See https://goo.gl/ZWyG1M for more details. +func (c *Client) InitSwarm(opts InitSwarmOptions) (string, error) { + path := "/swarm/init" + resp, err := c.do("POST", path, doOptions{ + data: opts.InitRequest, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return "", ErrNodeAlreadyInSwarm + } + return "", err + } + defer resp.Body.Close() + var response string + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return "", err + } + return response, nil +} + +// JoinSwarmOptions specify parameters to the JoinSwarm function. +// See https://goo.gl/TdhJWU for more details. +type JoinSwarmOptions struct { + swarm.JoinRequest + Context context.Context +} + +// JoinSwarm joins an existing Swarm. +// See https://goo.gl/N59IP1 for more details. +func (c *Client) JoinSwarm(opts JoinSwarmOptions) error { + path := "/swarm/join" + resp, err := c.do("POST", path, doOptions{ + data: opts.JoinRequest, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return ErrNodeAlreadyInSwarm + } + } + resp.Body.Close() + return err +} + +// LeaveSwarmOptions specify parameters to the LeaveSwarm function. +// See https://goo.gl/UWDlLg for more details. +type LeaveSwarmOptions struct { + Force bool + Context context.Context +} + +// LeaveSwarm leaves a Swarm. +// See https://goo.gl/FTX1aD for more details. +func (c *Client) LeaveSwarm(opts LeaveSwarmOptions) error { + params := make(url.Values) + params.Set("force", strconv.FormatBool(opts.Force)) + path := "/swarm/leave?" + params.Encode() + resp, err := c.do("POST", path, doOptions{ + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return ErrNodeNotInSwarm + } + } + resp.Body.Close() + return err +} + +// UpdateSwarmOptions specify parameters to the UpdateSwarm function. +// See https://goo.gl/vFbq36 for more details. +type UpdateSwarmOptions struct { + Version int + RotateWorkerToken bool + RotateManagerToken bool + Swarm swarm.Spec + Context context.Context +} + +// UpdateSwarm updates a Swarm. +// See https://goo.gl/iJFnsw for more details. +func (c *Client) UpdateSwarm(opts UpdateSwarmOptions) error { + params := make(url.Values) + params.Set("version", strconv.Itoa(opts.Version)) + params.Set("rotateWorkerToken", strconv.FormatBool(opts.RotateWorkerToken)) + params.Set("rotateManagerToken", strconv.FormatBool(opts.RotateManagerToken)) + path := "/swarm/update?" + params.Encode() + resp, err := c.do("POST", path, doOptions{ + data: opts.Swarm, + forceJSON: true, + context: opts.Context, + }) + if err != nil { + if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return ErrNodeNotInSwarm + } + } + resp.Body.Close() + return err +} + +// InspectSwarm inspects a Swarm. +// See https://goo.gl/MFwgX9 for more details. +func (c *Client) InspectSwarm(ctx context.Context) (swarm.Swarm, error) { + response := swarm.Swarm{} + resp, err := c.do("GET", "/swarm", doOptions{ + context: ctx, + }) + if err != nil { + if e, ok := err.(*Error); ok && (e.Status == http.StatusNotAcceptable || e.Status == http.StatusServiceUnavailable) { + return response, ErrNodeNotInSwarm + } + return response, err + } + defer resp.Body.Close() + err = json.NewDecoder(resp.Body).Decode(&response) + return response, err +} diff --git a/vendor/github.com/fsouza/go-dockerclient/tar.go b/vendor/github.com/fsouza/go-dockerclient/tar.go new file mode 100644 index 00000000000..be4dfa573e4 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/tar.go @@ -0,0 +1,117 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/fileutils" +) + +func createTarStream(srcPath, dockerfilePath string) (io.ReadCloser, error) { + excludes, err := parseDockerignore(srcPath) + if err != nil { + return nil, err + } + + includes := []string{"."} + + // If .dockerignore mentions .dockerignore or the Dockerfile + // then make sure we send both files over to the daemon + // because Dockerfile is, obviously, needed no matter what, and + // .dockerignore is needed to know if either one needs to be + // removed. The deamon will remove them for us, if needed, after it + // parses the Dockerfile. + // + // https://github.com/docker/docker/issues/8330 + // + forceIncludeFiles := []string{".dockerignore", dockerfilePath} + + for _, includeFile := range forceIncludeFiles { + if includeFile == "" { + continue + } + keepThem, err := fileutils.Matches(includeFile, excludes) + if err != nil { + return nil, fmt.Errorf("cannot match .dockerfile: '%s', error: %s", includeFile, err) + } + if keepThem { + includes = append(includes, includeFile) + } + } + + if err := validateContextDirectory(srcPath, excludes); err != nil { + return nil, err + } + tarOpts := &archive.TarOptions{ + ExcludePatterns: excludes, + IncludeFiles: includes, + Compression: archive.Uncompressed, + NoLchown: true, + } + return archive.TarWithOptions(srcPath, tarOpts) +} + +// validateContextDirectory checks if all the contents of the directory +// can be read and returns an error if some files can't be read. +// Symlinks which point to non-existing files don't trigger an error +func validateContextDirectory(srcPath string, excludes []string) error { + return filepath.Walk(filepath.Join(srcPath, "."), func(filePath string, f os.FileInfo, err error) error { + // skip this directory/file if it's not in the path, it won't get added to the context + if relFilePath, relErr := filepath.Rel(srcPath, filePath); relErr != nil { + return relErr + } else if skip, matchErr := fileutils.Matches(relFilePath, excludes); matchErr != nil { + return matchErr + } else if skip { + if f.IsDir() { + return filepath.SkipDir + } + return nil + } + + if err != nil { + if os.IsPermission(err) { + return fmt.Errorf("can't stat '%s'", filePath) + } + if os.IsNotExist(err) { + return nil + } + return err + } + + // skip checking if symlinks point to non-existing files, such symlinks can be useful + // also skip named pipes, because they hanging on open + if f.Mode()&(os.ModeSymlink|os.ModeNamedPipe) != 0 { + return nil + } + + if !f.IsDir() { + currentFile, err := os.Open(filePath) + if err != nil && os.IsPermission(err) { + return fmt.Errorf("no permission to read from '%s'", filePath) + } + currentFile.Close() + } + return nil + }) +} + +func parseDockerignore(root string) ([]string, error) { + var excludes []string + ignore, err := ioutil.ReadFile(path.Join(root, ".dockerignore")) + if err != nil && !os.IsNotExist(err) { + return excludes, fmt.Errorf("error reading .dockerignore: '%s'", err) + } + excludes = strings.Split(string(ignore), "\n") + + return excludes, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/task.go b/vendor/github.com/fsouza/go-dockerclient/task.go new file mode 100644 index 00000000000..b1dad4b231b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/task.go @@ -0,0 +1,70 @@ +// Copyright 2016 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "net/http" + + "github.com/docker/docker/api/types/swarm" + "golang.org/x/net/context" +) + +// NoSuchTask is the error returned when a given task does not exist. +type NoSuchTask struct { + ID string + Err error +} + +func (err *NoSuchTask) Error() string { + if err.Err != nil { + return err.Err.Error() + } + return "No such task: " + err.ID +} + +// ListTasksOptions specify parameters to the ListTasks function. +// +// See http://goo.gl/rByLzw for more details. +type ListTasksOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListTasks returns a slice of tasks matching the given criteria. +// +// See http://goo.gl/rByLzw for more details. +func (c *Client) ListTasks(opts ListTasksOptions) ([]swarm.Task, error) { + path := "/tasks?" + queryString(opts) + resp, err := c.do("GET", path, doOptions{context: opts.Context}) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var tasks []swarm.Task + if err := json.NewDecoder(resp.Body).Decode(&tasks); err != nil { + return nil, err + } + return tasks, nil +} + +// InspectTask returns information about a task by its ID. +// +// See http://goo.gl/kyziuq for more details. +func (c *Client) InspectTask(id string) (*swarm.Task, error) { + resp, err := c.do("GET", "/tasks/"+id, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, &NoSuchTask{ID: id} + } + return nil, err + } + defer resp.Body.Close() + var task swarm.Task + if err := json.NewDecoder(resp.Body).Decode(&task); err != nil { + return nil, err + } + return &task, nil +} diff --git a/vendor/github.com/fsouza/go-dockerclient/tls.go b/vendor/github.com/fsouza/go-dockerclient/tls.go new file mode 100644 index 00000000000..bb5790b5f0b --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/tls.go @@ -0,0 +1,118 @@ +// Copyright 2014 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// The content is borrowed from Docker's own source code to provide a simple +// tls based dialer + +package docker + +import ( + "crypto/tls" + "errors" + "net" + "strings" + "time" +) + +type tlsClientCon struct { + *tls.Conn + rawConn net.Conn +} + +func (c *tlsClientCon) CloseWrite() error { + // Go standard tls.Conn doesn't provide the CloseWrite() method so we do it + // on its underlying connection. + if cwc, ok := c.rawConn.(interface { + CloseWrite() error + }); ok { + return cwc.CloseWrite() + } + return nil +} + +func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) { + // We want the Timeout and Deadline values from dialer to cover the + // whole process: TCP connection and TLS handshake. This means that we + // also need to start our own timers now. + timeout := dialer.Timeout + + if !dialer.Deadline.IsZero() { + deadlineTimeout := dialer.Deadline.Sub(time.Now()) + if timeout == 0 || deadlineTimeout < timeout { + timeout = deadlineTimeout + } + } + + var errChannel chan error + + if timeout != 0 { + errChannel = make(chan error, 2) + time.AfterFunc(timeout, func() { + errChannel <- errors.New("") + }) + } + + rawConn, err := dialer.Dial(network, addr) + if err != nil { + return nil, err + } + + colonPos := strings.LastIndex(addr, ":") + if colonPos == -1 { + colonPos = len(addr) + } + hostname := addr[:colonPos] + + // If no ServerName is set, infer the ServerName + // from the hostname we're connecting to. + if config.ServerName == "" { + // Make a copy to avoid polluting argument or default. + config = copyTLSConfig(config) + config.ServerName = hostname + } + + conn := tls.Client(rawConn, config) + + if timeout == 0 { + err = conn.Handshake() + } else { + go func() { + errChannel <- conn.Handshake() + }() + + err = <-errChannel + } + + if err != nil { + rawConn.Close() + return nil, err + } + + // This is Docker difference with standard's crypto/tls package: returned a + // wrapper which holds both the TLS and raw connections. + return &tlsClientCon{conn, rawConn}, nil +} + +// this exists to silent an error message in go vet +func copyTLSConfig(cfg *tls.Config) *tls.Config { + return &tls.Config{ + Certificates: cfg.Certificates, + CipherSuites: cfg.CipherSuites, + ClientAuth: cfg.ClientAuth, + ClientCAs: cfg.ClientCAs, + ClientSessionCache: cfg.ClientSessionCache, + CurvePreferences: cfg.CurvePreferences, + InsecureSkipVerify: cfg.InsecureSkipVerify, + MaxVersion: cfg.MaxVersion, + MinVersion: cfg.MinVersion, + NameToCertificate: cfg.NameToCertificate, + NextProtos: cfg.NextProtos, + PreferServerCipherSuites: cfg.PreferServerCipherSuites, + Rand: cfg.Rand, + RootCAs: cfg.RootCAs, + ServerName: cfg.ServerName, + SessionTicketKey: cfg.SessionTicketKey, + SessionTicketsDisabled: cfg.SessionTicketsDisabled, + } +} diff --git a/vendor/github.com/fsouza/go-dockerclient/volume.go b/vendor/github.com/fsouza/go-dockerclient/volume.go new file mode 100644 index 00000000000..635ac143498 --- /dev/null +++ b/vendor/github.com/fsouza/go-dockerclient/volume.go @@ -0,0 +1,138 @@ +// Copyright 2015 go-dockerclient authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package docker + +import ( + "encoding/json" + "errors" + "net/http" + + "golang.org/x/net/context" +) + +var ( + // ErrNoSuchVolume is the error returned when the volume does not exist. + ErrNoSuchVolume = errors.New("no such volume") + + // ErrVolumeInUse is the error returned when the volume requested to be removed is still in use. + ErrVolumeInUse = errors.New("volume in use and cannot be removed") +) + +// Volume represents a volume. +// +// See https://goo.gl/FZA4BK for more details. +type Volume struct { + Name string `json:"Name" yaml:"Name" toml:"Name"` + Driver string `json:"Driver,omitempty" yaml:"Driver,omitempty" toml:"Driver,omitempty"` + Mountpoint string `json:"Mountpoint,omitempty" yaml:"Mountpoint,omitempty" toml:"Mountpoint,omitempty"` + Labels map[string]string `json:"Labels,omitempty" yaml:"Labels,omitempty" toml:"Labels,omitempty"` +} + +// ListVolumesOptions specify parameters to the ListVolumes function. +// +// See https://goo.gl/FZA4BK for more details. +type ListVolumesOptions struct { + Filters map[string][]string + Context context.Context +} + +// ListVolumes returns a list of available volumes in the server. +// +// See https://goo.gl/FZA4BK for more details. +func (c *Client) ListVolumes(opts ListVolumesOptions) ([]Volume, error) { + resp, err := c.do("GET", "/volumes?"+queryString(opts), doOptions{ + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + m := make(map[string]interface{}) + if err = json.NewDecoder(resp.Body).Decode(&m); err != nil { + return nil, err + } + var volumes []Volume + volumesJSON, ok := m["Volumes"] + if !ok { + return volumes, nil + } + data, err := json.Marshal(volumesJSON) + if err != nil { + return nil, err + } + if err := json.Unmarshal(data, &volumes); err != nil { + return nil, err + } + return volumes, nil +} + +// CreateVolumeOptions specify parameters to the CreateVolume function. +// +// See https://goo.gl/pBUbZ9 for more details. +type CreateVolumeOptions struct { + Name string + Driver string + DriverOpts map[string]string + Context context.Context `json:"-"` + Labels map[string]string +} + +// CreateVolume creates a volume on the server. +// +// See https://goo.gl/pBUbZ9 for more details. +func (c *Client) CreateVolume(opts CreateVolumeOptions) (*Volume, error) { + resp, err := c.do("POST", "/volumes/create", doOptions{ + data: opts, + context: opts.Context, + }) + if err != nil { + return nil, err + } + defer resp.Body.Close() + var volume Volume + if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil { + return nil, err + } + return &volume, nil +} + +// InspectVolume returns a volume by its name. +// +// See https://goo.gl/0g9A6i for more details. +func (c *Client) InspectVolume(name string) (*Volume, error) { + resp, err := c.do("GET", "/volumes/"+name, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok && e.Status == http.StatusNotFound { + return nil, ErrNoSuchVolume + } + return nil, err + } + defer resp.Body.Close() + var volume Volume + if err := json.NewDecoder(resp.Body).Decode(&volume); err != nil { + return nil, err + } + return &volume, nil +} + +// RemoveVolume removes a volume by its name. +// +// See https://goo.gl/79GNQz for more details. +func (c *Client) RemoveVolume(name string) error { + resp, err := c.do("DELETE", "/volumes/"+name, doOptions{}) + if err != nil { + if e, ok := err.(*Error); ok { + if e.Status == http.StatusNotFound { + return ErrNoSuchVolume + } + if e.Status == http.StatusConflict { + return ErrVolumeInUse + } + } + return nil + } + defer resp.Body.Close() + return nil +} diff --git a/vendor/github.com/hashicorp/go-cleanhttp/LICENSE b/vendor/github.com/hashicorp/go-cleanhttp/LICENSE new file mode 100644 index 00000000000..e87a115e462 --- /dev/null +++ b/vendor/github.com/hashicorp/go-cleanhttp/LICENSE @@ -0,0 +1,363 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. "Contributor" + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. "Contributor Version" + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. + +1.6. "Executable Form" + + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. + +1.8. "License" + + means this document. + +1.9. "Licensable" + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. + +1.10. "Modifications" + + means any of the following: + + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" + + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. + diff --git a/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go b/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go new file mode 100644 index 00000000000..7d8a57c2807 --- /dev/null +++ b/vendor/github.com/hashicorp/go-cleanhttp/cleanhttp.go @@ -0,0 +1,56 @@ +package cleanhttp + +import ( + "net" + "net/http" + "runtime" + "time" +) + +// DefaultTransport returns a new http.Transport with similar default values to +// http.DefaultTransport, but with idle connections and keepalives disabled. +func DefaultTransport() *http.Transport { + transport := DefaultPooledTransport() + transport.DisableKeepAlives = true + transport.MaxIdleConnsPerHost = -1 + return transport +} + +// DefaultPooledTransport returns a new http.Transport with similar default +// values to http.DefaultTransport. Do not use this for transient transports as +// it can leak file descriptors over time. Only use this for transports that +// will be re-used for the same host(s). +func DefaultPooledTransport() *http.Transport { + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1, + } + return transport +} + +// DefaultClient returns a new http.Client with similar default values to +// http.Client, but with a non-shared Transport, idle connections disabled, and +// keepalives disabled. +func DefaultClient() *http.Client { + return &http.Client{ + Transport: DefaultTransport(), + } +} + +// DefaultPooledClient returns a new http.Client with similar default values to +// http.Client, but with a shared Transport. Do not use this function for +// transient clients as it can leak file descriptors over time. Only use this +// for clients that will be re-used for the same host(s). +func DefaultPooledClient() *http.Client { + return &http.Client{ + Transport: DefaultPooledTransport(), + } +} diff --git a/vendor/github.com/hashicorp/go-cleanhttp/doc.go b/vendor/github.com/hashicorp/go-cleanhttp/doc.go new file mode 100644 index 00000000000..05841092a7b --- /dev/null +++ b/vendor/github.com/hashicorp/go-cleanhttp/doc.go @@ -0,0 +1,20 @@ +// Package cleanhttp offers convenience utilities for acquiring "clean" +// http.Transport and http.Client structs. +// +// Values set on http.DefaultClient and http.DefaultTransport affect all +// callers. This can have detrimental effects, esepcially in TLS contexts, +// where client or root certificates set to talk to multiple endpoints can end +// up displacing each other, leading to hard-to-debug issues. This package +// provides non-shared http.Client and http.Transport structs to ensure that +// the configuration will not be overwritten by other parts of the application +// or dependencies. +// +// The DefaultClient and DefaultTransport functions disable idle connections +// and keepalives. Without ensuring that idle connections are closed before +// garbage collection, short-term clients/transports can leak file descriptors, +// eventually leading to "too many open files" errors. If you will be +// connecting to the same hosts repeatedly from the same client, you can use +// DefaultPooledClient to receive a client that has connection pooling +// semantics similar to http.DefaultClient. +// +package cleanhttp diff --git a/vendor/github.com/opencontainers/go-digest/LICENSE.docs b/vendor/github.com/opencontainers/go-digest/LICENSE.docs deleted file mode 100644 index e26cd4fc8ed..00000000000 --- a/vendor/github.com/opencontainers/go-digest/LICENSE.docs +++ /dev/null @@ -1,425 +0,0 @@ -Attribution-ShareAlike 4.0 International - -======================================================================= - -Creative Commons Corporation ("Creative Commons") is not a law firm and -does not provide legal services or legal advice. Distribution of -Creative Commons public licenses does not create a lawyer-client or -other relationship. Creative Commons makes its licenses and related -information available on an "as-is" basis. Creative Commons gives no -warranties regarding its licenses, any material licensed under their -terms and conditions, or any related information. Creative Commons -disclaims all liability for damages resulting from their use to the -fullest extent possible. - -Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and -conditions that creators and other rights holders may use to share -original works of authorship and other material subject to copyright -and certain other rights specified in the public license below. The -following considerations are for informational purposes only, are not -exhaustive, and do not form part of our licenses. - - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors - - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More_considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees - -======================================================================= - -Creative Commons Attribution-ShareAlike 4.0 International Public -License - -By exercising the Licensed Rights (defined below), You accept and agree -to be bound by the terms and conditions of this Creative Commons -Attribution-ShareAlike 4.0 International Public License ("Public -License"). To the extent this Public License may be interpreted as a -contract, You are granted the Licensed Rights in consideration of Your -acceptance of these terms and conditions, and the Licensor grants You -such rights in consideration of benefits the Licensor receives from -making the Licensed Material available under these terms and -conditions. - - -Section 1 -- Definitions. - - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - - c. BY-SA Compatible License means a license listed at - creativecommons.org/compatiblelicenses, approved by Creative - Commons as essentially the equivalent of this Public License. - - d. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - - e. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - - f. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - - g. License Elements means the license attributes listed in the name - of a Creative Commons Public License. The License Elements of this - Public License are Attribution and ShareAlike. - - h. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - - i. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - - j. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - - k. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - - l. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - - m. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - - -Section 2 -- Scope. - - a. License grant. - - 1. Subject to the terms and conditions of this Public License, - the Licensor hereby grants You a worldwide, royalty-free, - non-sublicensable, non-exclusive, irrevocable license to - exercise the Licensed Rights in the Licensed Material to: - - a. reproduce and Share the Licensed Material, in whole or - in part; and - - b. produce, reproduce, and Share Adapted Material. - - 2. Exceptions and Limitations. For the avoidance of doubt, where - Exceptions and Limitations apply to Your use, this Public - License does not apply, and You do not need to comply with - its terms and conditions. - - 3. Term. The term of this Public License is specified in Section - 6(a). - - 4. Media and formats; technical modifications allowed. The - Licensor authorizes You to exercise the Licensed Rights in - all media and formats whether now known or hereafter created, - and to make technical modifications necessary to do so. The - Licensor waives and/or agrees not to assert any right or - authority to forbid You from making technical modifications - necessary to exercise the Licensed Rights, including - technical modifications necessary to circumvent Effective - Technological Measures. For purposes of this Public License, - simply making modifications authorized by this Section 2(a) - (4) never produces Adapted Material. - - 5. Downstream recipients. - - a. Offer from the Licensor -- Licensed Material. Every - recipient of the Licensed Material automatically - receives an offer from the Licensor to exercise the - Licensed Rights under the terms and conditions of this - Public License. - - b. Additional offer from the Licensor -- Adapted Material. - Every recipient of Adapted Material from You - automatically receives an offer from the Licensor to - exercise the Licensed Rights in the Adapted Material - under the conditions of the Adapter's License You apply. - - c. No downstream restrictions. You may not offer or impose - any additional or different terms or conditions on, or - apply any Effective Technological Measures to, the - Licensed Material if doing so restricts exercise of the - Licensed Rights by any recipient of the Licensed - Material. - - 6. No endorsement. Nothing in this Public License constitutes or - may be construed as permission to assert or imply that You - are, or that Your use of the Licensed Material is, connected - with, or sponsored, endorsed, or granted official status by, - the Licensor or others designated to receive attribution as - provided in Section 3(a)(1)(A)(i). - - b. Other rights. - - 1. Moral rights, such as the right of integrity, are not - licensed under this Public License, nor are publicity, - privacy, and/or other similar personality rights; however, to - the extent possible, the Licensor waives and/or agrees not to - assert any such rights held by the Licensor to the limited - extent necessary to allow You to exercise the Licensed - Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this - Public License. - - 3. To the extent possible, the Licensor waives any right to - collect royalties from You for the exercise of the Licensed - Rights, whether directly or through a collecting society - under any voluntary or waivable statutory or compulsory - licensing scheme. In all other cases the Licensor expressly - reserves any right to collect such royalties. - - -Section 3 -- License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the -following conditions. - - a. Attribution. - - 1. If You Share the Licensed Material (including in modified - form), You must: - - a. retain the following if it is supplied by the Licensor - with the Licensed Material: - - i. identification of the creator(s) of the Licensed - Material and any others designated to receive - attribution, in any reasonable manner requested by - the Licensor (including by pseudonym if - designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of - warranties; - - v. a URI or hyperlink to the Licensed Material to the - extent reasonably practicable; - - b. indicate if You modified the Licensed Material and - retain an indication of any previous modifications; and - - c. indicate the Licensed Material is licensed under this - Public License, and include the text of, or the URI or - hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any - reasonable manner based on the medium, means, and context in - which You Share the Licensed Material. For example, it may be - reasonable to satisfy the conditions by providing a URI or - hyperlink to a resource that includes the required - information. - - 3. If requested by the Licensor, You must remove any of the - information required by Section 3(a)(1)(A) to the extent - reasonably practicable. - - b. ShareAlike. - - In addition to the conditions in Section 3(a), if You Share - Adapted Material You produce, the following conditions also apply. - - 1. The Adapter's License You apply must be a Creative Commons - license with the same License Elements, this version or - later, or a BY-SA Compatible License. - - 2. You must include the text of, or the URI or hyperlink to, the - Adapter's License You apply. You may satisfy this condition - in any reasonable manner based on the medium, means, and - context in which You Share Adapted Material. - - 3. You may not offer or impose any additional or different terms - or conditions on, or apply any Effective Technological - Measures to, Adapted Material that restrict exercise of the - rights granted under the Adapter's License You apply. - - -Section 4 -- Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that -apply to Your use of the Licensed Material: - - a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database; - - b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material, - - including for purposes of Section 3(b); and - c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not -replace Your obligations under this Public License where the Licensed -Rights include other Copyright and Similar Rights. - - -Section 5 -- Disclaimer of Warranties and Limitation of Liability. - - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - - c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - - -Section 6 -- Term and Termination. - - a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. - - b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided - it is cured within 30 days of Your discovery of the - violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any - right the Licensor may have to seek remedies for Your violations - of this Public License. - - c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. - - -Section 7 -- Other Terms and Conditions. - - a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. - - -Section 8 -- Interpretation. - - a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - - c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - - d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. - - -======================================================================= - -Creative Commons is not a party to its public licenses. -Notwithstanding, Creative Commons may elect to apply one of its public -licenses to material it publishes and in those instances will be -considered the "Licensor." Except for the limited purpose of indicating -that material is shared under a Creative Commons public license or as -otherwise permitted by the Creative Commons policies published at -creativecommons.org/policies, Creative Commons does not authorize the -use of the trademark "Creative Commons" or any other trademark or logo -of Creative Commons without its prior written consent including, -without limitation, in connection with any unauthorized modifications -to any of its public licenses or any other arrangements, -understandings, or agreements concerning use of licensed material. For -the avoidance of doubt, this paragraph does not form part of the public -licenses. - -Creative Commons may be contacted at creativecommons.org. diff --git a/vendor/github.com/opencontainers/go-digest/README.md b/vendor/github.com/opencontainers/go-digest/README.md deleted file mode 100644 index 0f5a04092c4..00000000000 --- a/vendor/github.com/opencontainers/go-digest/README.md +++ /dev/null @@ -1,104 +0,0 @@ -# go-digest - -[![GoDoc](https://godoc.org/github.com/opencontainers/go-digest?status.svg)](https://godoc.org/github.com/opencontainers/go-digest) [![Go Report Card](https://goreportcard.com/badge/github.com/opencontainers/go-digest)](https://goreportcard.com/report/github.com/opencontainers/go-digest) [![Build Status](https://travis-ci.org/opencontainers/go-digest.svg?branch=master)](https://travis-ci.org/opencontainers/go-digest) - -Common digest package used across the container ecosystem. - -Please see the [godoc](https://godoc.org/github.com/opencontainers/go-digest) for more information. - -# What is a digest? - -A digest is just a hash. - -The most common use case for a digest is to create a content -identifier for use in [Content Addressable Storage](https://en.wikipedia.org/wiki/Content-addressable_storage) -systems: - -```go -id := digest.FromBytes([]byte("my content")) -``` - -In the example above, the id can be used to uniquely identify -the byte slice "my content". This allows two disparate applications -to agree on a verifiable identifier without having to trust one -another. - -An identifying digest can be verified, as follows: - -```go -if id != digest.FromBytes([]byte("my content")) { - return errors.New("the content has changed!") -} -``` - -A `Verifier` type can be used to handle cases where an `io.Reader` -makes more sense: - -```go -rd := getContent() -verifier := id.Verifier() -io.Copy(verifier, rd) - -if !verifier.Verified() { - return errors.New("the content has changed!") -} -``` - -Using [Merkle DAGs](https://en.wikipedia.org/wiki/Merkle_tree), this -can power a rich, safe, content distribution system. - -# Usage - -While the [godoc](https://godoc.org/github.com/opencontainers/go-digest) is -considered the best resource, a few important items need to be called -out when using this package. - -1. Make sure to import the hash implementations into your application - or the package will panic. You should have something like the - following in the main (or other entrypoint) of your application: - - ```go - import ( - _ "crypto/sha256" - _ "crypto/sha512" - ) - ``` - This may seem inconvenient but it allows you replace the hash - implementations with others, such as https://github.com/stevvooe/resumable. - -2. Even though `digest.Digest` may be assemable as a string, _always_ - verify your input with `digest.Parse` or use `Digest.Validate` - when accepting untrusted input. While there are measures to - avoid common problems, this will ensure you have valid digests - in the rest of your application. - -# Stability - -The Go API, at this stage, is considered stable, unless otherwise noted. - -As always, before using a package export, read the [godoc](https://godoc.org/github.com/opencontainers/go-digest). - -# Contributing - -This package is considered fairly complete. It has been in production -in thousands (millions?) of deployments and is fairly battle-hardened. -New additions will be met with skepticism. If you think there is a -missing feature, please file a bug clearly describing the problem and -the alternatives you tried before submitting a PR. - -# Reporting security issues - -Please DO NOT file a public issue, instead send your report privately to -security@opencontainers.org. - -The maintainers take security seriously. If you discover a security issue, -please bring it to their attention right away! - -If you are reporting a security issue, do not create an issue or file a pull -request on GitHub. Instead, disclose the issue responsibly by sending an email -to security@opencontainers.org (which is inhabited only by the maintainers of -the various OCI projects). - -# Copyright and license - -Copyright © 2016 Docker, Inc. All rights reserved, except as follows. Code is released under the [Apache 2.0 license](LICENSE.code). This `README.md` file and the [`CONTRIBUTING.md`](CONTRIBUTING.md) file are licensed under the Creative Commons Attribution 4.0 International License under the terms and conditions set forth in the file [`LICENSE.docs`](LICENSE.docs). You may obtain a duplicate copy of the same license, titled CC BY-SA 4.0, at http://creativecommons.org/licenses/by-sa/4.0/. diff --git a/vendor/github.com/opencontainers/selinux/README.md b/vendor/github.com/opencontainers/selinux/README.md deleted file mode 100644 index 043a9293718..00000000000 --- a/vendor/github.com/opencontainers/selinux/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# selinux - -[![GoDoc](https://godoc.org/github.com/opencontainers/selinux?status.svg)](https://godoc.org/github.com/opencontainers/selinux) [![Go Report Card](https://goreportcard.com/badge/github.com/opencontainers/selinux)](https://goreportcard.com/report/github.com/opencontainers/selinux) [![Build Status](https://travis-ci.org/opencontainers/selinux.svg?branch=master)](https://travis-ci.org/opencontainers/selinux) - -Common SELinux package used across the container ecosystem. - -Please see the [godoc](https://godoc.org/github.com/opencontainers/selinux) for more information. diff --git a/vendor/github.com/opencontainers/go-digest/LICENSE.code b/vendor/github.com/openshift/imagebuilder/LICENSE similarity index 99% rename from vendor/github.com/opencontainers/go-digest/LICENSE.code rename to vendor/github.com/openshift/imagebuilder/LICENSE index 0ea3ff81e3f..ea21aad9d49 100644 --- a/vendor/github.com/opencontainers/go-digest/LICENSE.code +++ b/vendor/github.com/openshift/imagebuilder/LICENSE @@ -176,7 +176,8 @@ END OF TERMS AND CONDITIONS - Copyright 2016 Docker, Inc. + Copyright 2013-2016 Docker, Inc. + Copyright 2016 The OpenShift Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/vendor/github.com/openshift/imagebuilder/builder.go b/vendor/github.com/openshift/imagebuilder/builder.go new file mode 100644 index 00000000000..3077fbcd919 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/builder.go @@ -0,0 +1,433 @@ +package imagebuilder + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "runtime" + "strings" + + docker "github.com/fsouza/go-dockerclient" + + "github.com/docker/docker/builder/dockerfile/command" + "github.com/docker/docker/builder/dockerfile/parser" +) + +// Copy defines a copy operation required on the container. +type Copy struct { + Src []string + Dest string + Download bool +} + +// Run defines a run operation required in the container. +type Run struct { + Shell bool + Args []string +} + +type Executor interface { + Preserve(path string) error + Copy(excludes []string, copies ...Copy) error + Run(run Run, config docker.Config) error + UnrecognizedInstruction(step *Step) error +} + +type logExecutor struct{} + +func (logExecutor) Preserve(path string) error { + log.Printf("PRESERVE %s", path) + return nil +} + +func (logExecutor) Copy(excludes []string, copies ...Copy) error { + for _, c := range copies { + log.Printf("COPY %v -> %s (download:%t)", c.Src, c.Dest, c.Download) + } + return nil +} + +func (logExecutor) Run(run Run, config docker.Config) error { + log.Printf("RUN %v %t (%v)", run.Args, run.Shell, config.Env) + return nil +} + +func (logExecutor) UnrecognizedInstruction(step *Step) error { + log.Printf("Unknown instruction: %s", strings.ToUpper(step.Command)) + return nil +} + +type noopExecutor struct{} + +func (noopExecutor) Preserve(path string) error { + return nil +} + +func (noopExecutor) Copy(excludes []string, copies ...Copy) error { + return nil +} + +func (noopExecutor) Run(run Run, config docker.Config) error { + return nil +} + +func (noopExecutor) UnrecognizedInstruction(step *Step) error { + return nil +} + +type VolumeSet []string + +func (s *VolumeSet) Add(path string) bool { + if path == "/" { + set := len(*s) != 1 || (*s)[0] != "" + *s = []string{""} + return set + } + path = strings.TrimSuffix(path, "/") + var adjusted []string + for _, p := range *s { + if p == path || strings.HasPrefix(path, p+"/") { + return false + } + if strings.HasPrefix(p, path+"/") { + continue + } + adjusted = append(adjusted, p) + } + adjusted = append(adjusted, path) + *s = adjusted + return true +} + +func (s VolumeSet) Has(path string) bool { + if path == "/" { + return len(s) == 1 && s[0] == "" + } + path = strings.TrimSuffix(path, "/") + for _, p := range s { + if p == path { + return true + } + } + return false +} + +func (s VolumeSet) Covers(path string) bool { + if path == "/" { + return len(s) == 1 && s[0] == "" + } + path = strings.TrimSuffix(path, "/") + for _, p := range s { + if p == path || strings.HasPrefix(path, p+"/") { + return true + } + } + return false +} + +var ( + LogExecutor = logExecutor{} + NoopExecutor = noopExecutor{} +) + +type Builder struct { + RunConfig docker.Config + + Env []string + Args map[string]string + CmdSet bool + Author string + + AllowedArgs map[string]bool + Volumes VolumeSet + Excludes []string + + PendingVolumes VolumeSet + PendingRuns []Run + PendingCopies []Copy + + Warnings []string +} + +func NewBuilder() *Builder { + args := make(map[string]bool) + for k, v := range builtinAllowedBuildArgs { + args[k] = v + } + return &Builder{ + Args: make(map[string]string), + AllowedArgs: args, + } +} + +func NewBuilderForReader(r io.Reader, args map[string]string) (*Builder, *parser.Node, error) { + b := NewBuilder() + b.Args = args + node, err := ParseDockerfile(r) + if err != nil { + return nil, nil, err + } + return b, node, err +} + +func NewBuilderForFile(path string, args map[string]string) (*Builder, *parser.Node, error) { + f, err := os.Open(path) + if err != nil { + return nil, nil, err + } + defer f.Close() + return NewBuilderForReader(f, args) +} + +// Step creates a new step from the current state. +func (b *Builder) Step() *Step { + dst := make([]string, len(b.Env)+len(b.RunConfig.Env)) + copy(dst, b.Env) + dst = append(dst, b.RunConfig.Env...) + dst = append(dst, b.Arguments()...) + return &Step{Env: dst} +} + +// Run executes a step, transforming the current builder and +// invoking any Copy or Run operations. noRunsRemaining is an +// optimization hint that allows the builder to avoid performing +// unnecessary work. +func (b *Builder) Run(step *Step, exec Executor, noRunsRemaining bool) error { + fn, ok := evaluateTable[step.Command] + if !ok { + return exec.UnrecognizedInstruction(step) + } + if err := fn(b, step.Args, step.Attrs, step.Flags, step.Original); err != nil { + return err + } + + copies := b.PendingCopies + b.PendingCopies = nil + runs := b.PendingRuns + b.PendingRuns = nil + + // Once a VOLUME is defined, future ADD/COPY instructions are + // all that may mutate that path. Instruct the executor to preserve + // the path. The executor must handle invalidating preserved info. + for _, path := range b.PendingVolumes { + if b.Volumes.Add(path) && !noRunsRemaining { + if err := exec.Preserve(path); err != nil { + return err + } + } + } + + if err := exec.Copy(b.Excludes, copies...); err != nil { + return err + } + for _, run := range runs { + config := b.Config() + config.Env = step.Env + if err := exec.Run(run, *config); err != nil { + return err + } + } + + return nil +} + +// RequiresStart returns true if a running container environment is necessary +// to invoke the provided commands +func (b *Builder) RequiresStart(node *parser.Node) bool { + for _, child := range node.Children { + if child.Value == command.Run { + return true + } + } + return false +} + +// Config returns a snapshot of the current RunConfig intended for +// use with a container commit. +func (b *Builder) Config() *docker.Config { + config := b.RunConfig + if config.OnBuild == nil { + config.OnBuild = []string{} + } + if config.Entrypoint == nil { + config.Entrypoint = []string{} + } + config.Image = "" + return &config +} + +// Arguments returns the currently active arguments. +func (b *Builder) Arguments() []string { + var envs []string + for key, val := range b.Args { + if _, ok := b.AllowedArgs[key]; ok { + envs = append(envs, fmt.Sprintf("%s=%s", key, val)) + } + } + return envs +} + +// ErrNoFROM is returned if the Dockerfile did not contain a FROM +// statement. +var ErrNoFROM = fmt.Errorf("no FROM statement found") + +// From returns the image this dockerfile depends on, or an error +// if no FROM is found or if multiple FROM are specified. If a +// single from is found the passed node is updated with only +// the remaining statements. The builder's RunConfig.Image field +// is set to the first From found, or left unchanged if already +// set. +func (b *Builder) From(node *parser.Node) (string, error) { + children := SplitChildren(node, command.From) + switch { + case len(children) == 0: + return "", ErrNoFROM + case len(children) > 1: + return "", fmt.Errorf("multiple FROM statements are not supported") + default: + step := b.Step() + if err := step.Resolve(children[0]); err != nil { + return "", err + } + if err := b.Run(step, NoopExecutor, false); err != nil { + return "", err + } + return b.RunConfig.Image, nil + } +} + +// FromImage updates the builder to use the provided image (resetting RunConfig +// and recording the image environment), and updates the node with any ONBUILD +// statements extracted from the parent image. +func (b *Builder) FromImage(image *docker.Image, node *parser.Node) error { + SplitChildren(node, command.From) + + b.RunConfig = *image.Config + b.Env = b.RunConfig.Env + b.RunConfig.Env = nil + + // Check to see if we have a default PATH, note that windows won't + // have one as its set by HCS + if runtime.GOOS != "windows" && !hasEnvName(b.Env, "PATH") { + b.RunConfig.Env = append(b.RunConfig.Env, "PATH="+defaultPathEnv) + } + + // Join the image onbuild statements into node + if image.Config == nil || len(image.Config.OnBuild) == 0 { + return nil + } + extra, err := ParseDockerfile(bytes.NewBufferString(strings.Join(image.Config.OnBuild, "\n"))) + if err != nil { + return err + } + for _, child := range extra.Children { + switch strings.ToUpper(child.Value) { + case "ONBUILD": + return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") + case "MAINTAINER", "FROM": + return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", child.Value) + } + } + node.Children = append(extra.Children, node.Children...) + // Since we've processed the OnBuild statements, clear them from the runconfig state. + b.RunConfig.OnBuild = nil + return nil +} + +// SplitChildren removes any children with the provided value from node +// and returns them as an array. node.Children is updated. +func SplitChildren(node *parser.Node, value string) []*parser.Node { + var split []*parser.Node + var children []*parser.Node + for _, child := range node.Children { + if child.Value == value { + split = append(split, child) + } else { + children = append(children, child) + } + } + node.Children = children + return split +} + +// StepFunc is invoked with the result of a resolved step. +type StepFunc func(*Builder, []string, map[string]bool, []string, string) error + +var evaluateTable = map[string]StepFunc{ + command.Env: env, + command.Label: label, + command.Maintainer: maintainer, + command.Add: add, + command.Copy: dispatchCopy, // copy() is a go builtin + command.From: from, + command.Onbuild: onbuild, + command.Workdir: workdir, + command.Run: run, + command.Cmd: cmd, + command.Entrypoint: entrypoint, + command.Expose: expose, + command.Volume: volume, + command.User: user, + command.StopSignal: stopSignal, + command.Arg: arg, + command.Healthcheck: healthcheck, +} + +// builtinAllowedBuildArgs is list of built-in allowed build args +var builtinAllowedBuildArgs = map[string]bool{ + "HTTP_PROXY": true, + "http_proxy": true, + "HTTPS_PROXY": true, + "https_proxy": true, + "FTP_PROXY": true, + "ftp_proxy": true, + "NO_PROXY": true, + "no_proxy": true, +} + +// ParseDockerIgnore returns a list of the excludes in the .dockerignore file. +// extracted from fsouza/go-dockerclient. +func ParseDockerignore(root string) ([]string, error) { + var excludes []string + ignore, err := ioutil.ReadFile(filepath.Join(root, ".dockerignore")) + if err != nil && !os.IsNotExist(err) { + return excludes, fmt.Errorf("error reading .dockerignore: '%s'", err) + } + return strings.Split(string(ignore), "\n"), nil +} + +// ExportEnv creates an export statement for a shell that contains all of the +// provided environment. +func ExportEnv(env []string) string { + if len(env) == 0 { + return "" + } + out := "export" + for _, e := range env { + if len(e) == 0 { + continue + } + out += " " + BashQuote(e) + } + return out + "; " +} + +// BashQuote escapes the provided string and surrounds it with double quotes. +// TODO: verify that these are all we have to escape. +func BashQuote(env string) string { + out := []rune{'"'} + for _, r := range env { + switch r { + case '$', '\\', '"': + out = append(out, '\\', r) + default: + out = append(out, r) + } + } + out = append(out, '"') + return string(out) +} diff --git a/vendor/github.com/openshift/imagebuilder/constants.go b/vendor/github.com/openshift/imagebuilder/constants.go new file mode 100644 index 00000000000..86cd2e5e27d --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/constants.go @@ -0,0 +1,13 @@ +package imagebuilder + +const ( + // in docker/system + NoBaseImageSpecifier = "scratch" + + // not yet part of our import + commandArg = "arg" + commandStopSignal = "stopsignal" + + // in docker/system + defaultPathEnv = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +) diff --git a/vendor/github.com/openshift/imagebuilder/dispatchers.go b/vendor/github.com/openshift/imagebuilder/dispatchers.go new file mode 100644 index 00000000000..3569caf0a37 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/dispatchers.go @@ -0,0 +1,528 @@ +package imagebuilder + +// This file contains the dispatchers for each command. Note that +// `nullDispatch` is not actually a command, but support for commands we parse +// but do nothing with. +// +// See evaluator.go for a higher level discussion of the whole evaluator +// package. + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "regexp" + "runtime" + "strconv" + "strings" + + docker "github.com/fsouza/go-dockerclient" + + "github.com/openshift/imagebuilder/signal" + "github.com/openshift/imagebuilder/strslice" +) + +// dispatch with no layer / parsing. This is effectively not a command. +func nullDispatch(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + return nil +} + +// ENV foo bar +// +// Sets the environment variable foo to bar, also makes interpolation +// in the dockerfile available from the next statement on via ${foo}. +// +func env(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) == 0 { + return errAtLeastOneArgument("ENV") + } + + if len(args)%2 != 0 { + // should never get here, but just in case + return errTooManyArguments("ENV") + } + + // TODO/FIXME/NOT USED + // Just here to show how to use the builder flags stuff within the + // context of a builder command. Will remove once we actually add + // a builder command to something! + /* + flBool1 := b.flags.AddBool("bool1", false) + flStr1 := b.flags.AddString("str1", "HI") + + if err := b.flags.Parse(); err != nil { + return err + } + + fmt.Printf("Bool1:%v\n", flBool1) + fmt.Printf("Str1:%v\n", flStr1) + */ + + for j := 0; j < len(args); j++ { + // name ==> args[j] + // value ==> args[j+1] + newVar := args[j] + "=" + args[j+1] + "" + gotOne := false + for i, envVar := range b.RunConfig.Env { + envParts := strings.SplitN(envVar, "=", 2) + if envParts[0] == args[j] { + b.RunConfig.Env[i] = newVar + b.Env = append([]string{newVar}, b.Env...) + gotOne = true + break + } + } + if !gotOne { + b.RunConfig.Env = append(b.RunConfig.Env, newVar) + b.Env = append([]string{newVar}, b.Env...) + } + j++ + } + + return nil +} + +// MAINTAINER some text +// +// Sets the maintainer metadata. +func maintainer(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) != 1 { + return errExactlyOneArgument("MAINTAINER") + } + b.Author = args[0] + return nil +} + +// LABEL some json data describing the image +// +// Sets the Label variable foo to bar, +// +func label(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) == 0 { + return errAtLeastOneArgument("LABEL") + } + if len(args)%2 != 0 { + // should never get here, but just in case + return errTooManyArguments("LABEL") + } + + if b.RunConfig.Labels == nil { + b.RunConfig.Labels = map[string]string{} + } + + for j := 0; j < len(args); j++ { + // name ==> args[j] + // value ==> args[j+1] + b.RunConfig.Labels[args[j]] = args[j+1] + j++ + } + return nil +} + +// ADD foo /path +// +// Add the file 'foo' to '/path'. Tarball and Remote URL (git, http) handling +// exist here. If you do not wish to have this automatic handling, use COPY. +// +func add(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) < 2 { + return errAtLeastOneArgument("ADD") + } + last := len(args) - 1 + dest := makeAbsolute(args[last], b.RunConfig.WorkingDir) + b.PendingCopies = append(b.PendingCopies, Copy{Src: args[0:last], Dest: dest, Download: true}) + return nil +} + +// COPY foo /path +// +// Same as 'ADD' but without the tar and remote url handling. +// +func dispatchCopy(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) < 2 { + return errAtLeastOneArgument("COPY") + } + last := len(args) - 1 + dest := makeAbsolute(args[last], b.RunConfig.WorkingDir) + b.PendingCopies = append(b.PendingCopies, Copy{Src: args[0:last], Dest: dest, Download: false}) + return nil +} + +// FROM imagename +// +// This sets the image the dockerfile will build on top of. +// +func from(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) != 1 { + return errExactlyOneArgument("FROM") + } + + name := args[0] + // Windows cannot support a container with no base image. + if name == NoBaseImageSpecifier { + if runtime.GOOS == "windows" { + return fmt.Errorf("Windows does not support FROM scratch") + } + } + b.RunConfig.Image = name + // TODO: handle onbuild + return nil +} + +// ONBUILD RUN echo yo +// +// ONBUILD triggers run when the image is used in a FROM statement. +// +// ONBUILD handling has a lot of special-case functionality, the heading in +// evaluator.go and comments around dispatch() in the same file explain the +// special cases. search for 'OnBuild' in internals.go for additional special +// cases. +// +func onbuild(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) == 0 { + return errAtLeastOneArgument("ONBUILD") + } + + triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0])) + switch triggerInstruction { + case "ONBUILD": + return fmt.Errorf("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") + case "MAINTAINER", "FROM": + return fmt.Errorf("%s isn't allowed as an ONBUILD trigger", triggerInstruction) + } + + original = regexp.MustCompile(`(?i)^\s*ONBUILD\s*`).ReplaceAllString(original, "") + + b.RunConfig.OnBuild = append(b.RunConfig.OnBuild, original) + return nil +} + +// WORKDIR /tmp +// +// Set the working directory for future RUN/CMD/etc statements. +// +func workdir(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) != 1 { + return errExactlyOneArgument("WORKDIR") + } + + // This is from the Dockerfile and will not necessarily be in platform + // specific semantics, hence ensure it is converted. + workdir := filepath.FromSlash(args[0]) + + if !filepath.IsAbs(workdir) { + current := filepath.FromSlash(b.RunConfig.WorkingDir) + workdir = filepath.Join(string(os.PathSeparator), current, workdir) + } + + b.RunConfig.WorkingDir = workdir + return nil +} + +// RUN some command yo +// +// run a command and commit the image. Args are automatically prepended with +// 'sh -c' under linux or 'cmd /S /C' under Windows, in the event there is +// only one argument. The difference in processing: +// +// RUN echo hi # sh -c echo hi (Linux) +// RUN echo hi # cmd /S /C echo hi (Windows) +// RUN [ "echo", "hi" ] # echo hi +// +func run(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if b.RunConfig.Image == "" { + return fmt.Errorf("Please provide a source image with `from` prior to run") + } + + args = handleJSONArgs(args, attributes) + + run := Run{Args: args} + + if !attributes["json"] { + run.Shell = true + } + b.PendingRuns = append(b.PendingRuns, run) + return nil +} + +// CMD foo +// +// Set the default command to run in the container (which may be empty). +// Argument handling is the same as RUN. +// +func cmd(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + cmdSlice := handleJSONArgs(args, attributes) + + if !attributes["json"] { + if runtime.GOOS != "windows" { + cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...) + } else { + cmdSlice = append([]string{"cmd", "/S", "/C"}, cmdSlice...) + } + } + + b.RunConfig.Cmd = strslice.StrSlice(cmdSlice) + if len(args) != 0 { + b.CmdSet = true + } + return nil +} + +// ENTRYPOINT /usr/sbin/nginx +// +// Set the entrypoint (which defaults to sh -c on linux, or cmd /S /C on Windows) to +// /usr/sbin/nginx. Will accept the CMD as the arguments to /usr/sbin/nginx. +// +// Handles command processing similar to CMD and RUN, only b.RunConfig.Entrypoint +// is initialized at NewBuilder time instead of through argument parsing. +// +func entrypoint(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + parsed := handleJSONArgs(args, attributes) + + switch { + case attributes["json"]: + // ENTRYPOINT ["echo", "hi"] + b.RunConfig.Entrypoint = strslice.StrSlice(parsed) + case len(parsed) == 0: + // ENTRYPOINT [] + b.RunConfig.Entrypoint = nil + default: + // ENTRYPOINT echo hi + if runtime.GOOS != "windows" { + b.RunConfig.Entrypoint = strslice.StrSlice{"/bin/sh", "-c", parsed[0]} + } else { + b.RunConfig.Entrypoint = strslice.StrSlice{"cmd", "/S", "/C", parsed[0]} + } + } + + // when setting the entrypoint if a CMD was not explicitly set then + // set the command to nil + if !b.CmdSet { + b.RunConfig.Cmd = nil + } + return nil +} + +// EXPOSE 6667/tcp 7000/tcp +// +// Expose ports for links and port mappings. This all ends up in +// b.RunConfig.ExposedPorts for runconfig. +// +func expose(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) == 0 { + return errAtLeastOneArgument("EXPOSE") + } + + if b.RunConfig.ExposedPorts == nil { + b.RunConfig.ExposedPorts = make(map[docker.Port]struct{}) + } + + existing := map[string]struct{}{} + for k := range b.RunConfig.ExposedPorts { + existing[k.Port()] = struct{}{} + } + + for _, port := range args { + dp := docker.Port(port) + if _, exists := existing[dp.Port()]; !exists { + b.RunConfig.ExposedPorts[docker.Port(fmt.Sprintf("%s/%s", dp.Port(), dp.Proto()))] = struct{}{} + } + } + return nil +} + +// USER foo +// +// Set the user to 'foo' for future commands and when running the +// ENTRYPOINT/CMD at container run time. +// +func user(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) != 1 { + return errExactlyOneArgument("USER") + } + + b.RunConfig.User = args[0] + return nil +} + +// VOLUME /foo +// +// Expose the volume /foo for use. Will also accept the JSON array form. +// +func volume(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) == 0 { + return errAtLeastOneArgument("VOLUME") + } + + if b.RunConfig.Volumes == nil { + b.RunConfig.Volumes = map[string]struct{}{} + } + for _, v := range args { + v = strings.TrimSpace(v) + if v == "" { + return fmt.Errorf("Volume specified can not be an empty string") + } + b.RunConfig.Volumes[v] = struct{}{} + b.PendingVolumes.Add(v) + } + return nil +} + +// STOPSIGNAL signal +// +// Set the signal that will be used to kill the container. +func stopSignal(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) != 1 { + return errExactlyOneArgument("STOPSIGNAL") + } + + sig := args[0] + _, err := signal.ParseSignal(sig) + if err != nil { + return err + } + + b.RunConfig.StopSignal = sig + return nil +} + +// HEALTHCHECK foo +// +// Set the default healthcheck command to run in the container (which may be empty). +// Argument handling is the same as RUN. +// +func healthcheck(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) == 0 { + return errAtLeastOneArgument("HEALTHCHECK") + } + typ := strings.ToUpper(args[0]) + args = args[1:] + if typ == "NONE" { + if len(args) != 0 { + return fmt.Errorf("HEALTHCHECK NONE takes no arguments") + } + test := strslice.StrSlice{typ} + b.RunConfig.Healthcheck = &docker.HealthConfig{ + Test: test, + } + } else { + if b.RunConfig.Healthcheck != nil { + oldCmd := b.RunConfig.Healthcheck.Test + if len(oldCmd) > 0 && oldCmd[0] != "NONE" { + b.Warnings = append(b.Warnings, fmt.Sprintf("Note: overriding previous HEALTHCHECK: %v\n", oldCmd)) + } + } + + healthcheck := docker.HealthConfig{} + + flags := flag.NewFlagSet("", flag.ContinueOnError) + flags.String("interval", "", "") + flags.String("timeout", "", "") + flRetries := flags.String("retries", "", "") + + if err := flags.Parse(flagArgs); err != nil { + return err + } + + switch typ { + case "CMD": + cmdSlice := handleJSONArgs(args, attributes) + if len(cmdSlice) == 0 { + return fmt.Errorf("Missing command after HEALTHCHECK CMD") + } + + if !attributes["json"] { + typ = "CMD-SHELL" + } + + healthcheck.Test = strslice.StrSlice(append([]string{typ}, cmdSlice...)) + default: + return fmt.Errorf("Unknown type %#v in HEALTHCHECK (try CMD)", typ) + } + + interval, err := parseOptInterval(flags.Lookup("interval")) + if err != nil { + return err + } + healthcheck.Interval = interval + + timeout, err := parseOptInterval(flags.Lookup("timeout")) + if err != nil { + return err + } + healthcheck.Timeout = timeout + + if *flRetries != "" { + retries, err := strconv.ParseInt(*flRetries, 10, 32) + if err != nil { + return err + } + if retries < 1 { + return fmt.Errorf("--retries must be at least 1 (not %d)", retries) + } + healthcheck.Retries = int(retries) + } else { + healthcheck.Retries = 0 + } + b.RunConfig.Healthcheck = &healthcheck + } + + return nil +} + +// ARG name[=value] +// +// Adds the variable foo to the trusted list of variables that can be passed +// to builder using the --build-arg flag for expansion/subsitution or passing to 'run'. +// Dockerfile author may optionally set a default value of this variable. +func arg(b *Builder, args []string, attributes map[string]bool, flagArgs []string, original string) error { + if len(args) != 1 { + return fmt.Errorf("ARG requires exactly one argument definition") + } + + var ( + name string + value string + hasDefault bool + ) + + arg := args[0] + // 'arg' can just be a name or name-value pair. Note that this is different + // from 'env' that handles the split of name and value at the parser level. + // The reason for doing it differently for 'arg' is that we support just + // defining an arg and not assign it a value (while 'env' always expects a + // name-value pair). If possible, it will be good to harmonize the two. + if strings.Contains(arg, "=") { + parts := strings.SplitN(arg, "=", 2) + name = parts[0] + value = parts[1] + hasDefault = true + } else { + name = arg + hasDefault = false + } + // add the arg to allowed list of build-time args from this step on. + b.AllowedArgs[name] = true + + // If there is a default value associated with this arg then add it to the + // b.buildArgs if one is not already passed to the builder. The args passed + // to builder override the default value of 'arg'. + if _, ok := b.Args[name]; !ok && hasDefault { + b.Args[name] = value + } + + return nil +} + +func errAtLeastOneArgument(command string) error { + return fmt.Errorf("%s requires at least one argument", command) +} + +func errExactlyOneArgument(command string) error { + return fmt.Errorf("%s requires exactly one argument", command) +} + +func errTooManyArguments(command string) error { + return fmt.Errorf("Bad input to %s, too many arguments", command) +} diff --git a/vendor/github.com/openshift/imagebuilder/doc.go b/vendor/github.com/openshift/imagebuilder/doc.go new file mode 100644 index 00000000000..97028ffc849 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/doc.go @@ -0,0 +1,6 @@ +// Package builder uses code from github.com/docker/docker/builder/* to implement +// a Docker builder that does not create individual layers, but instead creates a +// single layer. +// +// TODO: full windows support +package imagebuilder diff --git a/vendor/github.com/openshift/imagebuilder/evaluator.go b/vendor/github.com/openshift/imagebuilder/evaluator.go new file mode 100644 index 00000000000..885cd830dc5 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/evaluator.go @@ -0,0 +1,160 @@ +package imagebuilder + +import ( + "fmt" + "io" + "strings" + + "github.com/docker/docker/builder/dockerfile/command" + "github.com/docker/docker/builder/dockerfile/parser" +) + +// ParseDockerfile parses the provided stream as a canonical Dockerfile +func ParseDockerfile(r io.Reader) (*parser.Node, error) { + d := &parser.Directive{} + if err := parser.SetEscapeToken(parser.DefaultEscapeToken, d); err != nil { + return nil, err + } + return parser.Parse(r, d) +} + +// Environment variable interpolation will happen on these statements only. +var replaceEnvAllowed = map[string]bool{ + command.Env: true, + command.Label: true, + command.Add: true, + command.Copy: true, + command.Workdir: true, + command.Expose: true, + command.Volume: true, + command.User: true, + commandStopSignal: true, + commandArg: true, +} + +// Certain commands are allowed to have their args split into more +// words after env var replacements. Meaning: +// ENV foo="123 456" +// EXPOSE $foo +// should result in the same thing as: +// EXPOSE 123 456 +// and not treat "123 456" as a single word. +// Note that: EXPOSE "$foo" and EXPOSE $foo are not the same thing. +// Quotes will cause it to still be treated as single word. +var allowWordExpansion = map[string]bool{ + command.Expose: true, +} + +// Step represents the input Env and the output command after all +// post processing of the command arguments is done. +type Step struct { + Env []string + + Command string + Args []string + Flags []string + Attrs map[string]bool + Message string + Original string +} + +// Resolve transforms a parsed Dockerfile line into a command to execute, +// resolving any arguments. +// +// Almost all nodes will have this structure: +// Child[Node, Node, Node] where Child is from parser.Node.Children and each +// node comes from parser.Node.Next. This forms a "line" with a statement and +// arguments and we process them in this normalized form by hitting +// evaluateTable with the leaf nodes of the command and the Builder object. +// +// ONBUILD is a special case; in this case the parser will emit: +// Child[Node, Child[Node, Node...]] where the first node is the literal +// "onbuild" and the child entrypoint is the command of the ONBUILD statement, +// such as `RUN` in ONBUILD RUN foo. There is special case logic in here to +// deal with that, at least until it becomes more of a general concern with new +// features. +func (b *Step) Resolve(ast *parser.Node) error { + cmd := ast.Value + upperCasedCmd := strings.ToUpper(cmd) + + // To ensure the user is given a decent error message if the platform + // on which the daemon is running does not support a builder command. + if err := platformSupports(strings.ToLower(cmd)); err != nil { + return err + } + + attrs := ast.Attributes + original := ast.Original + flags := ast.Flags + strList := []string{} + msg := upperCasedCmd + + if len(ast.Flags) > 0 { + msg += " " + strings.Join(ast.Flags, " ") + } + + if cmd == "onbuild" { + if ast.Next == nil { + return fmt.Errorf("ONBUILD requires at least one argument") + } + ast = ast.Next.Children[0] + strList = append(strList, ast.Value) + msg += " " + ast.Value + + if len(ast.Flags) > 0 { + msg += " " + strings.Join(ast.Flags, " ") + } + + } + + // count the number of nodes that we are going to traverse first + // so we can pre-create the argument and message array. This speeds up the + // allocation of those list a lot when they have a lot of arguments + cursor := ast + var n int + for cursor.Next != nil { + cursor = cursor.Next + n++ + } + msgList := make([]string, n) + + var i int + envs := b.Env + for ast.Next != nil { + ast = ast.Next + var str string + str = ast.Value + if replaceEnvAllowed[cmd] { + var err error + var words []string + + if allowWordExpansion[cmd] { + words, err = ProcessWords(str, envs) + if err != nil { + return err + } + strList = append(strList, words...) + } else { + str, err = ProcessWord(str, envs) + if err != nil { + return err + } + strList = append(strList, str) + } + } else { + strList = append(strList, str) + } + msgList[i] = ast.Value + i++ + } + + msg += " " + strings.Join(msgList, " ") + + b.Message = msg + b.Command = cmd + b.Args = strList + b.Original = original + b.Attrs = attrs + b.Flags = flags + return nil +} diff --git a/vendor/github.com/openshift/imagebuilder/internals.go b/vendor/github.com/openshift/imagebuilder/internals.go new file mode 100644 index 00000000000..9a8005bfc27 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/internals.go @@ -0,0 +1,83 @@ +package imagebuilder + +import ( + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "time" +) + +// hasEnvName returns true if the provided environment contains the named ENV var. +func hasEnvName(env []string, name string) bool { + for _, e := range env { + if strings.HasPrefix(e, name+"=") { + return true + } + } + return false +} + +// platformSupports is a short-term function to give users a quality error +// message if a Dockerfile uses a command not supported on the platform. +func platformSupports(command string) error { + if runtime.GOOS != "windows" { + return nil + } + switch command { + case "expose", "user", "stopsignal", "arg": + return fmt.Errorf("The daemon on this platform does not support the command '%s'", command) + } + return nil +} + +func handleJSONArgs(args []string, attributes map[string]bool) []string { + if len(args) == 0 { + return []string{} + } + + if attributes != nil && attributes["json"] { + return args + } + + // literal string command, not an exec array + return []string{strings.Join(args, " ")} +} + +// makeAbsolute ensures that the provided path is absolute. +func makeAbsolute(dest, workingDir string) string { + // Twiddle the destination when its a relative path - meaning, make it + // relative to the WORKINGDIR + if !filepath.IsAbs(dest) { + hasSlash := strings.HasSuffix(dest, string(os.PathSeparator)) || strings.HasSuffix(dest, string(os.PathSeparator)+".") + dest = filepath.Join(string(os.PathSeparator), filepath.FromSlash(workingDir), dest) + + // Make sure we preserve any trailing slash + if hasSlash { + dest += string(os.PathSeparator) + } + } + return dest +} + +// parseOptInterval(flag) is the duration of flag.Value, or 0 if +// empty. An error is reported if the value is given and is not positive. +func parseOptInterval(f *flag.Flag) (time.Duration, error) { + if f == nil { + return 0, fmt.Errorf("No flag defined") + } + s := f.Value.String() + if s == "" { + return 0, nil + } + d, err := time.ParseDuration(s) + if err != nil { + return 0, err + } + if d <= 0 { + return 0, fmt.Errorf("Interval %#v must be positive", f.Name) + } + return d, nil +} diff --git a/vendor/github.com/openshift/imagebuilder/shell_parser.go b/vendor/github.com/openshift/imagebuilder/shell_parser.go new file mode 100644 index 00000000000..1b31dbc85c9 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/shell_parser.go @@ -0,0 +1,314 @@ +package imagebuilder + +// This will take a single word and an array of env variables and +// process all quotes (" and ') as well as $xxx and ${xxx} env variable +// tokens. Tries to mimic bash shell process. +// It doesn't support all flavors of ${xx:...} formats but new ones can +// be added by adding code to the "special ${} format processing" section + +import ( + "fmt" + "strings" + "text/scanner" + "unicode" +) + +type shellWord struct { + word string + scanner scanner.Scanner + envs []string + pos int +} + +// ProcessWord will use the 'env' list of environment variables, +// and replace any env var references in 'word'. +func ProcessWord(word string, env []string) (string, error) { + sw := &shellWord{ + word: word, + envs: env, + pos: 0, + } + sw.scanner.Init(strings.NewReader(word)) + word, _, err := sw.process() + return word, err +} + +// ProcessWords will use the 'env' list of environment variables, +// and replace any env var references in 'word' then it will also +// return a slice of strings which represents the 'word' +// split up based on spaces - taking into account quotes. Note that +// this splitting is done **after** the env var substitutions are done. +// Note, each one is trimmed to remove leading and trailing spaces (unless +// they are quoted", but ProcessWord retains spaces between words. +func ProcessWords(word string, env []string) ([]string, error) { + sw := &shellWord{ + word: word, + envs: env, + pos: 0, + } + sw.scanner.Init(strings.NewReader(word)) + _, words, err := sw.process() + return words, err +} + +func (sw *shellWord) process() (string, []string, error) { + return sw.processStopOn(scanner.EOF) +} + +type wordsStruct struct { + word string + words []string + inWord bool +} + +func (w *wordsStruct) addChar(ch rune) { + if unicode.IsSpace(ch) && w.inWord { + if len(w.word) != 0 { + w.words = append(w.words, w.word) + w.word = "" + w.inWord = false + } + } else if !unicode.IsSpace(ch) { + w.addRawChar(ch) + } +} + +func (w *wordsStruct) addRawChar(ch rune) { + w.word += string(ch) + w.inWord = true +} + +func (w *wordsStruct) addString(str string) { + var scan scanner.Scanner + scan.Init(strings.NewReader(str)) + for scan.Peek() != scanner.EOF { + w.addChar(scan.Next()) + } +} + +func (w *wordsStruct) addRawString(str string) { + w.word += str + w.inWord = true +} + +func (w *wordsStruct) getWords() []string { + if len(w.word) > 0 { + w.words = append(w.words, w.word) + + // Just in case we're called again by mistake + w.word = "" + w.inWord = false + } + return w.words +} + +// Process the word, starting at 'pos', and stop when we get to the +// end of the word or the 'stopChar' character +func (sw *shellWord) processStopOn(stopChar rune) (string, []string, error) { + var result string + var words wordsStruct + + var charFuncMapping = map[rune]func() (string, error){ + '\'': sw.processSingleQuote, + '"': sw.processDoubleQuote, + '$': sw.processDollar, + } + + for sw.scanner.Peek() != scanner.EOF { + ch := sw.scanner.Peek() + + if stopChar != scanner.EOF && ch == stopChar { + sw.scanner.Next() + break + } + if fn, ok := charFuncMapping[ch]; ok { + // Call special processing func for certain chars + tmp, err := fn() + if err != nil { + return "", []string{}, err + } + result += tmp + + if ch == rune('$') { + words.addString(tmp) + } else { + words.addRawString(tmp) + } + } else { + // Not special, just add it to the result + ch = sw.scanner.Next() + + if ch == '\\' { + // '\' escapes, except end of line + + ch = sw.scanner.Next() + + if ch == scanner.EOF { + break + } + + words.addRawChar(ch) + } else { + words.addChar(ch) + } + + result += string(ch) + } + } + + return result, words.getWords(), nil +} + +func (sw *shellWord) processSingleQuote() (string, error) { + // All chars between single quotes are taken as-is + // Note, you can't escape ' + var result string + + sw.scanner.Next() + + for { + ch := sw.scanner.Next() + if ch == '\'' || ch == scanner.EOF { + break + } + result += string(ch) + } + + return result, nil +} + +func (sw *shellWord) processDoubleQuote() (string, error) { + // All chars up to the next " are taken as-is, even ', except any $ chars + // But you can escape " with a \ + var result string + + sw.scanner.Next() + + for sw.scanner.Peek() != scanner.EOF { + ch := sw.scanner.Peek() + if ch == '"' { + sw.scanner.Next() + break + } + if ch == '$' { + tmp, err := sw.processDollar() + if err != nil { + return "", err + } + result += tmp + } else { + ch = sw.scanner.Next() + if ch == '\\' { + chNext := sw.scanner.Peek() + + if chNext == scanner.EOF { + // Ignore \ at end of word + continue + } + + if chNext == '"' || chNext == '$' { + // \" and \$ can be escaped, all other \'s are left as-is + ch = sw.scanner.Next() + } + } + result += string(ch) + } + } + + return result, nil +} + +func (sw *shellWord) processDollar() (string, error) { + sw.scanner.Next() + ch := sw.scanner.Peek() + if ch == '{' { + sw.scanner.Next() + name := sw.processName() + ch = sw.scanner.Peek() + if ch == '}' { + // Normal ${xx} case + sw.scanner.Next() + return sw.getEnv(name), nil + } + if ch == ':' { + // Special ${xx:...} format processing + // Yes it allows for recursive $'s in the ... spot + + sw.scanner.Next() // skip over : + modifier := sw.scanner.Next() + + word, _, err := sw.processStopOn('}') + if err != nil { + return "", err + } + + // Grab the current value of the variable in question so we + // can use to to determine what to do based on the modifier + newValue := sw.getEnv(name) + + switch modifier { + case '+': + if newValue != "" { + newValue = word + } + return newValue, nil + + case '-': + if newValue == "" { + newValue = word + } + return newValue, nil + + default: + return "", fmt.Errorf("Unsupported modifier (%c) in substitution: %s", modifier, sw.word) + } + } + return "", fmt.Errorf("Missing ':' in substitution: %s", sw.word) + } + // $xxx case + name := sw.processName() + if name == "" { + return "$", nil + } + return sw.getEnv(name), nil +} + +func (sw *shellWord) processName() string { + // Read in a name (alphanumeric or _) + // If it starts with a numeric then just return $# + var name string + + for sw.scanner.Peek() != scanner.EOF { + ch := sw.scanner.Peek() + if len(name) == 0 && unicode.IsDigit(ch) { + ch = sw.scanner.Next() + return string(ch) + } + if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) && ch != '_' { + break + } + ch = sw.scanner.Next() + name += string(ch) + } + + return name +} + +func (sw *shellWord) getEnv(name string) string { + for _, env := range sw.envs { + i := strings.Index(env, "=") + if i < 0 { + if name == env { + // Should probably never get here, but just in case treat + // it like "var" and "var=" are the same + return "" + } + continue + } + if name != env[:i] { + continue + } + return env[i+1:] + } + return "" +} diff --git a/vendor/github.com/openshift/imagebuilder/signal/signal.go b/vendor/github.com/openshift/imagebuilder/signal/signal.go new file mode 100644 index 00000000000..fb7d2e65cb1 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/signal/signal.go @@ -0,0 +1,37 @@ +// Package signal provides helper functions for dealing with signals across +// various operating systems. +package signal + +import ( + "fmt" + "strconv" + "strings" + "syscall" +) + +// ParseSignal translates a string to a valid syscall signal. +// It returns an error if the signal map doesn't include the given signal. +func ParseSignal(rawSignal string) (syscall.Signal, error) { + s, err := strconv.Atoi(rawSignal) + if err == nil { + if s == 0 { + return -1, fmt.Errorf("Invalid signal: %s", rawSignal) + } + return syscall.Signal(s), nil + } + signal, ok := SignalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")] + if !ok { + return -1, fmt.Errorf("Invalid signal: %s", rawSignal) + } + return signal, nil +} + +// ValidSignalForPlatform returns true if a signal is valid on the platform +func ValidSignalForPlatform(sig syscall.Signal) bool { + for _, v := range SignalMap { + if v == sig { + return true + } + } + return false +} diff --git a/vendor/github.com/openshift/imagebuilder/signal/signal_darwin.go b/vendor/github.com/openshift/imagebuilder/signal/signal_darwin.go new file mode 100644 index 00000000000..946de87e940 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/signal/signal_darwin.go @@ -0,0 +1,41 @@ +package signal + +import ( + "syscall" +) + +// SignalMap is a map of Darwin signals. +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUG": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "EMT": syscall.SIGEMT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INFO": syscall.SIGINFO, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "PROF": syscall.SIGPROF, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} diff --git a/vendor/github.com/openshift/imagebuilder/signal/signal_freebsd.go b/vendor/github.com/openshift/imagebuilder/signal/signal_freebsd.go new file mode 100644 index 00000000000..6b9569bb754 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/signal/signal_freebsd.go @@ -0,0 +1,43 @@ +package signal + +import ( + "syscall" +) + +// SignalMap is a map of FreeBSD signals. +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUF": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CONT": syscall.SIGCONT, + "EMT": syscall.SIGEMT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INFO": syscall.SIGINFO, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "LWP": syscall.SIGLWP, + "PIPE": syscall.SIGPIPE, + "PROF": syscall.SIGPROF, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "THR": syscall.SIGTHR, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, +} diff --git a/vendor/github.com/openshift/imagebuilder/signal/signal_linux.go b/vendor/github.com/openshift/imagebuilder/signal/signal_linux.go new file mode 100644 index 00000000000..d418cbe9e36 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/signal/signal_linux.go @@ -0,0 +1,80 @@ +package signal + +import ( + "syscall" +) + +const ( + sigrtmin = 34 + sigrtmax = 64 +) + +// SignalMap is a map of Linux signals. +var SignalMap = map[string]syscall.Signal{ + "ABRT": syscall.SIGABRT, + "ALRM": syscall.SIGALRM, + "BUS": syscall.SIGBUS, + "CHLD": syscall.SIGCHLD, + "CLD": syscall.SIGCLD, + "CONT": syscall.SIGCONT, + "FPE": syscall.SIGFPE, + "HUP": syscall.SIGHUP, + "ILL": syscall.SIGILL, + "INT": syscall.SIGINT, + "IO": syscall.SIGIO, + "IOT": syscall.SIGIOT, + "KILL": syscall.SIGKILL, + "PIPE": syscall.SIGPIPE, + "POLL": syscall.SIGPOLL, + "PROF": syscall.SIGPROF, + "PWR": syscall.SIGPWR, + "QUIT": syscall.SIGQUIT, + "SEGV": syscall.SIGSEGV, + "STKFLT": syscall.SIGSTKFLT, + "STOP": syscall.SIGSTOP, + "SYS": syscall.SIGSYS, + "TERM": syscall.SIGTERM, + "TRAP": syscall.SIGTRAP, + "TSTP": syscall.SIGTSTP, + "TTIN": syscall.SIGTTIN, + "TTOU": syscall.SIGTTOU, + "UNUSED": syscall.SIGUNUSED, + "URG": syscall.SIGURG, + "USR1": syscall.SIGUSR1, + "USR2": syscall.SIGUSR2, + "VTALRM": syscall.SIGVTALRM, + "WINCH": syscall.SIGWINCH, + "XCPU": syscall.SIGXCPU, + "XFSZ": syscall.SIGXFSZ, + "RTMIN": sigrtmin, + "RTMIN+1": sigrtmin + 1, + "RTMIN+2": sigrtmin + 2, + "RTMIN+3": sigrtmin + 3, + "RTMIN+4": sigrtmin + 4, + "RTMIN+5": sigrtmin + 5, + "RTMIN+6": sigrtmin + 6, + "RTMIN+7": sigrtmin + 7, + "RTMIN+8": sigrtmin + 8, + "RTMIN+9": sigrtmin + 9, + "RTMIN+10": sigrtmin + 10, + "RTMIN+11": sigrtmin + 11, + "RTMIN+12": sigrtmin + 12, + "RTMIN+13": sigrtmin + 13, + "RTMIN+14": sigrtmin + 14, + "RTMIN+15": sigrtmin + 15, + "RTMAX-14": sigrtmax - 14, + "RTMAX-13": sigrtmax - 13, + "RTMAX-12": sigrtmax - 12, + "RTMAX-11": sigrtmax - 11, + "RTMAX-10": sigrtmax - 10, + "RTMAX-9": sigrtmax - 9, + "RTMAX-8": sigrtmax - 8, + "RTMAX-7": sigrtmax - 7, + "RTMAX-6": sigrtmax - 6, + "RTMAX-5": sigrtmax - 5, + "RTMAX-4": sigrtmax - 4, + "RTMAX-3": sigrtmax - 3, + "RTMAX-2": sigrtmax - 2, + "RTMAX-1": sigrtmax - 1, + "RTMAX": sigrtmax, +} diff --git a/vendor/github.com/openshift/imagebuilder/signal/signal_unix.go b/vendor/github.com/openshift/imagebuilder/signal/signal_unix.go new file mode 100644 index 00000000000..d4fea931d16 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/signal/signal_unix.go @@ -0,0 +1,19 @@ +// +build !windows + +package signal + +import ( + "syscall" +) + +// Signals used in api/client (no windows equivalent, use +// invalid signals so they don't get handled) + +const ( + // SIGCHLD is a signal sent to a process when a child process terminates, is interrupted, or resumes after being interrupted. + SIGCHLD = syscall.SIGCHLD + // SIGWINCH is a signal sent to a process when its controlling terminal changes its size + SIGWINCH = syscall.SIGWINCH + // DefaultStopSignal is the syscall signal used to stop a container in unix systems. + DefaultStopSignal = "SIGTERM" +) diff --git a/vendor/github.com/openshift/imagebuilder/signal/signal_unsupported.go b/vendor/github.com/openshift/imagebuilder/signal/signal_unsupported.go new file mode 100644 index 00000000000..161ba27397d --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/signal/signal_unsupported.go @@ -0,0 +1,10 @@ +// +build !linux,!darwin,!freebsd,!windows + +package signal + +import ( + "syscall" +) + +// SignalMap is an empty map of signals for unsupported platform. +var SignalMap = map[string]syscall.Signal{} diff --git a/vendor/github.com/openshift/imagebuilder/signal/signal_windows.go b/vendor/github.com/openshift/imagebuilder/signal/signal_windows.go new file mode 100644 index 00000000000..c80a951c501 --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/signal/signal_windows.go @@ -0,0 +1,27 @@ +// +build windows + +package signal + +import ( + "syscall" +) + +// Signals used in api/client (no windows equivalent, use +// invalid signals so they don't get handled) +const ( + SIGCHLD = syscall.Signal(0xff) + SIGWINCH = syscall.Signal(0xff) + // DefaultStopSignal is the syscall signal used to stop a container in windows systems. + DefaultStopSignal = "15" +) + +// SignalMap is a map of "supported" signals. As per the comment in GOLang's +// ztypes_windows.go: "More invented values for signals". Windows doesn't +// really support signals in any way, shape or form that Unix does. +// +// We have these so that docker kill can be used to gracefully (TERM) and +// forcibly (KILL) terminate a container on Windows. +var SignalMap = map[string]syscall.Signal{ + "KILL": syscall.SIGKILL, + "TERM": syscall.SIGTERM, +} diff --git a/vendor/github.com/openshift/imagebuilder/strslice/strslice.go b/vendor/github.com/openshift/imagebuilder/strslice/strslice.go new file mode 100644 index 00000000000..bad493fb89f --- /dev/null +++ b/vendor/github.com/openshift/imagebuilder/strslice/strslice.go @@ -0,0 +1,30 @@ +package strslice + +import "encoding/json" + +// StrSlice represents a string or an array of strings. +// We need to override the json decoder to accept both options. +type StrSlice []string + +// UnmarshalJSON decodes the byte slice whether it's a string or an array of +// strings. This method is needed to implement json.Unmarshaler. +func (e *StrSlice) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + // With no input, we preserve the existing value by returning nil and + // leaving the target alone. This allows defining default values for + // the type. + return nil + } + + p := make([]string, 0, 1) + if err := json.Unmarshal(b, &p); err != nil { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + p = append(p, s) + } + + *e = p + return nil +} diff --git a/vendor/golang.org/x/text/README b/vendor/golang.org/x/text/README deleted file mode 100644 index 4826fe8fb6a..00000000000 --- a/vendor/golang.org/x/text/README +++ /dev/null @@ -1,23 +0,0 @@ -This repository holds supplementary Go libraries for text processing, many involving Unicode. - -To submit changes to this repository, see http://golang.org/doc/contribute.html. - -To generate the tables in this repository (except for the encoding tables), -run go generate from this directory. By default tables are generated for the -Unicode version in core and the CLDR version defined in -golang.org/x/text/unicode/cldr. - -Running go generate will as a side effect create a DATA subdirectory in this -directory which holds all files that are used as a source for generating the -tables. This directory will also serve as a cache. - -Run - - go test ./... - -from this directory to run all tests. Add the "-tags icu" flag to also run -ICU conformance tests (if available). This requires that you have the correct -ICU version installed on your system. - -TODO: -- updating unversioned source files. \ No newline at end of file diff --git a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml b/vendor/gopkg.in/yaml.v2/LICENSE.libyaml deleted file mode 100644 index 8da58fbf6f8..00000000000 --- a/vendor/gopkg.in/yaml.v2/LICENSE.libyaml +++ /dev/null @@ -1,31 +0,0 @@ -The following files were ported to Go from C files of libyaml, and thus -are still covered by their original copyright and license: - - apic.go - emitterc.go - parserc.go - readerc.go - scannerc.go - writerc.go - yamlh.go - yamlprivateh.go - -Copyright (c) 2006 Kirill Simonov - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE.