Merge pull request #11 from tiborvass/update-cli

Update cli folder with newer changes from moby/moby
This commit is contained in:
Tibor Vass 2017-05-05 15:04:48 -07:00 committed by GitHub
commit a6feb55a48
109 changed files with 2412 additions and 3301 deletions

View File

@ -39,7 +39,9 @@ type Cli interface {
Out() *OutStream Out() *OutStream
Err() io.Writer Err() io.Writer
In() *InStream In() *InStream
SetIn(in *InStream)
ConfigFile() *configfile.ConfigFile ConfigFile() *configfile.ConfigFile
CredentialsStore(serverAddress string) credentials.Store
} }
// DockerCli is an instance the docker command line client. // DockerCli is an instance the docker command line client.
@ -75,6 +77,11 @@ func (cli *DockerCli) Err() io.Writer {
return cli.err return cli.err
} }
// SetIn sets the reader used for stdin
func (cli *DockerCli) SetIn(in *InStream) {
cli.in = in
}
// In returns the reader used for stdin // In returns the reader used for stdin
func (cli *DockerCli) In() *InStream { func (cli *DockerCli) In() *InStream {
return cli.in return cli.in

View File

@ -118,7 +118,6 @@ type containerOptions struct {
runtime string runtime string
autoRemove bool autoRemove bool
init bool init bool
initPath string
Image string Image string
Args []string Args []string
@ -230,10 +229,10 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
// Health-checking // Health-checking
flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health") flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ns|us|ms|s|m|h) (default 0s)") flags.DurationVar(&copts.healthInterval, "health-interval", 0, "Time between running the check (ms|s|m|h) (default 0s)")
flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy") flags.IntVar(&copts.healthRetries, "health-retries", 0, "Consecutive failures needed to report unhealthy")
flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ns|us|ms|s|m|h) (default 0s)") flags.DurationVar(&copts.healthTimeout, "health-timeout", 0, "Maximum time to allow one check to run (ms|s|m|h) (default 0s)")
flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ns|us|ms|s|m|h) (default 0s)") flags.DurationVar(&copts.healthStartPeriod, "health-start-period", 0, "Start period for the container to initialize before starting health-retries countdown (ms|s|m|h) (default 0s)")
flags.SetAnnotation("health-start-period", "version", []string{"1.29"}) flags.SetAnnotation("health-start-period", "version", []string{"1.29"})
flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK") flags.BoolVar(&copts.noHealthcheck, "no-healthcheck", false, "Disable any container-specified HEALTHCHECK")
@ -284,8 +283,6 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes") flags.BoolVar(&copts.init, "init", false, "Run an init inside the container that forwards signals and reaps processes")
flags.SetAnnotation("init", "version", []string{"1.25"}) flags.SetAnnotation("init", "version", []string{"1.25"})
flags.StringVar(&copts.initPath, "init-path", "", "Path to the docker-init binary")
flags.SetAnnotation("init-path", "version", []string{"1.25"})
return copts return copts
} }

View File

@ -0,0 +1,67 @@
package formatter
import (
"strconv"
)
const (
defaultStackTableFormat = "table {{.Name}}\t{{.Services}}"
stackServicesHeader = "SERVICES"
)
// Stack contains deployed stack information.
type Stack struct {
// Name is the name of the stack
Name string
// Services is the number of the services
Services int
}
// NewStackFormat returns a format for use with a stack Context
func NewStackFormat(source string) Format {
switch source {
case TableFormatKey:
return defaultStackTableFormat
}
return Format(source)
}
// StackWrite writes formatted stacks using the Context
func StackWrite(ctx Context, stacks []*Stack) error {
render := func(format func(subContext subContext) error) error {
for _, stack := range stacks {
if err := format(&stackContext{s: stack}); err != nil {
return err
}
}
return nil
}
return ctx.Write(newStackContext(), render)
}
type stackContext struct {
HeaderContext
s *Stack
}
func newStackContext() *stackContext {
stackCtx := stackContext{}
stackCtx.header = map[string]string{
"Name": nameHeader,
"Services": stackServicesHeader,
}
return &stackCtx
}
func (s *stackContext) MarshalJSON() ([]byte, error) {
return marshalJSON(s)
}
func (s *stackContext) Name() string {
return s.s.Name
}
func (s *stackContext) Services() string {
return strconv.Itoa(s.s.Services)
}

View File

@ -0,0 +1,64 @@
package formatter
import (
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
func TestStackContextWrite(t *testing.T) {
cases := []struct {
context Context
expected string
}{
// Errors
{
Context{Format: "{{InvalidFunction}}"},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
Context{Format: "{{nil}}"},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table format
{
Context{Format: NewStackFormat("table")},
`NAME SERVICES
baz 2
bar 1
`,
},
{
Context{Format: NewStackFormat("table {{.Name}}")},
`NAME
baz
bar
`,
},
// Custom Format
{
Context{Format: NewStackFormat("{{.Name}}")},
`baz
bar
`,
},
}
stacks := []*Stack{
{Name: "baz", Services: 2},
{Name: "bar", Services: 1},
}
for _, testcase := range cases {
out := bytes.NewBufferString("")
testcase.context.Output = out
err := StackWrite(testcase.context, stacks)
if err != nil {
assert.Error(t, err, testcase.expected)
} else {
assert.Equal(t, out.String(), testcase.expected)
}
}
}

View File

@ -4,8 +4,10 @@ import (
"archive/tar" "archive/tar"
"bufio" "bufio"
"bytes" "bytes"
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"os" "os"
"regexp" "regexp"
"runtime" "runtime"
@ -59,6 +61,7 @@ type buildOptions struct {
networkMode string networkMode string
squash bool squash bool
target string target string
imageIDFile string
} }
// dockerfileFromStdin returns true when the user specified that the Dockerfile // dockerfileFromStdin returns true when the user specified that the Dockerfile
@ -123,6 +126,7 @@ func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
flags.SetAnnotation("network", "version", []string{"1.25"}) flags.SetAnnotation("network", "version", []string{"1.25"})
flags.Var(&options.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)") flags.Var(&options.extraHosts, "add-host", "Add a custom host-to-IP mapping (host:ip)")
flags.StringVar(&options.target, "target", "", "Set the target build stage to build.") flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
command.AddTrustVerificationFlags(flags) command.AddTrustVerificationFlags(flags)
@ -176,6 +180,12 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
progBuff = bytes.NewBuffer(nil) progBuff = bytes.NewBuffer(nil)
buildBuff = bytes.NewBuffer(nil) buildBuff = bytes.NewBuffer(nil)
} }
if options.imageIDFile != "" {
// Avoid leaving a stale file if we eventually fail
if err := os.Remove(options.imageIDFile); err != nil && !os.IsNotExist(err) {
return errors.Wrap(err, "Removing image ID file")
}
}
switch { switch {
case options.contextFromStdin(): case options.contextFromStdin():
@ -254,7 +264,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
} }
// Setup an upload progress bar // Setup an upload progress bar
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true) progressOutput := streamformatter.NewProgressOutput(progBuff)
if !dockerCli.Out().IsTerminal() { if !dockerCli.Out().IsTerminal() {
progressOutput = &lastProgressOutput{output: progressOutput} progressOutput = &lastProgressOutput{output: progressOutput}
} }
@ -301,7 +311,17 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
} }
defer response.Body.Close() defer response.Body.Close()
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), nil) imageID := ""
aux := func(auxJSON *json.RawMessage) {
var result types.BuildResult
if err := json.Unmarshal(*auxJSON, &result); err != nil {
fmt.Fprintf(dockerCli.Err(), "Failed to parse aux message: %s", err)
} else {
imageID = result.ID
}
}
err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, dockerCli.Out().FD(), dockerCli.Out().IsTerminal(), aux)
if err != nil { if err != nil {
if jerr, ok := err.(*jsonmessage.JSONError); ok { if jerr, ok := err.(*jsonmessage.JSONError); ok {
// If no error code is set, default to 1 // If no error code is set, default to 1
@ -329,9 +349,18 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
// Everything worked so if -q was provided the output from the daemon // Everything worked so if -q was provided the output from the daemon
// should be just the image ID and we'll print that to stdout. // should be just the image ID and we'll print that to stdout.
if options.quiet { if options.quiet {
fmt.Fprintf(dockerCli.Out(), "%s", buildBuff) imageID = fmt.Sprintf("%s", buildBuff)
fmt.Fprintf(dockerCli.Out(), imageID)
} }
if options.imageIDFile != "" {
if imageID == "" {
return errors.Errorf("Server did not provide an image ID. Cannot write %s", options.imageIDFile)
}
if err := ioutil.WriteFile(options.imageIDFile, []byte(imageID), 0666); err != nil {
return err
}
}
if command.IsTrusted() { if command.IsTrusted() {
// Since the build was successful, now we must tag any of the resolved // Since the build was successful, now we must tag any of the resolved
// images from the above Dockerfile rewrite. // images from the above Dockerfile rewrite.

View File

@ -165,7 +165,7 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read
if err != nil { if err != nil {
return nil, "", errors.Errorf("unable to download remote context %s: %v", remoteURL, err) return nil, "", errors.Errorf("unable to download remote context %s: %v", remoteURL, err)
} }
progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(out, true) progressOutput := streamformatter.NewProgressOutput(out)
// Pass the response body through a progress reader. // Pass the response body through a progress reader.
progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL)) progReader := progress.NewProgressReader(response.Body, progressOutput, response.ContentLength, "", fmt.Sprintf("Downloading build context from remote url: %s", remoteURL))

View File

@ -0,0 +1,116 @@
package image
import (
"io"
"io/ioutil"
"strings"
"time"
"github.com/docker/cli/client"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/image"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
imageTagFunc func(string, string) error
imageSaveFunc func(images []string) (io.ReadCloser, error)
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
imagePushFunc func(ref string, options types.ImagePushOptions) (io.ReadCloser, error)
infoFunc func() (types.Info, error)
imagePullFunc func(ref string, options types.ImagePullOptions) (io.ReadCloser, error)
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
imageListFunc func(options types.ImageListOptions) ([]types.ImageSummary, error)
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
imageHistoryFunc func(image string) ([]image.HistoryResponseItem, error)
}
func (cli *fakeClient) ImageTag(_ context.Context, image, ref string) error {
if cli.imageTagFunc != nil {
return cli.imageTagFunc(image, ref)
}
return nil
}
func (cli *fakeClient) ImageSave(_ context.Context, images []string) (io.ReadCloser, error) {
if cli.imageSaveFunc != nil {
return cli.imageSaveFunc(images)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (cli *fakeClient) ImageRemove(_ context.Context, image string,
options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
if cli.imageRemoveFunc != nil {
return cli.imageRemoveFunc(image, options)
}
return []types.ImageDeleteResponseItem{}, nil
}
func (cli *fakeClient) ImagePush(_ context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
if cli.imagePushFunc != nil {
return cli.imagePushFunc(ref, options)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (cli *fakeClient) Info(_ context.Context) (types.Info, error) {
if cli.infoFunc != nil {
return cli.infoFunc()
}
return types.Info{}, nil
}
func (cli *fakeClient) ImagePull(_ context.Context, ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
if cli.imagePullFunc != nil {
cli.imagePullFunc(ref, options)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (cli *fakeClient) ImagesPrune(_ context.Context, pruneFilter filters.Args) (types.ImagesPruneReport, error) {
if cli.imagesPruneFunc != nil {
return cli.imagesPruneFunc(pruneFilter)
}
return types.ImagesPruneReport{}, nil
}
func (cli *fakeClient) ImageLoad(_ context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
if cli.imageLoadFunc != nil {
return cli.imageLoadFunc(input, quiet)
}
return types.ImageLoadResponse{}, nil
}
func (cli *fakeClient) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) {
if cli.imageListFunc != nil {
return cli.imageListFunc(options)
}
return []types.ImageSummary{{}}, nil
}
func (cli *fakeClient) ImageInspectWithRaw(_ context.Context, image string) (types.ImageInspect, []byte, error) {
if cli.imageInspectFunc != nil {
return cli.imageInspectFunc(image)
}
return types.ImageInspect{}, nil, nil
}
func (cli *fakeClient) ImageImport(_ context.Context, source types.ImageImportSource, ref string,
options types.ImageImportOptions) (io.ReadCloser, error) {
if cli.imageImportFunc != nil {
return cli.imageImportFunc(source, ref, options)
}
return ioutil.NopCloser(strings.NewReader("")), nil
}
func (cli *fakeClient) ImageHistory(_ context.Context, img string) ([]image.HistoryResponseItem, error) {
if cli.imageHistoryFunc != nil {
return cli.imageHistoryFunc(img)
}
return []image.HistoryResponseItem{{ID: img, Created: time.Now().Unix()}}, nil
}

View File

@ -19,7 +19,7 @@ type historyOptions struct {
} }
// NewHistoryCommand creates a new `docker history` command // NewHistoryCommand creates a new `docker history` command
func NewHistoryCommand(dockerCli *command.DockerCli) *cobra.Command { func NewHistoryCommand(dockerCli command.Cli) *cobra.Command {
var opts historyOptions var opts historyOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -42,7 +42,7 @@ func NewHistoryCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runHistory(dockerCli *command.DockerCli, opts historyOptions) error { func runHistory(dockerCli command.Cli, opts historyOptions) error {
ctx := context.Background() ctx := context.Background()
history, err := dockerCli.Client().ImageHistory(ctx, opts.image) history, err := dockerCli.Client().ImageHistory(ctx, opts.image)

View File

@ -0,0 +1,108 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"regexp"
"testing"
"time"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestNewHistoryCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imageHistoryFunc func(img string) ([]image.HistoryResponseItem, error)
}{
{
name: "wrong-args",
args: []string{},
expectedError: "requires exactly 1 argument(s).",
},
{
name: "client-error",
args: []string{"image:tag"},
expectedError: "something went wrong",
imageHistoryFunc: func(img string) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{}}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewHistoryCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
outputRegex string
imageHistoryFunc func(img string) ([]image.HistoryResponseItem, error)
}{
{
name: "simple",
args: []string{"image:tag"},
imageHistoryFunc: func(img string) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{
ID: "1234567890123456789",
Created: time.Now().Unix(),
}}, nil
},
},
{
name: "quiet",
args: []string{"--quiet", "image:tag"},
},
// TODO: This test is failing since the output does not contain an RFC3339 date
//{
// name: "non-human",
// args: []string{"--human=false", "image:tag"},
// outputRegex: "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}", // RFC3339 date format match
//},
{
name: "non-human-header",
args: []string{"--human=false", "image:tag"},
outputRegex: "CREATED\\sAT",
},
{
name: "quiet-no-trunc",
args: []string{"--quiet", "--no-trunc", "image:tag"},
imageHistoryFunc: func(img string) ([]image.HistoryResponseItem, error) {
return []image.HistoryResponseItem{{
ID: "1234567890123456789",
Created: time.Now().Unix(),
}}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
if tc.outputRegex == "" {
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("history-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
} else {
match, _ := regexp.MatchString(tc.outputRegex, actual)
assert.True(t, match)
}
}
}

View File

@ -23,7 +23,7 @@ type importOptions struct {
} }
// NewImportCommand creates a new `docker import` command // NewImportCommand creates a new `docker import` command
func NewImportCommand(dockerCli *command.DockerCli) *cobra.Command { func NewImportCommand(dockerCli command.Cli) *cobra.Command {
var opts importOptions var opts importOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -48,7 +48,7 @@ func NewImportCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runImport(dockerCli *command.DockerCli, opts importOptions) error { func runImport(dockerCli command.Cli, opts importOptions) error {
var ( var (
in io.Reader in io.Reader
srcName = opts.source srcName = opts.source

View File

@ -0,0 +1,100 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestNewImportCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
}{
{
name: "wrong-args",
args: []string{},
expectedError: "requires at least 1 argument(s).",
},
{
name: "import-failed",
args: []string{"testdata/import-command-success.input.txt"},
expectedError: "something went wrong",
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
return nil, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewImportCommandInvalidFile(t *testing.T) {
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"testdata/import-command-success.unexistent-file"})
testutil.ErrorContains(t, cmd.Execute(), "testdata/import-command-success.unexistent-file")
}
func TestNewImportCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
}{
{
name: "simple",
args: []string{"testdata/import-command-success.input.txt"},
},
{
name: "terminal-source",
args: []string{"-"},
},
{
name: "double",
args: []string{"-", "image:local"},
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
assert.Equal(t, "image:local", ref)
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
{
name: "message",
args: []string{"--message", "test message", "-"},
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
assert.Equal(t, "test message", options.Message)
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
{
name: "change",
args: []string{"--change", "ENV DEBUG true", "-"},
imageImportFunc: func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error) {
assert.Equal(t, "ENV DEBUG true", options.Changes[0])
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
}
}

View File

@ -15,7 +15,7 @@ type inspectOptions struct {
} }
// newInspectCommand creates a new cobra.Command for `docker image inspect` // newInspectCommand creates a new cobra.Command for `docker image inspect`
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command { func newInspectCommand(dockerCli command.Cli) *cobra.Command {
var opts inspectOptions var opts inspectOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -33,7 +33,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error { func runInspect(dockerCli command.Cli, opts inspectOptions) error {
client := dockerCli.Client() client := dockerCli.Client()
ctx := context.Background() ctx := context.Background()

View File

@ -0,0 +1,92 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/stretchr/testify/assert"
)
func TestNewInspectCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
}{
{
name: "wrong-args",
args: []string{},
expectedError: "requires at least 1 argument(s).",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewInspectCommandSuccess(t *testing.T) {
imageInspectInvocationCount := 0
testCases := []struct {
name string
args []string
imageCount int
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
}{
{
name: "simple",
args: []string{"image"},
imageCount: 1,
imageInspectFunc: func(image string) (types.ImageInspect, []byte, error) {
imageInspectInvocationCount++
assert.Equal(t, "image", image)
return types.ImageInspect{}, nil, nil
},
},
{
name: "format",
imageCount: 1,
args: []string{"--format='{{.ID}}'", "image"},
imageInspectFunc: func(image string) (types.ImageInspect, []byte, error) {
imageInspectInvocationCount++
return types.ImageInspect{ID: image}, nil, nil
},
},
{
name: "simple-many",
args: []string{"image1", "image2"},
imageCount: 2,
imageInspectFunc: func(image string) (types.ImageInspect, []byte, error) {
imageInspectInvocationCount++
if imageInspectInvocationCount == 1 {
assert.Equal(t, "image1", image)
} else {
assert.Equal(t, "image2", image)
}
return types.ImageInspect{}, nil, nil
},
},
}
for _, tc := range testCases {
imageInspectInvocationCount = 0
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("inspect-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
assert.Equal(t, imageInspectInvocationCount, tc.imageCount)
}
}

View File

@ -23,7 +23,7 @@ type imagesOptions struct {
} }
// NewImagesCommand creates a new `docker images` command // NewImagesCommand creates a new `docker images` command
func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command { func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
opts := imagesOptions{filter: opts.NewFilterOpt()} opts := imagesOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -50,14 +50,14 @@ func NewImagesCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func newListCommand(dockerCli *command.DockerCli) *cobra.Command { func newListCommand(dockerCli command.Cli) *cobra.Command {
cmd := *NewImagesCommand(dockerCli) cmd := *NewImagesCommand(dockerCli)
cmd.Aliases = []string{"images", "list"} cmd.Aliases = []string{"images", "list"}
cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]" cmd.Use = "ls [OPTIONS] [REPOSITORY[:TAG]]"
return &cmd return &cmd
} }
func runImages(dockerCli *command.DockerCli, opts imagesOptions) error { func runImages(dockerCli command.Cli, opts imagesOptions) error {
ctx := context.Background() ctx := context.Background()
filters := opts.filter.Value() filters := opts.filter.Value()

View File

@ -0,0 +1,102 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestNewImagesCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imageListFunc func(options types.ImageListOptions) ([]types.ImageSummary, error)
}{
{
name: "wrong-args",
args: []string{"arg1", "arg2"},
expectedError: "requires at most 1 argument(s).",
},
{
name: "failed-list",
expectedError: "something went wrong",
imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) {
return []types.ImageSummary{{}}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewImagesCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageFormat string
imageListFunc func(options types.ImageListOptions) ([]types.ImageSummary, error)
}{
{
name: "simple",
},
{
name: "format",
imageFormat: "raw",
},
{
name: "quiet-format",
args: []string{"-q"},
imageFormat: "table",
},
{
name: "match-name",
args: []string{"image"},
imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) {
assert.Equal(t, "image", options.Filters.Get("reference")[0])
return []types.ImageSummary{{}}, nil
},
},
{
name: "filters",
args: []string{"--filter", "name=value"},
imageListFunc: func(options types.ImageListOptions) ([]types.ImageSummary, error) {
assert.Equal(t, "value", options.Filters.Get("name")[0])
return []types.ImageSummary{{}}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, buf)
cli.SetConfigfile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
cmd := NewImagesCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("list-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}
}
func TestNewListCommandAlias(t *testing.T) {
cmd := newListCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
assert.True(t, cmd.HasAlias("images"))
assert.True(t, cmd.HasAlias("list"))
assert.False(t, cmd.HasAlias("other"))
}

View File

@ -19,7 +19,7 @@ type loadOptions struct {
} }
// NewLoadCommand creates a new `docker load` command // NewLoadCommand creates a new `docker load` command
func NewLoadCommand(dockerCli *command.DockerCli) *cobra.Command { func NewLoadCommand(dockerCli command.Cli) *cobra.Command {
var opts loadOptions var opts loadOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -39,7 +39,7 @@ func NewLoadCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runLoad(dockerCli *command.DockerCli, opts loadOptions) error { func runLoad(dockerCli command.Cli, opts loadOptions) error {
var input io.Reader = dockerCli.In() var input io.Reader = dockerCli.In()
if opts.input != "" { if opts.input != "" {

View File

@ -0,0 +1,105 @@
package image
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestNewLoadCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
isTerminalIn bool
expectedError string
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
}{
{
name: "wrong-args",
args: []string{"arg"},
expectedError: "accepts no argument(s).",
},
{
name: "input-to-terminal",
isTerminalIn: true,
expectedError: "requested load from stdin, but stdin is empty",
},
{
name: "pull-error",
expectedError: "something went wrong",
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
return types.ImageLoadResponse{}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, new(bytes.Buffer))
cli.In().SetIsTerminal(tc.isTerminalIn)
cmd := NewLoadCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewLoadCommandInvalidInput(t *testing.T) {
expectedError := "open *"
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"--input", "*"})
err := cmd.Execute()
testutil.ErrorContains(t, err, expectedError)
}
func TestNewLoadCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageLoadFunc func(input io.Reader, quiet bool) (types.ImageLoadResponse, error)
}{
{
name: "simple",
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
return types.ImageLoadResponse{Body: ioutil.NopCloser(strings.NewReader("Success"))}, nil
},
},
{
name: "json",
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
json := "{\"ID\": \"1\"}"
return types.ImageLoadResponse{
Body: ioutil.NopCloser(strings.NewReader(json)),
JSON: true,
}, nil
},
},
{
name: "input-file",
args: []string{"--input", "testdata/load-command-success.input.txt"},
imageLoadFunc: func(input io.Reader, quiet bool) (types.ImageLoadResponse, error) {
return types.ImageLoadResponse{Body: ioutil.NopCloser(strings.NewReader("Success"))}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("load-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}
}

View File

@ -19,7 +19,7 @@ type pruneOptions struct {
} }
// NewPruneCommand returns a new cobra prune command for images // NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli *command.DockerCli) *cobra.Command { func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
opts := pruneOptions{filter: opts.NewFilterOpt()} opts := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -55,7 +55,7 @@ Are you sure you want to continue?`
Are you sure you want to continue?` Are you sure you want to continue?`
) )
func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) { func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := opts.filter.Value() pruneFilters := opts.filter.Value()
pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all)) pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all))
pruneFilters = command.PruneFilters(dockerCli, pruneFilters) pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
@ -90,6 +90,6 @@ func runPrune(dockerCli *command.DockerCli, opts pruneOptions) (spaceReclaimed u
// RunPrune calls the Image Prune API // RunPrune calls the Image Prune API
// This returns the amount of space reclaimed and a detailed output string // This returns the amount of space reclaimed and a detailed output string
func RunPrune(dockerCli *command.DockerCli, all bool, filter opts.FilterOpt) (uint64, string, error) { func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter}) return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
} }

View File

@ -0,0 +1,100 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestNewPruneCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
}{
{
name: "wrong-args",
args: []string{"something"},
expectedError: "accepts no argument(s).",
},
{
name: "prune-error",
args: []string{"--force"},
expectedError: "something went wrong",
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
return types.ImagesPruneReport{}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewPruneCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imagesPruneFunc func(pruneFilter filters.Args) (types.ImagesPruneReport, error)
}{
{
name: "all",
args: []string{"--all"},
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
assert.Equal(t, "false", pruneFilter.Get("dangling")[0])
return types.ImagesPruneReport{}, nil
},
},
{
name: "force-deleted",
args: []string{"--force"},
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
assert.Equal(t, "true", pruneFilter.Get("dangling")[0])
return types.ImagesPruneReport{
ImagesDeleted: []types.ImageDeleteResponseItem{{Deleted: "image1"}},
SpaceReclaimed: 1,
}, nil
},
},
{
name: "force-untagged",
args: []string{"--force"},
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
assert.Equal(t, "true", pruneFilter.Get("dangling")[0])
return types.ImagesPruneReport{
ImagesDeleted: []types.ImageDeleteResponseItem{{Untagged: "image1"}},
SpaceReclaimed: 2,
}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("prune-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}
}

View File

@ -19,7 +19,7 @@ type pullOptions struct {
} }
// NewPullCommand creates a new `docker pull` command // NewPullCommand creates a new `docker pull` command
func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command { func NewPullCommand(dockerCli command.Cli) *cobra.Command {
var opts pullOptions var opts pullOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -40,7 +40,7 @@ func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runPull(dockerCli *command.DockerCli, opts pullOptions) error { func runPull(dockerCli command.Cli, opts pullOptions) error {
distributionRef, err := reference.ParseNormalizedNamed(opts.remote) distributionRef, err := reference.ParseNormalizedNamed(opts.remote)
if err != nil { if err != nil {
return err return err

View File

@ -0,0 +1,85 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/docker/docker/registry"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestNewPullCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "wrong-args",
expectedError: "requires exactly 1 argument(s).",
args: []string{},
},
{
name: "invalid-name",
expectedError: "invalid reference format: repository name must be lowercase",
args: []string{"UPPERCASE_REPO"},
},
{
name: "all-tags-with-tag",
expectedError: "tag can't be used with --all-tags/-a",
args: []string{"--all-tags", "image:tag"},
},
{
name: "pull-error",
args: []string{"--disable-content-trust=false", "image:tag"},
expectedError: "you are not authorized to perform this operation: server returned 401.",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewPullCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "simple",
args: []string{"image:tag"},
},
{
name: "simple-no-tag",
args: []string{"image"},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("pull-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}
}

View File

@ -12,7 +12,7 @@ import (
) )
// NewPushCommand creates a new `docker push` command // NewPushCommand creates a new `docker push` command
func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command { func NewPushCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "push [OPTIONS] NAME[:TAG]", Use: "push [OPTIONS] NAME[:TAG]",
Short: "Push an image or a repository to a registry", Short: "Push an image or a repository to a registry",
@ -29,7 +29,7 @@ func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runPush(dockerCli *command.DockerCli, remote string) error { func runPush(dockerCli command.Cli, remote string) error {
ref, err := reference.ParseNormalizedNamed(remote) ref, err := reference.ParseNormalizedNamed(remote)
if err != nil { if err != nil {
return err return err

View File

@ -0,0 +1,85 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestNewPushCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imagePushFunc func(ref string, options types.ImagePushOptions) (io.ReadCloser, error)
}{
{
name: "wrong-args",
args: []string{},
expectedError: "requires exactly 1 argument(s).",
},
{
name: "invalid-name",
args: []string{"UPPERCASE_REPO"},
expectedError: "invalid reference format: repository name must be lowercase",
},
{
name: "push-failed",
args: []string{"image:repo"},
expectedError: "Failed to push",
imagePushFunc: func(ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), errors.Errorf("Failed to push")
},
},
{
name: "trust-error",
args: []string{"--disable-content-trust=false", "image:repo"},
expectedError: "you are not authorized to perform this operation: server returned 401.",
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewPushCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
trustedPushFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo,
ref reference.Named, authConfig types.AuthConfig,
requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "simple",
args: []string{"image:tag"},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{
imagePushFunc: func(ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
}
}

View File

@ -19,7 +19,7 @@ type removeOptions struct {
} }
// NewRemoveCommand creates a new `docker remove` command // NewRemoveCommand creates a new `docker remove` command
func NewRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { func NewRemoveCommand(dockerCli command.Cli) *cobra.Command {
var opts removeOptions var opts removeOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -39,14 +39,14 @@ func NewRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
cmd := *NewRemoveCommand(dockerCli) cmd := *NewRemoveCommand(dockerCli)
cmd.Aliases = []string{"rmi", "remove"} cmd.Aliases = []string{"rmi", "remove"}
cmd.Use = "rm [OPTIONS] IMAGE [IMAGE...]" cmd.Use = "rm [OPTIONS] IMAGE [IMAGE...]"
return &cmd return &cmd
} }
func runRemove(dockerCli *command.DockerCli, opts removeOptions, images []string) error { func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error {
client := dockerCli.Client() client := dockerCli.Client()
ctx := context.Background() ctx := context.Background()

View File

@ -0,0 +1,103 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestNewRemoveCommandAlias(t *testing.T) {
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
assert.True(t, cmd.HasAlias("rmi"))
assert.True(t, cmd.HasAlias("remove"))
assert.False(t, cmd.HasAlias("other"))
}
func TestNewRemoveCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
}{
{
name: "wrong args",
expectedError: "requires at least 1 argument(s).",
},
{
name: "ImageRemove fail",
args: []string{"arg1"},
expectedError: "error removing image",
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
assert.False(t, options.Force)
assert.True(t, options.PruneChildren)
return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image")
},
},
}
for _, tc := range testCases {
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
imageRemoveFunc: tc.imageRemoveFunc,
}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewRemoveCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
}{
{
name: "Image Deleted",
args: []string{"image1"},
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
assert.Equal(t, "image1", image)
return []types.ImageDeleteResponseItem{{Deleted: image}}, nil
},
},
{
name: "Image Untagged",
args: []string{"image1"},
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
assert.Equal(t, "image1", image)
return []types.ImageDeleteResponseItem{{Untagged: image}}, nil
},
},
{
name: "Image Deleted and Untagged",
args: []string{"image1", "image2"},
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
if image == "image1" {
return []types.ImageDeleteResponseItem{{Untagged: image}}, nil
}
return []types.ImageDeleteResponseItem{{Deleted: image}}, nil
},
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
imageRemoveFunc: tc.imageRemoveFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("remove-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}
}

View File

@ -16,7 +16,7 @@ type saveOptions struct {
} }
// NewSaveCommand creates a new `docker save` command // NewSaveCommand creates a new `docker save` command
func NewSaveCommand(dockerCli *command.DockerCli) *cobra.Command { func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
var opts saveOptions var opts saveOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -36,7 +36,7 @@ func NewSaveCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runSave(dockerCli *command.DockerCli, opts saveOptions) error { func runSave(dockerCli command.Cli, opts saveOptions) error {
if opts.output == "" && dockerCli.Out().IsTerminal() { if opts.output == "" && dockerCli.Out().IsTerminal() {
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect") return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
} }

View File

@ -0,0 +1,100 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewSaveCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
isTerminal bool
expectedError string
imageSaveFunc func(images []string) (io.ReadCloser, error)
}{
{
name: "wrong args",
args: []string{},
expectedError: "requires at least 1 argument(s).",
},
{
name: "output to terminal",
args: []string{"output", "file", "arg1"},
isTerminal: true,
expectedError: "cowardly refusing to save to a terminal. Use the -o flag or redirect",
},
{
name: "ImageSave fail",
args: []string{"arg1"},
isTerminal: false,
expectedError: "error saving image",
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), errors.Errorf("error saving image")
},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc}, new(bytes.Buffer))
cli.Out().SetIsTerminal(tc.isTerminal)
cmd := NewSaveCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNewSaveCommandSuccess(t *testing.T) {
testCases := []struct {
args []string
isTerminal bool
imageSaveFunc func(images []string) (io.ReadCloser, error)
deferredFunc func()
}{
{
args: []string{"-o", "save_tmp_file", "arg1"},
isTerminal: true,
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
require.Len(t, images, 1)
assert.Equal(t, "arg1", images[0])
return ioutil.NopCloser(strings.NewReader("")), nil
},
deferredFunc: func() {
os.Remove("save_tmp_file")
},
},
{
args: []string{"arg1", "arg2"},
isTerminal: false,
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
require.Len(t, images, 2)
assert.Equal(t, "arg1", images[0])
assert.Equal(t, "arg2", images[1])
return ioutil.NopCloser(strings.NewReader("")), nil
},
},
}
for _, tc := range testCases {
cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},
}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
if tc.deferredFunc != nil {
tc.deferredFunc()
}
}
}

View File

@ -14,7 +14,7 @@ type tagOptions struct {
} }
// NewTagCommand creates a new `docker tag` command // NewTagCommand creates a new `docker tag` command
func NewTagCommand(dockerCli *command.DockerCli) *cobra.Command { func NewTagCommand(dockerCli command.Cli) *cobra.Command {
var opts tagOptions var opts tagOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
@ -34,7 +34,7 @@ func NewTagCommand(dockerCli *command.DockerCli) *cobra.Command {
return cmd return cmd
} }
func runTag(dockerCli *command.DockerCli, opts tagOptions) error { func runTag(dockerCli command.Cli, opts tagOptions) error {
ctx := context.Background() ctx := context.Background()
return dockerCli.Client().ImageTag(ctx, opts.image, opts.name) return dockerCli.Client().ImageTag(ctx, opts.image, opts.name)

View File

@ -0,0 +1,44 @@
package image
import (
"bytes"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/pkg/testutil"
"github.com/stretchr/testify/assert"
)
func TestCliNewTagCommandErrors(t *testing.T) {
testCases := [][]string{
{},
{"image1"},
{"image1", "image2", "image3"},
}
expectedError := "\"tag\" requires exactly 2 argument(s)."
buf := new(bytes.Buffer)
for _, args := range testCases {
cmd := NewTagCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetArgs(args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), expectedError)
}
}
func TestCliNewTagCommand(t *testing.T) {
buf := new(bytes.Buffer)
cmd := NewTagCommand(
test.NewFakeCli(&fakeClient{
imageTagFunc: func(image string, ref string) error {
assert.Equal(t, "image1", image)
assert.Equal(t, "image2", ref)
return nil
},
}, buf))
cmd.SetArgs([]string{"image1", "image2"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())
value, _ := cmd.Flags().GetBool("interspersed")
assert.False(t, value)
}

View File

@ -0,0 +1 @@
1234567890123456789

View File

@ -0,0 +1 @@
tag

View File

@ -0,0 +1,2 @@
IMAGE CREATED CREATED BY SIZE COMMENT
123456789012 Less than a second ago 0B

View File

@ -0,0 +1 @@
file input test

View File

@ -0,0 +1 @@
'image'

View File

@ -0,0 +1,50 @@
[
{
"Id": "",
"RepoTags": null,
"RepoDigests": null,
"Parent": "",
"Comment": "",
"Created": "",
"Container": "",
"ContainerConfig": null,
"DockerVersion": "",
"Author": "",
"Config": null,
"Architecture": "",
"Os": "",
"Size": 0,
"VirtualSize": 0,
"GraphDriver": {
"Data": null,
"Name": ""
},
"RootFS": {
"Type": ""
}
},
{
"Id": "",
"RepoTags": null,
"RepoDigests": null,
"Parent": "",
"Comment": "",
"Created": "",
"Container": "",
"ContainerConfig": null,
"DockerVersion": "",
"Author": "",
"Config": null,
"Architecture": "",
"Os": "",
"Size": 0,
"VirtualSize": 0,
"GraphDriver": {
"Data": null,
"Name": ""
},
"RootFS": {
"Type": ""
}
}
]

View File

@ -0,0 +1,26 @@
[
{
"Id": "",
"RepoTags": null,
"RepoDigests": null,
"Parent": "",
"Comment": "",
"Created": "",
"Container": "",
"ContainerConfig": null,
"DockerVersion": "",
"Author": "",
"Config": null,
"Architecture": "",
"Os": "",
"Size": 0,
"VirtualSize": 0,
"GraphDriver": {
"Data": null,
"Name": ""
},
"RootFS": {
"Type": ""
}
}
]

View File

@ -0,0 +1 @@
REPOSITORY TAG IMAGE ID CREATED SIZE

View File

@ -0,0 +1 @@
REPOSITORY TAG IMAGE ID CREATED SIZE

View File

@ -0,0 +1 @@
REPOSITORY TAG IMAGE ID CREATED SIZE

View File

@ -0,0 +1 @@
Success

View File

@ -0,0 +1 @@
file input test

View File

@ -0,0 +1 @@
1:

View File

@ -0,0 +1 @@
Success

View File

@ -0,0 +1,2 @@
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N] Total reclaimed space: 0B

View File

@ -0,0 +1,4 @@
Deleted Images:
deleted: image1
Total reclaimed space: 1B

View File

@ -0,0 +1,4 @@
Deleted Images:
untagged: image1
Total reclaimed space: 2B

View File

@ -0,0 +1 @@
Using default tag: latest

View File

@ -0,0 +1,4 @@
Untagged: image1
Deleted: image2
Untagged: image1
Deleted: image2

View File

@ -0,0 +1,2 @@
Deleted: image1
Deleted: image1

View File

@ -0,0 +1,2 @@
Untagged: image1
Untagged: image1

View File

@ -29,7 +29,7 @@ type target struct {
} }
// trustedPush handles content trust pushing of an image // trustedPush handles content trust pushing of an image
func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { func trustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege) responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege)
if err != nil { if err != nil {
return err return err
@ -42,7 +42,7 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
// PushTrustedReference pushes a canonical reference to the trust server. // PushTrustedReference pushes a canonical reference to the trust server.
// nolint: gocyclo // nolint: gocyclo
func PushTrustedReference(cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, in io.Reader) error { func PushTrustedReference(cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, in io.Reader) error {
// If it is a trusted push we would like to find the target entry which match the // If it is a trusted push we would like to find the target entry which match the
// tag provided in the function and then do an AddTarget later. // tag provided in the function and then do an AddTarget later.
target := &client.Target{} target := &client.Target{}
@ -203,7 +203,7 @@ func addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.T
} }
// imagePushPrivileged push the image // imagePushPrivileged push the image
func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref reference.Named, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) { func imagePushPrivileged(ctx context.Context, cli command.Cli, authConfig types.AuthConfig, ref reference.Named, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) {
encodedAuth, err := command.EncodeAuthToBase64(authConfig) encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil { if err != nil {
return nil, err return nil, err
@ -217,7 +217,7 @@ func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
} }
// trustedPull handles content trust pulling of an image // trustedPull handles content trust pulling of an image
func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { func trustedPull(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
var refs []target var refs []target
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull") notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
@ -296,7 +296,7 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry
} }
// imagePullPrivileged pulls the image and displays it to the output // imagePullPrivileged pulls the image and displays it to the output
func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error { func imagePullPrivileged(ctx context.Context, cli command.Cli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error {
encodedAuth, err := command.EncodeAuthToBase64(authConfig) encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil { if err != nil {
@ -318,7 +318,7 @@ func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
} }
// TrustedReference returns the canonical trusted reference for an image reference // TrustedReference returns the canonical trusted reference for an image reference
func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) { func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) {
var ( var (
repoInfo *registry.RepositoryInfo repoInfo *registry.RepositoryInfo
err error err error
@ -372,7 +372,7 @@ func convertTarget(t client.Target) (target, error) {
} }
// TagTrusted tags a trusted ref // TagTrusted tags a trusted ref
func TagTrusted(ctx context.Context, cli *command.DockerCli, trustedRef reference.Canonical, ref reference.NamedTagged) error { func TagTrusted(ctx context.Context, cli command.Cli, trustedRef reference.Canonical, ref reference.NamedTagged) error {
// Use familiar references when interacting with client and output // Use familiar references when interacting with client and output
familiarRef := reference.FamiliarString(ref) familiarRef := reference.FamiliarString(ref)
trustedFamiliarRef := reference.FamiliarString(trustedRef) trustedFamiliarRef := reference.FamiliarString(trustedRef)

View File

@ -1,20 +1,18 @@
package command package command
import ( import (
"errors"
"io" "io"
"os" "os"
"runtime" "runtime"
"github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/term"
"github.com/pkg/errors"
) )
// InStream is an input stream used by the DockerCli to read user input // InStream is an input stream used by the DockerCli to read user input
type InStream struct { type InStream struct {
CommonStream
in io.ReadCloser in io.ReadCloser
fd uintptr
isTerminal bool
state *term.State
} }
func (i *InStream) Read(p []byte) (int, error) { func (i *InStream) Read(p []byte) (int, error) {
@ -26,32 +24,15 @@ func (i *InStream) Close() error {
return i.in.Close() return i.in.Close()
} }
// FD returns the file descriptor number for this stream
func (i *InStream) FD() uintptr {
return i.fd
}
// IsTerminal returns true if this stream is connected to a terminal
func (i *InStream) IsTerminal() bool {
return i.isTerminal
}
// SetRawTerminal sets raw mode on the input terminal // SetRawTerminal sets raw mode on the input terminal
func (i *InStream) SetRawTerminal() (err error) { func (i *InStream) SetRawTerminal() (err error) {
if os.Getenv("NORAW") != "" || !i.isTerminal { if os.Getenv("NORAW") != "" || !i.CommonStream.isTerminal {
return nil return nil
} }
i.state, err = term.SetRawTerminal(i.fd) i.CommonStream.state, err = term.SetRawTerminal(i.CommonStream.fd)
return err return err
} }
// RestoreTerminal restores normal mode to the terminal
func (i *InStream) RestoreTerminal() {
if i.state != nil {
term.RestoreTerminal(i.fd, i.state)
}
}
// CheckTty checks if we are trying to attach to a container tty // CheckTty checks if we are trying to attach to a container tty
// from a non-tty client input stream, and if so, returns an error. // from a non-tty client input stream, and if so, returns an error.
func (i *InStream) CheckTty(attachStdin, ttyMode bool) error { func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
@ -71,5 +52,5 @@ func (i *InStream) CheckTty(attachStdin, ttyMode bool) error {
// NewInStream returns a new InStream object from a ReadCloser // NewInStream returns a new InStream object from a ReadCloser
func NewInStream(in io.ReadCloser) *InStream { func NewInStream(in io.ReadCloser) *InStream {
fd, isTerminal := term.GetFdInfo(in) fd, isTerminal := term.GetFdInfo(in)
return &InStream{in: in, fd: fd, isTerminal: isTerminal} return &InStream{CommonStream: CommonStream{fd: fd, isTerminal: isTerminal}, in: in}
} }

View File

@ -11,42 +11,23 @@ import (
// OutStream is an output stream used by the DockerCli to write normal program // OutStream is an output stream used by the DockerCli to write normal program
// output. // output.
type OutStream struct { type OutStream struct {
CommonStream
out io.Writer out io.Writer
fd uintptr
isTerminal bool
state *term.State
} }
func (o *OutStream) Write(p []byte) (int, error) { func (o *OutStream) Write(p []byte) (int, error) {
return o.out.Write(p) return o.out.Write(p)
} }
// FD returns the file descriptor number for this stream // SetRawTerminal sets raw mode on the input terminal
func (o *OutStream) FD() uintptr {
return o.fd
}
// IsTerminal returns true if this stream is connected to a terminal
func (o *OutStream) IsTerminal() bool {
return o.isTerminal
}
// SetRawTerminal sets raw mode on the output terminal
func (o *OutStream) SetRawTerminal() (err error) { func (o *OutStream) SetRawTerminal() (err error) {
if os.Getenv("NORAW") != "" || !o.isTerminal { if os.Getenv("NORAW") != "" || !o.CommonStream.isTerminal {
return nil return nil
} }
o.state, err = term.SetRawTerminalOutput(o.fd) o.CommonStream.state, err = term.SetRawTerminalOutput(o.CommonStream.fd)
return err return err
} }
// RestoreTerminal restores normal mode to the terminal
func (o *OutStream) RestoreTerminal() {
if o.state != nil {
term.RestoreTerminal(o.fd, o.state)
}
}
// GetTtySize returns the height and width in characters of the tty // GetTtySize returns the height and width in characters of the tty
func (o *OutStream) GetTtySize() (uint, uint) { func (o *OutStream) GetTtySize() (uint, uint) {
if !o.isTerminal { if !o.isTerminal {
@ -65,5 +46,5 @@ func (o *OutStream) GetTtySize() (uint, uint) {
// NewOutStream returns a new OutStream object from a Writer // NewOutStream returns a new OutStream object from a Writer
func NewOutStream(out io.Writer) *OutStream { func NewOutStream(out io.Writer) *OutStream {
fd, isTerminal := term.GetFdInfo(out) fd, isTerminal := term.GetFdInfo(out)
return &OutStream{out: out, fd: fd, isTerminal: isTerminal} return &OutStream{CommonStream: CommonStream{fd: fd, isTerminal: isTerminal}, out: out}
} }

View File

@ -21,7 +21,7 @@ import (
) )
// ElectAuthServer returns the default registry to use (by asking the daemon) // ElectAuthServer returns the default registry to use (by asking the daemon)
func ElectAuthServer(ctx context.Context, cli *DockerCli) string { func ElectAuthServer(ctx context.Context, cli Cli) string {
// The daemon `/info` endpoint informs us of the default registry being // The daemon `/info` endpoint informs us of the default registry being
// used. This is essential in cross-platforms environment, where for // used. This is essential in cross-platforms environment, where for
// example a Linux client might be interacting with a Windows daemon, hence // example a Linux client might be interacting with a Windows daemon, hence
@ -46,7 +46,7 @@ func EncodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
// RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info // RegistryAuthenticationPrivilegedFunc returns a RequestPrivilegeFunc from the specified registry index info
// for the given command. // for the given command.
func RegistryAuthenticationPrivilegedFunc(cli *DockerCli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc { func RegistryAuthenticationPrivilegedFunc(cli Cli, index *registrytypes.IndexInfo, cmdName string) types.RequestPrivilegeFunc {
return func() (string, error) { return func() (string, error) {
fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName) fmt.Fprintf(cli.Out(), "\nPlease login prior to %s:\n", cmdName)
indexServer := registry.GetAuthConfigKey(index) indexServer := registry.GetAuthConfigKey(index)
@ -62,7 +62,7 @@ func RegistryAuthenticationPrivilegedFunc(cli *DockerCli, index *registrytypes.I
// ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the // ResolveAuthConfig is like registry.ResolveAuthConfig, but if using the
// default index, it uses the default index name for the daemon's platform, // default index, it uses the default index name for the daemon's platform,
// not the client's platform. // not the client's platform.
func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes.IndexInfo) types.AuthConfig { func ResolveAuthConfig(ctx context.Context, cli Cli, index *registrytypes.IndexInfo) types.AuthConfig {
configKey := index.Name configKey := index.Name
if index.Official { if index.Official {
configKey = ElectAuthServer(ctx, cli) configKey = ElectAuthServer(ctx, cli)
@ -73,10 +73,10 @@ func ResolveAuthConfig(ctx context.Context, cli *DockerCli, index *registrytypes
} }
// ConfigureAuth returns an AuthConfig from the specified user, password and server. // ConfigureAuth returns an AuthConfig from the specified user, password and server.
func ConfigureAuth(cli *DockerCli, flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) { func ConfigureAuth(cli Cli, flUser, flPassword, serverAddress string, isDefaultRegistry bool) (types.AuthConfig, error) {
// On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210 // On Windows, force the use of the regular OS stdin stream. Fixes #14336/#14210
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
cli.in = NewInStream(os.Stdin) cli.SetIn(NewInStream(os.Stdin))
} }
if !isDefaultRegistry { if !isDefaultRegistry {
@ -160,7 +160,7 @@ func promptWithDefault(out io.Writer, prompt string, configDefault string) {
} }
// RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image // RetrieveAuthTokenFromImage retrieves an encoded auth token given a complete image
func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image string) (string, error) { func RetrieveAuthTokenFromImage(ctx context.Context, cli Cli, image string) (string, error) {
// Retrieve encoded auth token from the image reference // Retrieve encoded auth token from the image reference
authConfig, err := resolveAuthConfigFromImage(ctx, cli, image) authConfig, err := resolveAuthConfigFromImage(ctx, cli, image)
if err != nil { if err != nil {
@ -174,7 +174,7 @@ func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image strin
} }
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string // resolveAuthConfigFromImage retrieves that AuthConfig using the image string
func resolveAuthConfigFromImage(ctx context.Context, cli *DockerCli, image string) (types.AuthConfig, error) { func resolveAuthConfigFromImage(ctx context.Context, cli Cli, image string) (types.AuthConfig, error) {
registryRef, err := reference.ParseNormalizedNamed(image) registryRef, err := reference.ParseNormalizedNamed(image)
if err != nil { if err != nil {
return types.AuthConfig{}, err return types.AuthConfig{}, err

View File

@ -45,7 +45,8 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
ctx := context.Background() ctx := context.Background()
client := dockerCli.Client() client := dockerCli.Client()
services, err := client.ServiceList(ctx, types.ServiceListOptions{}) serviceFilters := opts.filter.Value()
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceFilters})
if err != nil { if err != nil {
return err return err
} }

View File

@ -92,7 +92,15 @@ func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
if !client.IsErrServiceNotFound(err) { if !client.IsErrServiceNotFound(err) {
return err return err
} }
task, _, _ := cli.TaskInspectWithRaw(ctx, opts.target) task, _, err := cli.TaskInspectWithRaw(ctx, opts.target)
if err != nil {
if client.IsErrTaskNotFound(err) {
// if the task isn't found, rewrite the error to be clear
// that we looked for services AND tasks and found none
err = fmt.Errorf("no such task or service")
}
return err
}
tty = task.Spec.ContainerSpec.TTY tty = task.Spec.ContainerSpec.TTY
// TODO(dperny) hot fix until we get a nice details system squared away, // TODO(dperny) hot fix until we get a nice details system squared away,
// ignores details (including task context) if we have a TTY log // ignores details (including task context) if we have a TTY log
@ -104,15 +112,10 @@ func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
responseBody, err = cli.TaskLogs(ctx, opts.target, options) responseBody, err = cli.TaskLogs(ctx, opts.target, options)
if err != nil { if err != nil {
if client.IsErrTaskNotFound(err) {
// if the task ALSO isn't found, rewrite the error to be clear
// that we looked for services AND tasks
err = fmt.Errorf("No such task or service")
}
return err return err
} }
maxLength = getMaxLength(task.Slot) maxLength = getMaxLength(task.Slot)
responseBody, _ = cli.TaskLogs(ctx, opts.target, options)
} else { } else {
tty = service.Spec.TaskTemplate.ContainerSpec.TTY tty = service.Spec.TaskTemplate.ContainerSpec.TTY
// TODO(dperny) hot fix until we get a nice details system squared away, // TODO(dperny) hot fix until we get a nice details system squared away,

View File

@ -802,13 +802,13 @@ func addServiceFlags(flags *pflag.FlagSet, opts *serviceOptions, defaultFlagValu
flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health") flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health")
flags.SetAnnotation(flagHealthCmd, "version", []string{"1.25"}) flags.SetAnnotation(flagHealthCmd, "version", []string{"1.25"})
flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ns|us|ms|s|m|h)") flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ms|s|m|h)")
flags.SetAnnotation(flagHealthInterval, "version", []string{"1.25"}) flags.SetAnnotation(flagHealthInterval, "version", []string{"1.25"})
flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ns|us|ms|s|m|h)") flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ms|s|m|h)")
flags.SetAnnotation(flagHealthTimeout, "version", []string{"1.25"}) flags.SetAnnotation(flagHealthTimeout, "version", []string{"1.25"})
flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy") flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy")
flags.SetAnnotation(flagHealthRetries, "version", []string{"1.25"}) flags.SetAnnotation(flagHealthRetries, "version", []string{"1.25"})
flags.Var(&opts.healthcheck.startPeriod, flagHealthStartPeriod, "Start period for the container to initialize before counting retries towards unstable (ns|us|ms|s|m|h)") flags.Var(&opts.healthcheck.startPeriod, flagHealthStartPeriod, "Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)")
flags.SetAnnotation(flagHealthStartPeriod, "version", []string{"1.29"}) flags.SetAnnotation(flagHealthStartPeriod, "version", []string{"1.29"})
flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK") flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK")
flags.SetAnnotation(flagNoHealthcheck, "version", []string{"1.25"}) flags.SetAnnotation(flagNoHealthcheck, "version", []string{"1.25"})

View File

@ -63,7 +63,7 @@ func stateToProgress(state swarm.TaskState, rollback bool) int64 {
func ServiceProgress(ctx context.Context, client client.APIClient, serviceID string, progressWriter io.WriteCloser) error { func ServiceProgress(ctx context.Context, client client.APIClient, serviceID string, progressWriter io.WriteCloser) error {
defer progressWriter.Close() defer progressWriter.Close()
progressOut := streamformatter.NewJSONStreamFormatter().NewProgressOutput(progressWriter, false) progressOut := streamformatter.NewJSONProgressOutput(progressWriter, false)
sigint := make(chan os.Signal, 1) sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt) signal.Notify(sigint, os.Interrupt)

View File

@ -18,9 +18,7 @@ func getStackFilter(namespace string) filters.Args {
} }
func getServiceFilter(namespace string) filters.Args { func getServiceFilter(namespace string) filters.Args {
filter := getStackFilter(namespace) return getStackFilter(namespace)
filter.Add("runtime", string(swarm.RuntimeContainer))
return filter
} }
func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args { func getStackFilterFromOpt(namespace string, opt opts.FilterOpt) filters.Args {

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"sort" "sort"
"strings" "strings"
@ -20,7 +21,7 @@ import (
) )
func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error { func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deployOptions) error {
configDetails, err := getConfigDetails(opts) configDetails, err := getConfigDetails(opts.composefile)
if err != nil { if err != nil {
return err return err
} }
@ -108,16 +109,16 @@ func propertyWarnings(properties map[string]string) string {
return strings.Join(msgs, "\n\n") return strings.Join(msgs, "\n\n")
} }
func getConfigDetails(opts deployOptions) (composetypes.ConfigDetails, error) { func getConfigDetails(composefile string) (composetypes.ConfigDetails, error) {
var details composetypes.ConfigDetails var details composetypes.ConfigDetails
var err error
details.WorkingDir, err = os.Getwd() absPath, err := filepath.Abs(composefile)
if err != nil { if err != nil {
return details, err return details, err
} }
details.WorkingDir = filepath.Dir(absPath)
configFile, err := getConfigFile(opts.composefile) configFile, err := getConfigFile(composefile)
if err != nil { if err != nil {
return details, err return details, err
} }

View File

@ -0,0 +1,28 @@
package stack
import (
"os"
"path/filepath"
"testing"
"github.com/docker/docker/pkg/testutil/tempfile"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetConfigDetails(t *testing.T) {
content := `
version: "3.0"
services:
foo:
image: alpine:3.5
`
file := tempfile.NewTempFile(t, "test-get-config-details", content)
defer file.Remove()
details, err := getConfigDetails(file.Name())
require.NoError(t, err)
assert.Equal(t, filepath.Dir(file.Name()), details.WorkingDir)
assert.Len(t, details.ConfigFiles, 1)
assert.Len(t, details.Environment, len(os.Environ()))
}

View File

@ -1,14 +1,11 @@
package stack package stack
import ( import (
"fmt"
"io"
"sort" "sort"
"strconv"
"text/tabwriter"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/compose/convert" "github.com/docker/cli/cli/compose/convert"
"github.com/docker/cli/client" "github.com/docker/cli/client"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -17,11 +14,8 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
) )
const (
listItemFmt = "%s\t%s\n"
)
type listOptions struct { type listOptions struct {
format string
} }
func newListCommand(dockerCli *command.DockerCli) *cobra.Command { func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
@ -37,6 +31,8 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
}, },
} }
flags := cmd.Flags()
flags.StringVar(&opts.format, "format", "", "Pretty-print stacks using a Go template")
return cmd return cmd
} }
@ -48,55 +44,32 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
if err != nil { if err != nil {
return err return err
} }
format := opts.format
out := dockerCli.Out() if len(format) == 0 {
printTable(out, stacks) format = formatter.TableFormatKey
return nil }
stackCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewStackFormat(format),
}
sort.Sort(byName(stacks))
return formatter.StackWrite(stackCtx, stacks)
} }
type byName []*stack type byName []*formatter.Stack
func (n byName) Len() int { return len(n) } func (n byName) Len() int { return len(n) }
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] } func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name } func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name }
func printTable(out io.Writer, stacks []*stack) { func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.Stack, error) {
writer := tabwriter.NewWriter(out, 0, 4, 2, ' ', 0)
// Ignore flushing errors
defer writer.Flush()
sort.Sort(byName(stacks))
fmt.Fprintf(writer, listItemFmt, "NAME", "SERVICES")
for _, stack := range stacks {
fmt.Fprintf(
writer,
listItemFmt,
stack.Name,
strconv.Itoa(stack.Services),
)
}
}
type stack struct {
// Name is the name of the stack
Name string
// Services is the number of the services
Services int
}
func getStacks(
ctx context.Context,
apiclient client.APIClient,
) ([]*stack, error) {
services, err := apiclient.ServiceList( services, err := apiclient.ServiceList(
ctx, ctx,
types.ServiceListOptions{Filters: getAllStacksFilter()}) types.ServiceListOptions{Filters: getAllStacksFilter()})
if err != nil { if err != nil {
return nil, err return nil, err
} }
m := make(map[string]*stack, 0) m := make(map[string]*formatter.Stack, 0)
for _, service := range services { for _, service := range services {
labels := service.Spec.Labels labels := service.Spec.Labels
name, ok := labels[convert.LabelNamespace] name, ok := labels[convert.LabelNamespace]
@ -106,7 +79,7 @@ func getStacks(
} }
ztack, ok := m[name] ztack, ok := m[name]
if !ok { if !ok {
m[name] = &stack{ m[name] = &formatter.Stack{
Name: name, Name: name,
Services: 1, Services: 1,
} }
@ -114,7 +87,7 @@ func getStacks(
ztack.Services++ ztack.Services++
} }
} }
var stacks []*stack var stacks []*formatter.Stack
for _, stack := range m { for _, stack := range m {
stacks = append(stacks, stack) stacks = append(stacks, stack)
} }

34
cli/command/stream.go Normal file
View File

@ -0,0 +1,34 @@
package command
import (
"github.com/docker/docker/pkg/term"
)
// CommonStream is an input stream used by the DockerCli to read user input
type CommonStream struct {
fd uintptr
isTerminal bool
state *term.State
}
// FD returns the file descriptor number for this stream
func (s *CommonStream) FD() uintptr {
return s.fd
}
// IsTerminal returns true if this stream is connected to a terminal
func (s *CommonStream) IsTerminal() bool {
return s.isTerminal
}
// RestoreTerminal restores normal mode to the terminal
func (s *CommonStream) RestoreTerminal() {
if s.state != nil {
term.RestoreTerminal(s.fd, s.state)
}
}
// SetIsTerminal sets the boolean used for isTerminal
func (s *CommonStream) SetIsTerminal(isTerminal bool) {
s.isTerminal = isTerminal
}

View File

@ -19,6 +19,7 @@ type initOptions struct {
listenAddr NodeAddrOption listenAddr NodeAddrOption
// Not a NodeAddrOption because it has no default port. // Not a NodeAddrOption because it has no default port.
advertiseAddr string advertiseAddr string
dataPathAddr string
forceNewCluster bool forceNewCluster bool
availability string availability string
} }
@ -40,6 +41,7 @@ func newInitCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags() flags := cmd.Flags()
flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])") flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])")
flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])") flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])")
flags.StringVar(&opts.dataPathAddr, flagDataPathAddr, "", "Address or interface to use for data path traffic (format: <ip|interface>)")
flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state") flags.BoolVar(&opts.forceNewCluster, "force-new-cluster", false, "Force create a new cluster from current state")
flags.BoolVar(&opts.autolock, flagAutolock, false, "Enable manager autolocking (requiring an unlock key to start a stopped manager)") flags.BoolVar(&opts.autolock, flagAutolock, false, "Enable manager autolocking (requiring an unlock key to start a stopped manager)")
flags.StringVar(&opts.availability, flagAvailability, "active", `Availability of the node ("active"|"pause"|"drain")`) flags.StringVar(&opts.availability, flagAvailability, "active", `Availability of the node ("active"|"pause"|"drain")`)
@ -54,6 +56,7 @@ func runInit(dockerCli command.Cli, flags *pflag.FlagSet, opts initOptions) erro
req := swarm.InitRequest{ req := swarm.InitRequest{
ListenAddr: opts.listenAddr.String(), ListenAddr: opts.listenAddr.String(),
AdvertiseAddr: opts.advertiseAddr, AdvertiseAddr: opts.advertiseAddr,
DataPathAddr: opts.dataPathAddr,
ForceNewCluster: opts.forceNewCluster, ForceNewCluster: opts.forceNewCluster,
Spec: opts.swarmOptions.ToSpec(flags), Spec: opts.swarmOptions.ToSpec(flags),
AutoLockManagers: opts.swarmOptions.autolock, AutoLockManagers: opts.swarmOptions.autolock,

View File

@ -19,6 +19,7 @@ type joinOptions struct {
listenAddr NodeAddrOption listenAddr NodeAddrOption
// Not a NodeAddrOption because it has no default port. // Not a NodeAddrOption because it has no default port.
advertiseAddr string advertiseAddr string
dataPathAddr string
token string token string
availability string availability string
} }
@ -41,6 +42,7 @@ func newJoinCommand(dockerCli command.Cli) *cobra.Command {
flags := cmd.Flags() flags := cmd.Flags()
flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])") flags.Var(&opts.listenAddr, flagListenAddr, "Listen address (format: <ip|interface>[:port])")
flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])") flags.StringVar(&opts.advertiseAddr, flagAdvertiseAddr, "", "Advertised address (format: <ip|interface>[:port])")
flags.StringVar(&opts.dataPathAddr, flagDataPathAddr, "", "Address or interface to use for data path traffic (format: <ip|interface>)")
flags.StringVar(&opts.token, flagToken, "", "Token for entry into the swarm") flags.StringVar(&opts.token, flagToken, "", "Token for entry into the swarm")
flags.StringVar(&opts.availability, flagAvailability, "active", `Availability of the node ("active"|"pause"|"drain")`) flags.StringVar(&opts.availability, flagAvailability, "active", `Availability of the node ("active"|"pause"|"drain")`)
return cmd return cmd
@ -54,6 +56,7 @@ func runJoin(dockerCli command.Cli, flags *pflag.FlagSet, opts joinOptions) erro
JoinToken: opts.token, JoinToken: opts.token,
ListenAddr: opts.listenAddr.String(), ListenAddr: opts.listenAddr.String(),
AdvertiseAddr: opts.advertiseAddr, AdvertiseAddr: opts.advertiseAddr,
DataPathAddr: opts.dataPathAddr,
RemoteAddrs: []string{opts.remote}, RemoteAddrs: []string{opts.remote},
} }
if flags.Changed(flagAvailability) { if flags.Changed(flagAvailability) {

View File

@ -108,10 +108,10 @@ func printJoinCommand(ctx context.Context, dockerCli command.Cli, nodeID string,
if node.ManagerStatus != nil { if node.ManagerStatus != nil {
if worker { if worker {
fmt.Fprintf(dockerCli.Out(), "To add a worker to this swarm, run the following command:\n\n docker swarm join \\\n --token %s \\\n %s\n\n", sw.JoinTokens.Worker, node.ManagerStatus.Addr) fmt.Fprintf(dockerCli.Out(), "To add a worker to this swarm, run the following command:\n\n docker swarm join --token %s %s\n\n", sw.JoinTokens.Worker, node.ManagerStatus.Addr)
} }
if manager { if manager {
fmt.Fprintf(dockerCli.Out(), "To add a manager to this swarm, run the following command:\n\n docker swarm join \\\n --token %s \\\n %s\n\n", sw.JoinTokens.Manager, node.ManagerStatus.Addr) fmt.Fprintf(dockerCli.Out(), "To add a manager to this swarm, run the following command:\n\n docker swarm join --token %s %s\n\n", sw.JoinTokens.Manager, node.ManagerStatus.Addr)
} }
} }

View File

@ -2,7 +2,9 @@ package swarm
import ( import (
"encoding/csv" "encoding/csv"
"encoding/pem"
"fmt" "fmt"
"io/ioutil"
"strings" "strings"
"time" "time"
@ -19,6 +21,7 @@ const (
flagDispatcherHeartbeat = "dispatcher-heartbeat" flagDispatcherHeartbeat = "dispatcher-heartbeat"
flagListenAddr = "listen-addr" flagListenAddr = "listen-addr"
flagAdvertiseAddr = "advertise-addr" flagAdvertiseAddr = "advertise-addr"
flagDataPathAddr = "data-path-addr"
flagQuiet = "quiet" flagQuiet = "quiet"
flagRotate = "rotate" flagRotate = "rotate"
flagToken = "token" flagToken = "token"
@ -154,6 +157,15 @@ func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
case "url": case "url":
hasURL = true hasURL = true
externalCA.URL = value externalCA.URL = value
case "cacert":
cacontents, err := ioutil.ReadFile(value)
if err != nil {
return nil, errors.Wrap(err, "unable to read CA cert for external CA")
}
if pemBlock, _ := pem.Decode(cacontents); pemBlock == nil {
return nil, errors.New("CA cert for external CA must be in PEM format")
}
externalCA.CACert = string(cacontents)
default: default:
externalCA.Options[key] = value externalCA.Options[key] = value
} }

View File

@ -2,7 +2,4 @@ Successfully rotated manager join token.
To add a manager to this swarm, run the following command: To add a manager to this swarm, run the following command:
docker swarm join \ docker swarm join --token manager-join-token 127.0.0.1
--token manager-join-token \
127.0.0.1

View File

@ -1,6 +1,3 @@
To add a manager to this swarm, run the following command: To add a manager to this swarm, run the following command:
docker swarm join \ docker swarm join --token manager-join-token 127.0.0.1
--token manager-join-token \
127.0.0.1

View File

@ -1,6 +1,3 @@
To add a worker to this swarm, run the following command: To add a worker to this swarm, run the following command:
docker swarm join \ docker swarm join --token worker-join-token 127.0.0.1
--token worker-join-token \
127.0.0.1

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test" "github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
@ -96,7 +97,7 @@ func TestSwarmUnlock(t *testing.T) {
return nil return nil
}, },
}, buf) }, buf)
dockerCli.SetIn(ioutil.NopCloser(strings.NewReader(input))) dockerCli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
cmd := newUnlockCommand(dockerCli) cmd := newUnlockCommand(dockerCli)
assert.NoError(t, cmd.Execute()) assert.NoError(t, cmd.Execute())
} }

View File

@ -91,6 +91,10 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error {
fmt.Fprintf(dockerCli.Out(), "\n") fmt.Fprintf(dockerCli.Out(), "\n")
} }
fmt.Fprintf(dockerCli.Out(), " Log:")
fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Log, " "))
fmt.Fprintf(dockerCli.Out(), "\n")
fmt.Fprintf(dockerCli.Out(), "Swarm: %v\n", info.Swarm.LocalNodeState) fmt.Fprintf(dockerCli.Out(), "Swarm: %v\n", info.Swarm.LocalNodeState)
if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked { if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked {
fmt.Fprintf(dockerCli.Out(), " NodeID: %s\n", info.Swarm.NodeID) fmt.Fprintf(dockerCli.Out(), " NodeID: %s\n", info.Swarm.NodeID)

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test" "github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
@ -91,7 +92,7 @@ func TestVolumePrunePromptYes(t *testing.T) {
volumePruneFunc: simplePruneFunc, volumePruneFunc: simplePruneFunc,
}, buf) }, buf)
cli.SetIn(ioutil.NopCloser(strings.NewReader(input))) cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
cmd := NewPruneCommand( cmd := NewPruneCommand(
cli, cli,
) )
@ -113,7 +114,7 @@ func TestVolumePrunePromptNo(t *testing.T) {
volumePruneFunc: simplePruneFunc, volumePruneFunc: simplePruneFunc,
}, buf) }, buf)
cli.SetIn(ioutil.NopCloser(strings.NewReader(input))) cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input))))
cmd := NewPruneCommand( cmd := NewPruneCommand(
cli, cli,
) )

File diff suppressed because one or more lines are too long

View File

@ -72,6 +72,7 @@
"context": {"type": "string"}, "context": {"type": "string"},
"dockerfile": {"type": "string"}, "dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"}, "args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"cache_from": {"$ref": "#/definitions/list_of_strings"} "cache_from": {"$ref": "#/definitions/list_of_strings"}
}, },
"additionalProperties": false "additionalProperties": false

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/client" "github.com/docker/cli/client"
) )
@ -15,23 +16,24 @@ type FakeCli struct {
command.DockerCli command.DockerCli
client client.APIClient client client.APIClient
configfile *configfile.ConfigFile configfile *configfile.ConfigFile
out io.Writer out *command.OutStream
err io.Writer err io.Writer
in io.ReadCloser in *command.InStream
store credentials.Store
} }
// NewFakeCli returns a Cli backed by the fakeCli // NewFakeCli returns a Cli backed by the fakeCli
func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli { func NewFakeCli(client client.APIClient, out io.Writer) *FakeCli {
return &FakeCli{ return &FakeCli{
client: client, client: client,
out: out, out: command.NewOutStream(out),
err: ioutil.Discard, err: ioutil.Discard,
in: ioutil.NopCloser(strings.NewReader("")), in: command.NewInStream(ioutil.NopCloser(strings.NewReader(""))),
} }
} }
// SetIn sets the input of the cli to the specified ReadCloser // SetIn sets the input of the cli to the specified ReadCloser
func (c *FakeCli) SetIn(in io.ReadCloser) { func (c *FakeCli) SetIn(in *command.InStream) {
c.in = in c.in = in
} }
@ -52,7 +54,7 @@ func (c *FakeCli) Client() client.APIClient {
// Out returns the output stream (stdout) the cli should write on // Out returns the output stream (stdout) the cli should write on
func (c *FakeCli) Out() *command.OutStream { func (c *FakeCli) Out() *command.OutStream {
return command.NewOutStream(c.out) return c.out
} }
// Err returns the output stream (stderr) the cli should write on // Err returns the output stream (stderr) the cli should write on
@ -62,10 +64,18 @@ func (c *FakeCli) Err() io.Writer {
// In returns the input stream the cli will use // In returns the input stream the cli will use
func (c *FakeCli) In() *command.InStream { func (c *FakeCli) In() *command.InStream {
return command.NewInStream(c.in) return c.in
} }
// ConfigFile returns the cli configfile object (to get client configuration) // ConfigFile returns the cli configfile object (to get client configuration)
func (c *FakeCli) ConfigFile() *configfile.ConfigFile { func (c *FakeCli) ConfigFile() *configfile.ConfigFile {
return c.configfile return c.configfile
} }
// CredentialsStore returns the fake store the cli will use
func (c *FakeCli) CredentialsStore(serverAddress string) credentials.Store {
if c.store == nil {
c.store = NewFakeStore()
}
return c.store
}

View File

@ -0,0 +1,74 @@
package test
import (
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/docker/api/types"
)
// fake store implements a credentials.Store that only acts as an in memory map
type fakeStore struct {
store map[string]types.AuthConfig
eraseFunc func(serverAddress string) error
getFunc func(serverAddress string) (types.AuthConfig, error)
getAllFunc func() (map[string]types.AuthConfig, error)
storeFunc func(authConfig types.AuthConfig) error
}
// NewFakeStore creates a new file credentials store.
func NewFakeStore() credentials.Store {
return &fakeStore{store: map[string]types.AuthConfig{}}
}
func (c *fakeStore) SetStore(store map[string]types.AuthConfig) {
c.store = store
}
func (c *fakeStore) SetEraseFunc(eraseFunc func(string) error) {
c.eraseFunc = eraseFunc
}
func (c *fakeStore) SetGetFunc(getFunc func(string) (types.AuthConfig, error)) {
c.getFunc = getFunc
}
func (c *fakeStore) SetGetAllFunc(getAllFunc func() (map[string]types.AuthConfig, error)) {
c.getAllFunc = getAllFunc
}
func (c *fakeStore) SetStoreFunc(storeFunc func(types.AuthConfig) error) {
c.storeFunc = storeFunc
}
// Erase removes the given credentials from the map store
func (c *fakeStore) Erase(serverAddress string) error {
if c.eraseFunc != nil {
return c.eraseFunc(serverAddress)
}
delete(c.store, serverAddress)
return nil
}
// Get retrieves credentials for a specific server from the map store.
func (c *fakeStore) Get(serverAddress string) (types.AuthConfig, error) {
if c.getFunc != nil {
return c.getFunc(serverAddress)
}
authConfig, _ := c.store[serverAddress]
return authConfig, nil
}
func (c *fakeStore) GetAll() (map[string]types.AuthConfig, error) {
if c.getAllFunc != nil {
return c.getAllFunc()
}
return c.store, nil
}
// Store saves the given credentials in the map store.
func (c *fakeStore) Store(authConfig types.AuthConfig) error {
if c.storeFunc != nil {
return c.storeFunc(authConfig)
}
c.store[authConfig.ServerAddress] = authConfig
return nil
}

View File

@ -6,7 +6,7 @@ github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20 github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621 github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
github.com/docker/docker f33f2578881af36dc403a2063a00b19a4844fcbe github.com/docker/docker d624f9a7b00419179eb0e7e81f2894dde0752873
github.com/docker/docker-credential-helpers v0.5.0 github.com/docker/docker-credential-helpers v0.5.0
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5 github.com/docker/go-connections e15c02316c12de00874640cd76311849de2aeed5
@ -17,7 +17,6 @@ github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
github.com/docker/notary v0.4.2 github.com/docker/notary v0.4.2
github.com/docker/swarmkit b19d028de0a6e9ca281afeb76cea2544b9edd839 github.com/docker/swarmkit b19d028de0a6e9ca281afeb76cea2544b9edd839
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
github.com/go-check/check 4ed411733c5785b40214c70bce814c3a3a689609 https://github.com/cpuguy83/check.git
github.com/gogo/protobuf 7efa791bd276fd4db00867cbd982b552627c24cb github.com/gogo/protobuf 7efa791bd276fd4db00867cbd982b552627c24cb
github.com/golang/protobuf 8ee79997227bf9b34611aee7946ae64735e6fd93 github.com/golang/protobuf 8ee79997227bf9b34611aee7946ae64735e6fd93
github.com/gorilla/context v1.1 github.com/gorilla/context v1.1
@ -28,6 +27,7 @@ github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715 github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb
github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git
github.com/opencontainers/selinux ba1aefe8057f1d0cfb8e88d0ec1dc85925ef987d
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2 github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2
github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git
@ -43,3 +43,4 @@ golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
google.golang.org/grpc v1.0.4 google.golang.org/grpc v1.0.4
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6 gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6

View File

@ -1,270 +1,80 @@
Docker: the container engine [![Release](https://img.shields.io/github/release/docker/docker.svg)](https://github.com/docker/docker/releases/latest) ### Docker users, see [Moby and Docker](https://mobyproject.org/#moby-and-docker) to clarify the relationship between the projects
============================
Docker is an open source project to pack, ship and run any application ### Docker maintainers and contributors, see [Transitioning to Moby](#transitioning-to-moby) for more details
as a lightweight container.
Docker containers are both *hardware-agnostic* and *platform-agnostic*. The Moby Project
This means they can run anywhere, from your laptop to the largest ================
cloud compute instance and everything in between - and they don't require
you to use a particular language, framework or packaging system. That
makes them great building blocks for deploying and scaling web apps,
databases, and backend services without depending on a particular stack
or provider.
Docker began as an open-source implementation of the deployment engine which ![Moby Project logo](docs/static_files/moby-project-logo.png "The Moby Project")
powered [dotCloud](http://web.archive.org/web/20130530031104/https://www.dotcloud.com/),
a popular Platform-as-a-Service. It benefits directly from the experience
accumulated over several years of large-scale operation and support of hundreds
of thousands of applications and databases.
![Docker logo](docs/static_files/docker-logo-compressed.png "Docker") Moby is an open-source project created by Docker to advance the software containerization movement.
It provides a “Lego set” of dozens of components, the framework for assembling them into custom container-based systems, and a place for all container enthusiasts to experiment and exchange ideas.
## Security Disclosure # Moby
Security is very important to us. If you have any issue regarding security, ## Overview
please disclose the information responsibly by sending an email to
security@docker.com and not by creating a GitHub issue.
## Better than VMs At the core of Moby is a framework to assemble specialized container systems.
It provides:
A common method for distributing applications and sandboxing their - A library of containerized components for all vital aspects of a container system: OS, container runtime, orchestration, infrastructure management, networking, storage, security, build, image distribution, etc.
execution is to use virtual machines, or VMs. Typical VM formats are - Tools to assemble the components into runnable artifacts for a variety of platforms and architectures: bare metal (both x86 and Arm); executables for Linux, Mac and Windows; VM images for popular cloud and virtualization providers.
VMware's vmdk, Oracle VirtualBox's vdi, and Amazon EC2's ami. In theory - A set of reference assemblies which can be used as-is, modified, or used as inspiration to create your own.
these formats should allow every developer to automatically package
their application into a "machine" for easy distribution and deployment.
In practice, that almost never happens, for a few reasons:
* *Size*: VMs are very large which makes them impractical to store All Moby components are containers, so creating new components is as easy as building a new OCI-compatible container.
and transfer.
* *Performance*: running VMs consumes significant CPU and memory,
which makes them impractical in many scenarios, for example local
development of multi-tier applications, and large-scale deployment
of cpu and memory-intensive applications on large numbers of
machines.
* *Portability*: competing VM environments don't play well with each
other. Although conversion tools do exist, they are limited and
add even more overhead.
* *Hardware-centric*: VMs were designed with machine operators in
mind, not software developers. As a result, they offer very
limited tooling for what developers need most: building, testing
and running their software. For example, VMs offer no facilities
for application versioning, monitoring, configuration, logging or
service discovery.
By contrast, Docker relies on a different sandboxing method known as ## Principles
*containerization*. Unlike traditional virtualization, containerization
takes place at the kernel level. Most modern operating system kernels
now support the primitives necessary for containerization, including
Linux with [openvz](https://openvz.org),
[vserver](http://linux-vserver.org) and more recently
[lxc](https://linuxcontainers.org/), Solaris with
[zones](https://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc),
and FreeBSD with
[Jails](https://www.freebsd.org/doc/handbook/jails.html).
Docker builds on top of these low-level primitives to offer developers a Moby is an open project guided by strong principles, but modular, flexible and without too strong an opinion on user experience, so it is open to the community to help set its direction.
portable format and runtime environment that solves all four problems. The guiding principles are:
Docker containers are small (and their transfer can be optimized with
layers), they have basically zero memory and cpu overhead, they are
completely portable, and are designed from the ground up with an
application-centric design.
Perhaps best of all, because Docker operates at the OS level, it can still be - Batteries included but swappable: Moby includes enough components to build fully featured container system, but its modular architecture ensures that most of the components can be swapped by different implementations.
run inside a VM! - Usable security: Moby will provide secure defaults without compromising usability.
- Container centric: Moby is built with containers, for running containers.
## Plays well with others With Moby, you should be able to describe all the components of your distributed application, from the high-level configuration files down to the kernel you would like to use and build and deploy it easily.
Docker does not require you to buy into a particular programming Moby uses [containerd](https://github.com/containerd/containerd) as the default container runtime.
language, framework, packaging system, or configuration language.
Is your application a Unix process? Does it use files, tcp connections, ## Audience
environment variables, standard Unix streams and command-line arguments
as inputs and outputs? Then Docker can run it.
Can your application's build be expressed as a sequence of such Moby is recommended for anyone who wants to assemble a container-based system. This includes:
commands? Then Docker can build it.
## Escape dependency hell - Hackers who want to customize or patch their Docker build
- System engineers or integrators building a container system
- Infrastructure providers looking to adapt existing container systems to their environment
- Container enthusiasts who want to experiment with the latest container tech
- Open-source developers looking to test their project in a variety of different systems
- Anyone curious about Docker internals and how its built
A common problem for developers is the difficulty of managing all Moby is NOT recommended for:
their application's dependencies in a simple and automated way.
This is usually difficult for several reasons: - Application developers looking for an easy way to run their applications in containers. We recommend Docker CE instead.
- Enterprise IT and development teams looking for a ready-to-use, commercially supported container platform. We recommend Docker EE instead.
- Anyone curious about containers and looking for an easy way to learn. We recommend the docker.com website instead.
* *Cross-platform dependencies*. Modern applications often depend on # Transitioning to Moby
a combination of system libraries and binaries, language-specific
packages, framework-specific modules, internal components
developed for another project, etc. These dependencies live in
different "worlds" and require different tools - these tools
typically don't work well with each other, requiring awkward
custom integrations.
* *Conflicting dependencies*. Different applications may depend on Docker is transitioning all of its open source collaborations to the Moby project going forward.
different versions of the same dependency. Packaging tools handle During the transition, all open source activity should continue as usual.
these situations with various degrees of ease - but they all
handle them in different and incompatible ways, which again forces
the developer to do extra work.
* *Custom dependencies*. A developer may need to prepare a custom We are proposing the following list of changes:
version of their application's dependency. Some packaging systems
can handle custom versions of a dependency, others can't - and all
of them handle it differently.
- splitting up the engine into more open components
- removing the docker UI, SDK etc to keep them in the Docker org
- clarifying that the project is not limited to the engine, but to the assembly of all the individual components of the Docker platform
- open-source new tools & components which we currently use to assemble the Docker product, but could benefit the community
- defining an open, community-centric governance inspired by the Fedora project (a very successful example of balancing the needs of the community with the constraints of the primary corporate sponsor)
Docker solves the problem of dependency hell by giving developers a simple -----
way to express *all* their application's dependencies in one place, while
streamlining the process of assembling them. If this makes you think of
[XKCD 927](https://xkcd.com/927/), don't worry. Docker doesn't
*replace* your favorite packaging systems. It simply orchestrates
their use in a simple and repeatable way. How does it do that? With
layers.
Docker defines a build as running a sequence of Unix commands, one Legal
after the other, in the same container. Build commands modify the =====
contents of the container (usually by installing new files on the
filesystem), the next command modifies it some more, etc. Since each
build command inherits the result of the previous commands, the
*order* in which the commands are executed expresses *dependencies*.
Here's a typical Docker build process:
```bash
FROM ubuntu:12.04
RUN apt-get update && apt-get install -y python python-pip curl
RUN curl -sSL https://github.com/shykes/helloflask/archive/master.tar.gz | tar -xzv
RUN cd helloflask-master && pip install -r requirements.txt
```
Note that Docker doesn't care *how* dependencies are built - as long
as they can be built by running a Unix command in a container.
Getting started
===============
Docker can be installed either on your computer for building applications or
on servers for running them. To get started, [check out the installation
instructions in the
documentation](https://docs.docker.com/engine/installation/).
Usage examples
==============
Docker can be used to run short-lived commands, long-running daemons
(app servers, databases, etc.), interactive shell sessions, etc.
You can find a [list of real-world
examples](https://docs.docker.com/engine/examples/) in the
documentation.
Under the hood
--------------
Under the hood, Docker is built on the following components:
* The
[cgroups](https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt)
and
[namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html)
capabilities of the Linux kernel
* The [Go](https://golang.org) programming language
* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md)
* The [Libcontainer Specification](https://github.com/opencontainers/runc/blob/master/libcontainer/SPEC.md)
Contributing to Docker [![GoDoc](https://godoc.org/github.com/docker/docker?status.svg)](https://godoc.org/github.com/docker/docker)
======================
| **Master** (Linux) | **Experimental** (Linux) | **Windows** | **FreeBSD** |
|------------------|----------------------|---------|---------|
| [![Jenkins Build Status](https://jenkins.dockerproject.org/view/Docker/job/Docker%20Master/badge/icon)](https://jenkins.dockerproject.org/view/Docker/job/Docker%20Master/) | [![Jenkins Build Status](https://jenkins.dockerproject.org/view/Docker/job/Docker%20Master%20%28experimental%29/badge/icon)](https://jenkins.dockerproject.org/view/Docker/job/Docker%20Master%20%28experimental%29/) | [![Build Status](http://jenkins.dockerproject.org/job/Docker%20Master%20(windows)/badge/icon)](http://jenkins.dockerproject.org/job/Docker%20Master%20(windows)/) | [![Build Status](http://jenkins.dockerproject.org/job/Docker%20Master%20(freebsd)/badge/icon)](http://jenkins.dockerproject.org/job/Docker%20Master%20(freebsd)/) |
Want to hack on Docker? Awesome! We have [instructions to help you get
started contributing code or documentation](https://docs.docker.com/opensource/project/who-written-for/).
These instructions are probably not perfect, please let us know if anything
feels wrong or incomplete. Better yet, submit a PR and improve them yourself.
Getting the development builds
==============================
Want to run Docker from a master build? You can download
master builds at [master.dockerproject.org](https://master.dockerproject.org).
They are updated with each commit merged into the master branch.
Don't know how to use that super cool new feature in the master build? Check
out the master docs at
[docs.master.dockerproject.org](http://docs.master.dockerproject.org).
How the project is run
======================
Docker is a very, very active project. If you want to learn more about how it is run,
or want to get more involved, the best place to start is [the project directory](https://github.com/docker/docker/tree/master/project).
We are always open to suggestions on process improvements, and are always looking for more maintainers.
### Talking to other Docker users and contributors
<table class="tg">
<col width="45%">
<col width="65%">
<tr>
<td>Internet&nbsp;Relay&nbsp;Chat&nbsp;(IRC)</td>
<td>
<p>
IRC is a direct line to our most knowledgeable Docker users; we have
both the <code>#docker</code> and <code>#docker-dev</code> group on
<strong>irc.freenode.net</strong>.
IRC is a rich chat protocol but it can overwhelm new users. You can search
<a href="https://botbot.me/freenode/docker/#" target="_blank">our chat archives</a>.
</p>
Read our <a href="https://docs.docker.com/opensource/get-help/#/irc-quickstart" target="_blank">IRC quickstart guide</a> for an easy way to get started.
</td>
</tr>
<tr>
<td>Docker Community Forums</td>
<td>
The <a href="https://forums.docker.com/c/open-source-projects/de" target="_blank">Docker Engine</a>
group is for users of the Docker Engine project.
</td>
</tr>
<tr>
<td>Google Groups</td>
<td>
The <a href="https://groups.google.com/forum/#!forum/docker-dev"
target="_blank">docker-dev</a> group is for contributors and other people
contributing to the Docker project. You can join this group without a
Google account by sending an email to <a
href="mailto:docker-dev+subscribe@googlegroups.com">docker-dev+subscribe@googlegroups.com</a>.
You'll receive a join-request message; simply reply to the message to
confirm your subscription.
</td>
</tr>
<tr>
<td>Twitter</td>
<td>
You can follow <a href="https://twitter.com/docker/" target="_blank">Docker's Twitter feed</a>
to get updates on our products. You can also tweet us questions or just
share blogs or stories.
</td>
</tr>
<tr>
<td>Stack Overflow</td>
<td>
Stack Overflow has thousands of Docker questions listed. We regularly
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
and so do many other knowledgeable Docker users.
</td>
</tr>
</table>
### Legal
*Brought to you courtesy of our legal counsel. For more context, *Brought to you courtesy of our legal counsel. For more context,
please see the [NOTICE](https://github.com/docker/docker/blob/master/NOTICE) document in this repo.* please see the [NOTICE](https://github.com/moby/moby/blob/master/NOTICE) document in this repo.*
Use and transfer of Docker may be subject to certain restrictions by the Use and transfer of Moby may be subject to certain restrictions by the
United States and other governments. United States and other governments.
It is your responsibility to ensure that your use and/or transfer does not It is your responsibility to ensure that your use and/or transfer does not
@ -275,30 +85,6 @@ For more information, please see https://www.bis.doc.gov
Licensing Licensing
========= =========
Docker is licensed under the Apache License, Version 2.0. See Moby is licensed under the Apache License, Version 2.0. See
[LICENSE](https://github.com/docker/docker/blob/master/LICENSE) for the full [LICENSE](https://github.com/moby/moby/blob/master/LICENSE) for the full
license text. license text.
Other Docker Related Projects
=============================
There are a number of projects under development that are based on Docker's
core technology. These projects expand the tooling built around the
Docker platform to broaden its application and utility.
* [Docker Registry](https://github.com/docker/distribution): Registry
server for Docker (hosting/delivery of repositories and images)
* [Docker Machine](https://github.com/docker/machine): Machine management
for a container-centric world
* [Docker Swarm](https://github.com/docker/swarm): A Docker-native clustering
system
* [Docker Compose](https://github.com/docker/compose) (formerly Fig):
Define and run multi-container apps
* [Kitematic](https://github.com/docker/kitematic): The easiest way to use
Docker on Mac and Windows
If you know of another project underway that should be listed here, please help
us keep this list up-to-date by submitting a PR.
Awesome-Docker
==============
You can find more projects, tools and articles related to Docker on the [awesome-docker list](https://github.com/veggiemonk/awesome-docker). Add your project there.

View File

@ -14,8 +14,8 @@ It consists of various components in this repository:
The API is defined by the [Swagger](http://swagger.io/specification/) definition in `api/swagger.yaml`. This definition can be used to: The API is defined by the [Swagger](http://swagger.io/specification/) definition in `api/swagger.yaml`. This definition can be used to:
1. To automatically generate documentation. 1. Automatically generate documentation.
2. To automatically generate the Go server and client. (A work-in-progress.) 2. Automatically generate the Go server and client. (A work-in-progress.)
3. Provide a machine readable version of the API for introspecting what it can do, automatically generating clients for other languages, etc. 3. Provide a machine readable version of the API for introspecting what it can do, automatically generating clients for other languages, etc.
## Updating the API documentation ## Updating the API documentation

View File

@ -7,6 +7,12 @@ import (
"github.com/docker/go-connections/nat" "github.com/docker/go-connections/nat"
) )
// MinimumDuration puts a minimum on user configured duration.
// This is to prevent API error on time unit. For example, API may
// set 3 as healthcheck interval with intention of 3 seconds, but
// Docker interprets it as 3 nanoseconds.
const MinimumDuration = 1 * time.Millisecond
// HealthConfig holds configuration settings for the HEALTHCHECK feature. // HealthConfig holds configuration settings for the HEALTHCHECK feature.
type HealthConfig struct { type HealthConfig struct {
// Test is the test to perform to check that the container is healthy. // Test is the test to perform to check that the container is healthy.

View File

@ -377,7 +377,4 @@ type HostConfig struct {
// Run a custom init inside the container, if null, use the daemon's configured settings // Run a custom init inside the container, if null, use the daemon's configured settings
Init *bool `json:",omitempty"` Init *bool `json:",omitempty"`
// Custom init path
InitPath string `json:",omitempty"`
} }

View File

@ -126,12 +126,17 @@ type ExternalCA struct {
// Options is a set of additional key/value pairs whose interpretation // Options is a set of additional key/value pairs whose interpretation
// depends on the specified CA type. // depends on the specified CA type.
Options map[string]string `json:",omitempty"` Options map[string]string `json:",omitempty"`
// CACert specifies which root CA is used by this external CA. This certificate must
// be in PEM format.
CACert string
} }
// InitRequest is the request used to init a swarm. // InitRequest is the request used to init a swarm.
type InitRequest struct { type InitRequest struct {
ListenAddr string ListenAddr string
AdvertiseAddr string AdvertiseAddr string
DataPathAddr string
ForceNewCluster bool ForceNewCluster bool
Spec Spec Spec Spec
AutoLockManagers bool AutoLockManagers bool
@ -142,6 +147,7 @@ type InitRequest struct {
type JoinRequest struct { type JoinRequest struct {
ListenAddr string ListenAddr string
AdvertiseAddr string AdvertiseAddr string
DataPathAddr string
RemoteAddrs []string RemoteAddrs []string
JoinToken string // accept by secret JoinToken string // accept by secret
Availability NodeAvailability Availability NodeAvailability

View File

@ -238,6 +238,8 @@ type PluginsInfo struct {
Network []string Network []string
// List of Authorization plugins registered // List of Authorization plugins registered
Authorization []string Authorization []string
// List of Log plugins registered
Log []string
} }
// ExecStartCheck is a temp struct used by execStart // ExecStartCheck is a temp struct used by execStart
@ -528,3 +530,8 @@ type PushResult struct {
Digest string Digest string
Size int Size int
} }
// BuildResult contains the image id of a successful build
type BuildResult struct {
ID string
}

View File

@ -109,7 +109,7 @@ type JSONMessage struct {
TimeNano int64 `json:"timeNano,omitempty"` TimeNano int64 `json:"timeNano,omitempty"`
Error *JSONError `json:"errorDetail,omitempty"` Error *JSONError `json:"errorDetail,omitempty"`
ErrorMessage string `json:"error,omitempty"` //deprecated ErrorMessage string `json:"error,omitempty"` //deprecated
// Aux contains out-of-band data, such as digests for push signing. // Aux contains out-of-band data, such as digests for push signing and image id after building.
Aux *json.RawMessage `json:"aux,omitempty"` Aux *json.RawMessage `json:"aux,omitempty"`
} }

View File

@ -10,71 +10,45 @@ import (
"github.com/docker/docker/pkg/progress" "github.com/docker/docker/pkg/progress"
) )
// StreamFormatter formats a stream, optionally using JSON.
type StreamFormatter struct {
json bool
}
// NewStreamFormatter returns a simple StreamFormatter
func NewStreamFormatter() *StreamFormatter {
return &StreamFormatter{}
}
// NewJSONStreamFormatter returns a StreamFormatter configured to stream json
func NewJSONStreamFormatter() *StreamFormatter {
return &StreamFormatter{true}
}
const streamNewline = "\r\n" const streamNewline = "\r\n"
var streamNewlineBytes = []byte(streamNewline) type jsonProgressFormatter struct{}
// FormatStream formats the specified stream. func appendNewline(source []byte) []byte {
func (sf *StreamFormatter) FormatStream(str string) []byte { return append(source, []byte(streamNewline)...)
if sf.json {
b, err := json.Marshal(&jsonmessage.JSONMessage{Stream: str})
if err != nil {
return sf.FormatError(err)
}
return append(b, streamNewlineBytes...)
}
return []byte(str + "\r")
} }
// FormatStatus formats the specified objects according to the specified format (and id). // FormatStatus formats the specified objects according to the specified format (and id).
func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []byte { func FormatStatus(id, format string, a ...interface{}) []byte {
str := fmt.Sprintf(format, a...) str := fmt.Sprintf(format, a...)
if sf.json {
b, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Status: str}) b, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Status: str})
if err != nil { if err != nil {
return sf.FormatError(err) return FormatError(err)
} }
return append(b, streamNewlineBytes...) return appendNewline(b)
}
return []byte(str + streamNewline)
} }
// FormatError formats the specified error. // FormatError formats the error as a JSON object
func (sf *StreamFormatter) FormatError(err error) []byte { func FormatError(err error) []byte {
if sf.json {
jsonError, ok := err.(*jsonmessage.JSONError) jsonError, ok := err.(*jsonmessage.JSONError)
if !ok { if !ok {
jsonError = &jsonmessage.JSONError{Message: err.Error()} jsonError = &jsonmessage.JSONError{Message: err.Error()}
} }
if b, err := json.Marshal(&jsonmessage.JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil { if b, err := json.Marshal(&jsonmessage.JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil {
return append(b, streamNewlineBytes...) return appendNewline(b)
} }
return []byte("{\"error\":\"format error\"}" + streamNewline) return []byte(`{"error":"format error"}` + streamNewline)
}
return []byte("Error: " + err.Error() + streamNewline)
} }
// FormatProgress formats the progress information for a specified action. func (sf *jsonProgressFormatter) formatStatus(id, format string, a ...interface{}) []byte {
func (sf *StreamFormatter) FormatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte { return FormatStatus(id, format, a...)
}
// formatProgress formats the progress information for a specified action.
func (sf *jsonProgressFormatter) formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte {
if progress == nil { if progress == nil {
progress = &jsonmessage.JSONProgress{} progress = &jsonmessage.JSONProgress{}
} }
if sf.json {
var auxJSON *json.RawMessage var auxJSON *json.RawMessage
if aux != nil { if aux != nil {
auxJSONBytes, err := json.Marshal(aux) auxJSONBytes, err := json.Marshal(aux)
@ -94,7 +68,18 @@ func (sf *StreamFormatter) FormatProgress(id, action string, progress *jsonmessa
if err != nil { if err != nil {
return nil return nil
} }
return append(b, streamNewlineBytes...) return appendNewline(b)
}
type rawProgressFormatter struct{}
func (sf *rawProgressFormatter) formatStatus(id, format string, a ...interface{}) []byte {
return []byte(fmt.Sprintf(format, a...) + streamNewline)
}
func (sf *rawProgressFormatter) formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte {
if progress == nil {
progress = &jsonmessage.JSONProgress{}
} }
endl := "\r" endl := "\r"
if progress.String() == "" { if progress.String() == "" {
@ -105,16 +90,23 @@ func (sf *StreamFormatter) FormatProgress(id, action string, progress *jsonmessa
// NewProgressOutput returns a progress.Output object that can be passed to // NewProgressOutput returns a progress.Output object that can be passed to
// progress.NewProgressReader. // progress.NewProgressReader.
func (sf *StreamFormatter) NewProgressOutput(out io.Writer, newLines bool) progress.Output { func NewProgressOutput(out io.Writer) progress.Output {
return &progressOutput{ return &progressOutput{sf: &rawProgressFormatter{}, out: out, newLines: true}
sf: sf, }
out: out,
newLines: newLines, // NewJSONProgressOutput returns a progress.Output that that formats output
} // using JSON objects
func NewJSONProgressOutput(out io.Writer, newLines bool) progress.Output {
return &progressOutput{sf: &jsonProgressFormatter{}, out: out, newLines: newLines}
}
type formatProgress interface {
formatStatus(id, format string, a ...interface{}) []byte
formatProgress(id, action string, progress *jsonmessage.JSONProgress, aux interface{}) []byte
} }
type progressOutput struct { type progressOutput struct {
sf *StreamFormatter sf formatProgress
out io.Writer out io.Writer
newLines bool newLines bool
} }
@ -123,10 +115,10 @@ type progressOutput struct {
func (out *progressOutput) WriteProgress(prog progress.Progress) error { func (out *progressOutput) WriteProgress(prog progress.Progress) error {
var formatted []byte var formatted []byte
if prog.Message != "" { if prog.Message != "" {
formatted = out.sf.FormatStatus(prog.ID, prog.Message) formatted = out.sf.formatStatus(prog.ID, prog.Message)
} else { } else {
jsonProgress := jsonmessage.JSONProgress{Current: prog.Current, Total: prog.Total, HideCounts: prog.HideCounts} jsonProgress := jsonmessage.JSONProgress{Current: prog.Current, Total: prog.Total, HideCounts: prog.HideCounts}
formatted = out.sf.FormatProgress(prog.ID, prog.Action, &jsonProgress, prog.Aux) formatted = out.sf.formatProgress(prog.ID, prog.Action, &jsonProgress, prog.Aux)
} }
_, err := out.out.Write(formatted) _, err := out.out.Write(formatted)
if err != nil { if err != nil {
@ -134,39 +126,34 @@ func (out *progressOutput) WriteProgress(prog progress.Progress) error {
} }
if out.newLines && prog.LastUpdate { if out.newLines && prog.LastUpdate {
_, err = out.out.Write(out.sf.FormatStatus("", "")) _, err = out.out.Write(out.sf.formatStatus("", ""))
return err return err
} }
return nil return nil
} }
// StdoutFormatter is a streamFormatter that writes to the standard output. // AuxFormatter is a streamFormatter that writes aux progress messages
type StdoutFormatter struct { type AuxFormatter struct {
io.Writer io.Writer
*StreamFormatter
} }
func (sf *StdoutFormatter) Write(buf []byte) (int, error) { // Emit emits the given interface as an aux progress message
formattedBuf := sf.StreamFormatter.FormatStream(string(buf)) func (sf *AuxFormatter) Emit(aux interface{}) error {
n, err := sf.Writer.Write(formattedBuf) auxJSONBytes, err := json.Marshal(aux)
if n != len(formattedBuf) { if err != nil {
return n, io.ErrShortWrite return err
} }
return len(buf), err auxJSON := new(json.RawMessage)
} *auxJSON = auxJSONBytes
msgJSON, err := json.Marshal(&jsonmessage.JSONMessage{Aux: auxJSON})
// StderrFormatter is a streamFormatter that writes to the standard error. if err != nil {
type StderrFormatter struct { return err
io.Writer
*StreamFormatter
}
func (sf *StderrFormatter) Write(buf []byte) (int, error) {
formattedBuf := sf.StreamFormatter.FormatStream("\033[91m" + string(buf) + "\033[0m")
n, err := sf.Writer.Write(formattedBuf)
if n != len(formattedBuf) {
return n, io.ErrShortWrite
} }
return len(buf), err msgJSON = appendNewline(msgJSON)
n, err := sf.Writer.Write(msgJSON)
if n != len(msgJSON) {
return io.ErrShortWrite
}
return err
} }

View File

@ -0,0 +1,47 @@
package streamformatter
import (
"encoding/json"
"io"
"github.com/docker/docker/pkg/jsonmessage"
)
type streamWriter struct {
io.Writer
lineFormat func([]byte) string
}
func (sw *streamWriter) Write(buf []byte) (int, error) {
formattedBuf := sw.format(buf)
n, err := sw.Writer.Write(formattedBuf)
if n != len(formattedBuf) {
return n, io.ErrShortWrite
}
return len(buf), err
}
func (sw *streamWriter) format(buf []byte) []byte {
msg := &jsonmessage.JSONMessage{Stream: sw.lineFormat(buf)}
b, err := json.Marshal(msg)
if err != nil {
return FormatError(err)
}
return appendNewline(b)
}
// NewStdoutWriter returns a writer which formats the output as json message
// representing stdout lines
func NewStdoutWriter(out io.Writer) io.Writer {
return &streamWriter{Writer: out, lineFormat: func(buf []byte) string {
return string(buf)
}}
}
// NewStderrWriter returns a writer which formats the output as json message
// representing stderr lines
func NewStderrWriter(out io.Writer) io.Writer {
return &streamWriter{Writer: out, lineFormat: func(buf []byte) string {
return "\033[91m" + string(buf) + "\033[0m"
}}
}

View File

@ -1,307 +0,0 @@
package cmd
import (
"bytes"
"fmt"
"io"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
"github.com/docker/docker/pkg/system"
"github.com/go-check/check"
)
type testingT interface {
Fatalf(string, ...interface{})
}
const (
// None is a token to inform Result.Assert that the output should be empty
None string = "<NOTHING>"
)
type lockedBuffer struct {
m sync.RWMutex
buf bytes.Buffer
}
func (buf *lockedBuffer) Write(b []byte) (int, error) {
buf.m.Lock()
defer buf.m.Unlock()
return buf.buf.Write(b)
}
func (buf *lockedBuffer) String() string {
buf.m.RLock()
defer buf.m.RUnlock()
return buf.buf.String()
}
// Result stores the result of running a command
type Result struct {
Cmd *exec.Cmd
ExitCode int
Error error
// Timeout is true if the command was killed because it ran for too long
Timeout bool
outBuffer *lockedBuffer
errBuffer *lockedBuffer
}
// Assert compares the Result against the Expected struct, and fails the test if
// any of the expcetations are not met.
func (r *Result) Assert(t testingT, exp Expected) *Result {
err := r.Compare(exp)
if err == nil {
return r
}
_, file, line, ok := runtime.Caller(1)
if ok {
t.Fatalf("at %s:%d - %s", filepath.Base(file), line, err.Error())
} else {
t.Fatalf("(no file/line info) - %s", err.Error())
}
return nil
}
// Compare returns a formatted error with the command, stdout, stderr, exit
// code, and any failed expectations
func (r *Result) Compare(exp Expected) error {
errors := []string{}
add := func(format string, args ...interface{}) {
errors = append(errors, fmt.Sprintf(format, args...))
}
if exp.ExitCode != r.ExitCode {
add("ExitCode was %d expected %d", r.ExitCode, exp.ExitCode)
}
if exp.Timeout != r.Timeout {
if exp.Timeout {
add("Expected command to timeout")
} else {
add("Expected command to finish, but it hit the timeout")
}
}
if !matchOutput(exp.Out, r.Stdout()) {
add("Expected stdout to contain %q", exp.Out)
}
if !matchOutput(exp.Err, r.Stderr()) {
add("Expected stderr to contain %q", exp.Err)
}
switch {
// If a non-zero exit code is expected there is going to be an error.
// Don't require an error message as well as an exit code because the
// error message is going to be "exit status <code> which is not useful
case exp.Error == "" && exp.ExitCode != 0:
case exp.Error == "" && r.Error != nil:
add("Expected no error")
case exp.Error != "" && r.Error == nil:
add("Expected error to contain %q, but there was no error", exp.Error)
case exp.Error != "" && !strings.Contains(r.Error.Error(), exp.Error):
add("Expected error to contain %q", exp.Error)
}
if len(errors) == 0 {
return nil
}
return fmt.Errorf("%s\nFailures:\n%s\n", r, strings.Join(errors, "\n"))
}
func matchOutput(expected string, actual string) bool {
switch expected {
case None:
return actual == ""
default:
return strings.Contains(actual, expected)
}
}
func (r *Result) String() string {
var timeout string
if r.Timeout {
timeout = " (timeout)"
}
return fmt.Sprintf(`
Command: %s
ExitCode: %d%s
Error: %v
Stdout: %v
Stderr: %v
`,
strings.Join(r.Cmd.Args, " "),
r.ExitCode,
timeout,
r.Error,
r.Stdout(),
r.Stderr())
}
// Expected is the expected output from a Command. This struct is compared to a
// Result struct by Result.Assert().
type Expected struct {
ExitCode int
Timeout bool
Error string
Out string
Err string
}
// Success is the default expected result
var Success = Expected{}
// Stdout returns the stdout of the process as a string
func (r *Result) Stdout() string {
return r.outBuffer.String()
}
// Stderr returns the stderr of the process as a string
func (r *Result) Stderr() string {
return r.errBuffer.String()
}
// Combined returns the stdout and stderr combined into a single string
func (r *Result) Combined() string {
return r.outBuffer.String() + r.errBuffer.String()
}
// SetExitError sets Error and ExitCode based on Error
func (r *Result) SetExitError(err error) {
if err == nil {
return
}
r.Error = err
r.ExitCode = system.ProcessExitCode(err)
}
type matches struct{}
// Info returns the CheckerInfo
func (m *matches) Info() *check.CheckerInfo {
return &check.CheckerInfo{
Name: "CommandMatches",
Params: []string{"result", "expected"},
}
}
// Check compares a result against the expected
func (m *matches) Check(params []interface{}, names []string) (bool, string) {
result, ok := params[0].(*Result)
if !ok {
return false, fmt.Sprintf("result must be a *Result, not %T", params[0])
}
expected, ok := params[1].(Expected)
if !ok {
return false, fmt.Sprintf("expected must be an Expected, not %T", params[1])
}
err := result.Compare(expected)
if err == nil {
return true, ""
}
return false, err.Error()
}
// Matches is a gocheck.Checker for comparing a Result against an Expected
var Matches = &matches{}
// Cmd contains the arguments and options for a process to run as part of a test
// suite.
type Cmd struct {
Command []string
Timeout time.Duration
Stdin io.Reader
Stdout io.Writer
Dir string
Env []string
}
// Command create a simple Cmd with the specified command and arguments
func Command(command string, args ...string) Cmd {
return Cmd{Command: append([]string{command}, args...)}
}
// RunCmd runs a command and returns a Result
func RunCmd(cmd Cmd, cmdOperators ...func(*Cmd)) *Result {
for _, op := range cmdOperators {
op(&cmd)
}
result := StartCmd(cmd)
if result.Error != nil {
return result
}
return WaitOnCmd(cmd.Timeout, result)
}
// RunCommand parses a command line and runs it, returning a result
func RunCommand(command string, args ...string) *Result {
return RunCmd(Command(command, args...))
}
// StartCmd starts a command, but doesn't wait for it to finish
func StartCmd(cmd Cmd) *Result {
result := buildCmd(cmd)
if result.Error != nil {
return result
}
result.SetExitError(result.Cmd.Start())
return result
}
func buildCmd(cmd Cmd) *Result {
var execCmd *exec.Cmd
switch len(cmd.Command) {
case 1:
execCmd = exec.Command(cmd.Command[0])
default:
execCmd = exec.Command(cmd.Command[0], cmd.Command[1:]...)
}
outBuffer := new(lockedBuffer)
errBuffer := new(lockedBuffer)
execCmd.Stdin = cmd.Stdin
execCmd.Dir = cmd.Dir
execCmd.Env = cmd.Env
if cmd.Stdout != nil {
execCmd.Stdout = io.MultiWriter(outBuffer, cmd.Stdout)
} else {
execCmd.Stdout = outBuffer
}
execCmd.Stderr = errBuffer
return &Result{
Cmd: execCmd,
outBuffer: outBuffer,
errBuffer: errBuffer,
}
}
// WaitOnCmd waits for a command to complete. If timeout is non-nil then
// only wait until the timeout.
func WaitOnCmd(timeout time.Duration, result *Result) *Result {
if timeout == time.Duration(0) {
result.SetExitError(result.Cmd.Wait())
return result
}
done := make(chan error, 1)
// Wait for command to exit in a goroutine
go func() {
done <- result.Cmd.Wait()
}()
select {
case <-time.After(timeout):
killErr := result.Cmd.Process.Kill()
if killErr != nil {
fmt.Printf("failed to kill (pid=%d): %v\n", result.Cmd.Process.Pid, killErr)
}
result.Timeout = true
case err := <-done:
result.SetExitError(err)
}
return result
}

View File

@ -16,7 +16,6 @@ import (
"github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/system"
icmd "github.com/docker/docker/pkg/testutil/cmd"
) )
// IsKilled process the specified error and returns whether the process was killed or not. // IsKilled process the specified error and returns whether the process was killed or not.
@ -212,20 +211,6 @@ func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
} }
} }
// RunAtDifferentDate runs the specified function with the given time.
// It changes the date of the system, which can led to weird behaviors.
func RunAtDifferentDate(date time.Time, block func()) {
// Layout for date. MMDDhhmmYYYY
const timeLayout = "010203042006"
// Ensure we bring time back to now
now := time.Now().Format(timeLayout)
defer icmd.RunCommand("date", now)
icmd.RunCommand("date", date.Format(timeLayout))
block()
return
}
// ReadBody read the specified ReadCloser content and returns it // ReadBody read the specified ReadCloser content and returns it
func ReadBody(b io.ReadCloser) ([]byte, error) { func ReadBody(b io.ReadCloser) ([]byte, error) {
defer b.Close() defer b.Close()

View File

@ -1,8 +1,7 @@
# the following lines are in sorted order, FYI # the following lines are in sorted order, FYI
github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62 github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62
github.com/Microsoft/hcsshim v0.5.13 github.com/Microsoft/hcsshim v0.5.13
# TODO: get rid of this fork once PR https://github.com/Microsoft/go-winio/pull/43 is merged github.com/Microsoft/go-winio v0.3.9
github.com/Microsoft/go-winio 7c7d6b461cb10872c1138a0d7f3acf9a41b5c353 https://github.com/dgageot/go-winio.git
github.com/Sirupsen/logrus v0.11.0 github.com/Sirupsen/logrus v0.11.0
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76 github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
@ -23,14 +22,17 @@ github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987
github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5 github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5
github.com/imdario/mergo 0.2.1 github.com/imdario/mergo 0.2.1
golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0
#get libnetwork packages #get libnetwork packages
github.com/docker/libnetwork b13e0604016a4944025aaff521d9c125850b0d04 github.com/docker/libnetwork cace103704768d39bd88a23d0df76df125a0e39a
github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894 github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80 github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b github.com/hashicorp/go-msgpack 71c2886f5a673a35f909803f38ece5810165097b
github.com/hashicorp/memberlist 88ac4de0d1a0ca6def284b571342db3b777a4c37 github.com/hashicorp/memberlist v0.1.0
github.com/sean-/seed e2103e2c35297fb7e17febb81e49b312087a2372
github.com/hashicorp/go-sockaddr acd314c5781ea706c710d9ea70069fd2e110d61d
github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e github.com/hashicorp/go-multierror fcdddc395df1ddf4247c69bd436e84cfa0733f7e
github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870 github.com/hashicorp/serf 598c54895cc5a7b1a24a398d635e8c0ea0959870
github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef github.com/docker/libkv 1d8431073ae03cdaedb198a89722f3aab6d418ef
@ -39,7 +41,7 @@ github.com/vishvananda/netlink 1e86b2bee5b6a7d377e4c02bb7f98209d6a7297c
github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060 github.com/BurntSushi/toml f706d00e3de6abe700c994cdd545a1a4915af060
github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374 github.com/samuel/go-zookeeper d0e0d8e11f318e000a8cc434616d69e329edc374
github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d github.com/deckarep/golang-set ef32fa3046d9f249d399f98ebaf9be944430fd1d
github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20 github.com/coreos/etcd ea5389a79f40206170582c1ea076191b8622cb8e https://github.com/aaronlehmann/etcd # for https://github.com/coreos/etcd/pull/7830
github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065 github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065
github.com/hashicorp/consul v0.5.2 github.com/hashicorp/consul v0.5.2
github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904 github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904
@ -106,7 +108,7 @@ github.com/docker/containerd 9048e5e50717ea4497b757314bad98ea3763c145
github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4 github.com/tonistiigi/fifo 1405643975692217d6720f8b54aeee1bf2cd5cf4
# cluster # cluster
github.com/docker/swarmkit b19d028de0a6e9ca281afeb76cea2544b9edd839 github.com/docker/swarmkit 8f053c2030ebfc90f19f241fb7880e95b9761b7a
github.com/gogo/protobuf 8d70fb3182befc465c4a1eac8ad4d38ff49778e2 github.com/gogo/protobuf 8d70fb3182befc465c4a1eac8ad4d38ff49778e2
github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a github.com/cloudflare/cfssl 7fb22c8cba7ecaf98e4082d22d65800cf45e042a
github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e github.com/google/certificate-transparency d90e65c3a07988180c5b1ece71791c0b6506826e
@ -142,3 +144,4 @@ github.com/xeipuuv/gojsonpointer e0fe6f68307607d540ed8eac07a342c33fa1b54a
github.com/xeipuuv/gojsonreference e02fc20de94c78484cd5ffb007f8af96be030a45 github.com/xeipuuv/gojsonreference e02fc20de94c78484cd5ffb007f8af96be030a45
github.com/xeipuuv/gojsonschema 93e72a773fade158921402d6a24c819b48aba29d github.com/xeipuuv/gojsonschema 93e72a773fade158921402d6a24c819b48aba29d
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6 gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
github.com/opencontainers/selinux ba1aefe8057f1d0cfb8e88d0ec1dc85925ef987d

View File

@ -10,7 +10,7 @@ import (
mounttypes "github.com/docker/docker/api/types/mount" mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/stringid"
"github.com/opencontainers/runc/libcontainer/label" "github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -311,10 +311,12 @@ func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*Moun
} }
case mounttypes.TypeBind: case mounttypes.TypeBind:
mp.Source = clean(convertSlash(cfg.Source)) mp.Source = clean(convertSlash(cfg.Source))
if cfg.BindOptions != nil { if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 {
if len(cfg.BindOptions.Propagation) > 0 {
mp.Propagation = cfg.BindOptions.Propagation mp.Propagation = cfg.BindOptions.Propagation
} } else {
// If user did not specify a propagation mode, get
// default propagation mode.
mp.Propagation = DefaultPropagationMode
} }
case mounttypes.TypeTmpfs: case mounttypes.TypeTmpfs:
// NOP // NOP

View File

@ -1,25 +0,0 @@
Gocheck - A rich testing framework for Go
Copyright (c) 2010-2013 Gustavo Niemeyer <gustavo@niemeyer.net>
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.

View File

@ -1,10 +0,0 @@
Go-check
========
This is a fork of https://github.com/go-check/check
The intention of this fork is not to change any of the original behavior, but add
some specific behaviors needed for some of my projects already using this test suite.
For documentation on the main behavior of go-check see the aforementioned repo.
The original branch is intact at `orig_v1`

View File

@ -1,187 +0,0 @@
// Copyright (c) 2012 The Go 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.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// 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.
package check
import (
"fmt"
"runtime"
"time"
)
var memStats runtime.MemStats
// testingB is a type passed to Benchmark functions to manage benchmark
// timing and to specify the number of iterations to run.
type timer struct {
start time.Time // Time test or benchmark started
duration time.Duration
N int
bytes int64
timerOn bool
benchTime time.Duration
// The initial states of memStats.Mallocs and memStats.TotalAlloc.
startAllocs uint64
startBytes uint64
// The net total of this test after being run.
netAllocs uint64
netBytes uint64
}
// StartTimer starts timing a test. This function is called automatically
// before a benchmark starts, but it can also used to resume timing after
// a call to StopTimer.
func (c *C) StartTimer() {
if !c.timerOn {
c.start = time.Now()
c.timerOn = true
runtime.ReadMemStats(&memStats)
c.startAllocs = memStats.Mallocs
c.startBytes = memStats.TotalAlloc
}
}
// StopTimer stops timing a test. This can be used to pause the timer
// while performing complex initialization that you don't
// want to measure.
func (c *C) StopTimer() {
if c.timerOn {
c.duration += time.Now().Sub(c.start)
c.timerOn = false
runtime.ReadMemStats(&memStats)
c.netAllocs += memStats.Mallocs - c.startAllocs
c.netBytes += memStats.TotalAlloc - c.startBytes
}
}
// ResetTimer sets the elapsed benchmark time to zero.
// It does not affect whether the timer is running.
func (c *C) ResetTimer() {
if c.timerOn {
c.start = time.Now()
runtime.ReadMemStats(&memStats)
c.startAllocs = memStats.Mallocs
c.startBytes = memStats.TotalAlloc
}
c.duration = 0
c.netAllocs = 0
c.netBytes = 0
}
// SetBytes informs the number of bytes that the benchmark processes
// on each iteration. If this is called in a benchmark it will also
// report MB/s.
func (c *C) SetBytes(n int64) {
c.bytes = n
}
func (c *C) nsPerOp() int64 {
if c.N <= 0 {
return 0
}
return c.duration.Nanoseconds() / int64(c.N)
}
func (c *C) mbPerSec() float64 {
if c.bytes <= 0 || c.duration <= 0 || c.N <= 0 {
return 0
}
return (float64(c.bytes) * float64(c.N) / 1e6) / c.duration.Seconds()
}
func (c *C) timerString() string {
if c.N <= 0 {
return fmt.Sprintf("%3.3fs", float64(c.duration.Nanoseconds())/1e9)
}
mbs := c.mbPerSec()
mb := ""
if mbs != 0 {
mb = fmt.Sprintf("\t%7.2f MB/s", mbs)
}
nsop := c.nsPerOp()
ns := fmt.Sprintf("%10d ns/op", nsop)
if c.N > 0 && nsop < 100 {
// The format specifiers here make sure that
// the ones digits line up for all three possible formats.
if nsop < 10 {
ns = fmt.Sprintf("%13.2f ns/op", float64(c.duration.Nanoseconds())/float64(c.N))
} else {
ns = fmt.Sprintf("%12.1f ns/op", float64(c.duration.Nanoseconds())/float64(c.N))
}
}
memStats := ""
if c.benchMem {
allocedBytes := fmt.Sprintf("%8d B/op", int64(c.netBytes)/int64(c.N))
allocs := fmt.Sprintf("%8d allocs/op", int64(c.netAllocs)/int64(c.N))
memStats = fmt.Sprintf("\t%s\t%s", allocedBytes, allocs)
}
return fmt.Sprintf("%8d\t%s%s%s", c.N, ns, mb, memStats)
}
func min(x, y int) int {
if x > y {
return y
}
return x
}
func max(x, y int) int {
if x < y {
return y
}
return x
}
// roundDown10 rounds a number down to the nearest power of 10.
func roundDown10(n int) int {
var tens = 0
// tens = floor(log_10(n))
for n > 10 {
n = n / 10
tens++
}
// result = 10^tens
result := 1
for i := 0; i < tens; i++ {
result *= 10
}
return result
}
// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX].
func roundUp(n int) int {
base := roundDown10(n)
if n < (2 * base) {
return 2 * base
}
if n < (5 * base) {
return 5 * base
}
return 10 * base
}

View File

@ -1,939 +0,0 @@
// Package check is a rich testing extension for Go's testing package.
//
// For details about the project, see:
//
// http://labix.org/gocheck
//
package check
import (
"bytes"
"errors"
"fmt"
"io"
"math/rand"
"os"
"path"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
// -----------------------------------------------------------------------
// Internal type which deals with suite method calling.
const (
fixtureKd = iota
testKd
)
type funcKind int
const (
succeededSt = iota
failedSt
skippedSt
panickedSt
fixturePanickedSt
missedSt
)
type funcStatus uint32
// A method value can't reach its own Method structure.
type methodType struct {
reflect.Value
Info reflect.Method
}
func newMethod(receiver reflect.Value, i int) *methodType {
return &methodType{receiver.Method(i), receiver.Type().Method(i)}
}
func (method *methodType) PC() uintptr {
return method.Info.Func.Pointer()
}
func (method *methodType) suiteName() string {
t := method.Info.Type.In(0)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t.Name()
}
func (method *methodType) String() string {
return method.suiteName() + "." + method.Info.Name
}
func (method *methodType) matches(re *regexp.Regexp) bool {
return (re.MatchString(method.Info.Name) ||
re.MatchString(method.suiteName()) ||
re.MatchString(method.String()))
}
type C struct {
method *methodType
kind funcKind
testName string
_status funcStatus
logb *logger
logw io.Writer
done chan *C
reason string
mustFail bool
tempDir *tempDir
benchMem bool
startTime time.Time
timer
}
func (c *C) status() funcStatus {
return funcStatus(atomic.LoadUint32((*uint32)(&c._status)))
}
func (c *C) setStatus(s funcStatus) {
atomic.StoreUint32((*uint32)(&c._status), uint32(s))
}
func (c *C) stopNow() {
runtime.Goexit()
}
// logger is a concurrency safe byte.Buffer
type logger struct {
sync.Mutex
writer bytes.Buffer
}
func (l *logger) Write(buf []byte) (int, error) {
l.Lock()
defer l.Unlock()
return l.writer.Write(buf)
}
func (l *logger) WriteTo(w io.Writer) (int64, error) {
l.Lock()
defer l.Unlock()
return l.writer.WriteTo(w)
}
func (l *logger) String() string {
l.Lock()
defer l.Unlock()
return l.writer.String()
}
// -----------------------------------------------------------------------
// Handling of temporary files and directories.
type tempDir struct {
sync.Mutex
path string
counter int
}
func (td *tempDir) newPath() string {
td.Lock()
defer td.Unlock()
if td.path == "" {
var err error
for i := 0; i != 100; i++ {
path := fmt.Sprintf("%s%ccheck-%d", os.TempDir(), os.PathSeparator, rand.Int())
if err = os.Mkdir(path, 0700); err == nil {
td.path = path
break
}
}
if td.path == "" {
panic("Couldn't create temporary directory: " + err.Error())
}
}
result := filepath.Join(td.path, strconv.Itoa(td.counter))
td.counter += 1
return result
}
func (td *tempDir) removeAll() {
td.Lock()
defer td.Unlock()
if td.path != "" {
err := os.RemoveAll(td.path)
if err != nil {
fmt.Fprintf(os.Stderr, "WARNING: Error cleaning up temporaries: "+err.Error())
}
}
}
// Create a new temporary directory which is automatically removed after
// the suite finishes running.
func (c *C) MkDir() string {
path := c.tempDir.newPath()
if err := os.Mkdir(path, 0700); err != nil {
panic(fmt.Sprintf("Couldn't create temporary directory %s: %s", path, err.Error()))
}
return path
}
// -----------------------------------------------------------------------
// Low-level logging functions.
func (c *C) log(args ...interface{}) {
c.writeLog([]byte(fmt.Sprint(args...) + "\n"))
}
func (c *C) logf(format string, args ...interface{}) {
c.writeLog([]byte(fmt.Sprintf(format+"\n", args...)))
}
func (c *C) logNewLine() {
c.writeLog([]byte{'\n'})
}
func (c *C) writeLog(buf []byte) {
c.logb.Write(buf)
if c.logw != nil {
c.logw.Write(buf)
}
}
func hasStringOrError(x interface{}) (ok bool) {
_, ok = x.(fmt.Stringer)
if ok {
return
}
_, ok = x.(error)
return
}
func (c *C) logValue(label string, value interface{}) {
if label == "" {
if hasStringOrError(value) {
c.logf("... %#v (%q)", value, value)
} else {
c.logf("... %#v", value)
}
} else if value == nil {
c.logf("... %s = nil", label)
} else {
if hasStringOrError(value) {
fv := fmt.Sprintf("%#v", value)
qv := fmt.Sprintf("%q", value)
if fv != qv {
c.logf("... %s %s = %s (%s)", label, reflect.TypeOf(value), fv, qv)
return
}
}
if s, ok := value.(string); ok && isMultiLine(s) {
c.logf(`... %s %s = "" +`, label, reflect.TypeOf(value))
c.logMultiLine(s)
} else {
c.logf("... %s %s = %#v", label, reflect.TypeOf(value), value)
}
}
}
func (c *C) logMultiLine(s string) {
b := make([]byte, 0, len(s)*2)
i := 0
n := len(s)
for i < n {
j := i + 1
for j < n && s[j-1] != '\n' {
j++
}
b = append(b, "... "...)
b = strconv.AppendQuote(b, s[i:j])
if j < n {
b = append(b, " +"...)
}
b = append(b, '\n')
i = j
}
c.writeLog(b)
}
func isMultiLine(s string) bool {
for i := 0; i+1 < len(s); i++ {
if s[i] == '\n' {
return true
}
}
return false
}
func (c *C) logString(issue string) {
c.log("... ", issue)
}
func (c *C) logCaller(skip int) {
// This is a bit heavier than it ought to be.
skip += 1 // Our own frame.
pc, callerFile, callerLine, ok := runtime.Caller(skip)
if !ok {
return
}
var testFile string
var testLine int
testFunc := runtime.FuncForPC(c.method.PC())
if runtime.FuncForPC(pc) != testFunc {
for {
skip += 1
if pc, file, line, ok := runtime.Caller(skip); ok {
// Note that the test line may be different on
// distinct calls for the same test. Showing
// the "internal" line is helpful when debugging.
if runtime.FuncForPC(pc) == testFunc {
testFile, testLine = file, line
break
}
} else {
break
}
}
}
if testFile != "" && (testFile != callerFile || testLine != callerLine) {
c.logCode(testFile, testLine)
}
c.logCode(callerFile, callerLine)
}
func (c *C) logCode(path string, line int) {
c.logf("%s:%d:", nicePath(path), line)
code, err := printLine(path, line)
if code == "" {
code = "..." // XXX Open the file and take the raw line.
if err != nil {
code += err.Error()
}
}
c.log(indent(code, " "))
}
var valueGo = filepath.Join("reflect", "value.go")
var asmGo = filepath.Join("runtime", "asm_")
func (c *C) logPanic(skip int, value interface{}) {
skip++ // Our own frame.
initialSkip := skip
for ; ; skip++ {
if pc, file, line, ok := runtime.Caller(skip); ok {
if skip == initialSkip {
c.logf("... Panic: %s (PC=0x%X)\n", value, pc)
}
name := niceFuncName(pc)
path := nicePath(file)
if strings.Contains(path, "/gopkg.in/check.v") {
continue
}
if name == "Value.call" && strings.HasSuffix(path, valueGo) {
continue
}
if (name == "call16" || name == "call32") && strings.Contains(path, asmGo) {
continue
}
c.logf("%s:%d\n in %s", nicePath(file), line, name)
} else {
break
}
}
}
func (c *C) logSoftPanic(issue string) {
c.log("... Panic: ", issue)
}
func (c *C) logArgPanic(method *methodType, expectedType string) {
c.logf("... Panic: %s argument should be %s",
niceFuncName(method.PC()), expectedType)
}
// -----------------------------------------------------------------------
// Some simple formatting helpers.
var initWD, initWDErr = os.Getwd()
func init() {
if initWDErr == nil {
initWD = strings.Replace(initWD, "\\", "/", -1) + "/"
}
}
func nicePath(path string) string {
if initWDErr == nil {
if strings.HasPrefix(path, initWD) {
return path[len(initWD):]
}
}
return path
}
func niceFuncPath(pc uintptr) string {
function := runtime.FuncForPC(pc)
if function != nil {
filename, line := function.FileLine(pc)
return fmt.Sprintf("%s:%d", nicePath(filename), line)
}
return "<unknown path>"
}
func niceFuncName(pc uintptr) string {
function := runtime.FuncForPC(pc)
if function != nil {
name := path.Base(function.Name())
if i := strings.Index(name, "."); i > 0 {
name = name[i+1:]
}
if strings.HasPrefix(name, "(*") {
if i := strings.Index(name, ")"); i > 0 {
name = name[2:i] + name[i+1:]
}
}
if i := strings.LastIndex(name, ".*"); i != -1 {
name = name[:i] + "." + name[i+2:]
}
if i := strings.LastIndex(name, "·"); i != -1 {
name = name[:i] + "." + name[i+2:]
}
return name
}
return "<unknown function>"
}
// -----------------------------------------------------------------------
// Result tracker to aggregate call results.
type Result struct {
Succeeded int
Failed int
Skipped int
Panicked int
FixturePanicked int
ExpectedFailures int
Missed int // Not even tried to run, related to a panic in the fixture.
RunError error // Houston, we've got a problem.
WorkDir string // If KeepWorkDir is true
}
type resultTracker struct {
result Result
_lastWasProblem bool
_waiting int
_missed int
_expectChan chan *C
_doneChan chan *C
_stopChan chan bool
}
func newResultTracker() *resultTracker {
return &resultTracker{_expectChan: make(chan *C), // Synchronous
_doneChan: make(chan *C, 32), // Asynchronous
_stopChan: make(chan bool)} // Synchronous
}
func (tracker *resultTracker) start() {
go tracker._loopRoutine()
}
func (tracker *resultTracker) waitAndStop() {
<-tracker._stopChan
}
func (tracker *resultTracker) expectCall(c *C) {
tracker._expectChan <- c
}
func (tracker *resultTracker) callDone(c *C) {
tracker._doneChan <- c
}
func (tracker *resultTracker) _loopRoutine() {
for {
var c *C
if tracker._waiting > 0 {
// Calls still running. Can't stop.
select {
// XXX Reindent this (not now to make diff clear)
case c = <-tracker._expectChan:
tracker._waiting += 1
case c = <-tracker._doneChan:
tracker._waiting -= 1
switch c.status() {
case succeededSt:
if c.kind == testKd {
if c.mustFail {
tracker.result.ExpectedFailures++
} else {
tracker.result.Succeeded++
}
}
case failedSt:
tracker.result.Failed++
case panickedSt:
if c.kind == fixtureKd {
tracker.result.FixturePanicked++
} else {
tracker.result.Panicked++
}
case fixturePanickedSt:
// Track it as missed, since the panic
// was on the fixture, not on the test.
tracker.result.Missed++
case missedSt:
tracker.result.Missed++
case skippedSt:
if c.kind == testKd {
tracker.result.Skipped++
}
}
}
} else {
// No calls. Can stop, but no done calls here.
select {
case tracker._stopChan <- true:
return
case c = <-tracker._expectChan:
tracker._waiting += 1
case c = <-tracker._doneChan:
panic("Tracker got an unexpected done call.")
}
}
}
}
// -----------------------------------------------------------------------
// The underlying suite runner.
type suiteRunner struct {
suite interface{}
setUpSuite, tearDownSuite *methodType
setUpTest, tearDownTest *methodType
onTimeout *methodType
tests []*methodType
tracker *resultTracker
tempDir *tempDir
keepDir bool
output *outputWriter
reportedProblemLast bool
benchTime time.Duration
benchMem bool
checkTimeout time.Duration
}
type RunConf struct {
Output io.Writer
Stream bool
Verbose bool
Filter string
Benchmark bool
BenchmarkTime time.Duration // Defaults to 1 second
BenchmarkMem bool
KeepWorkDir bool
CheckTimeout time.Duration
}
// Create a new suiteRunner able to run all methods in the given suite.
func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner {
var conf RunConf
if runConf != nil {
conf = *runConf
}
if conf.Output == nil {
conf.Output = os.Stdout
}
if conf.Benchmark {
conf.Verbose = true
}
suiteType := reflect.TypeOf(suite)
suiteNumMethods := suiteType.NumMethod()
suiteValue := reflect.ValueOf(suite)
runner := &suiteRunner{
suite: suite,
output: newOutputWriter(conf.Output, conf.Stream, conf.Verbose),
tracker: newResultTracker(),
benchTime: conf.BenchmarkTime,
benchMem: conf.BenchmarkMem,
tempDir: &tempDir{},
keepDir: conf.KeepWorkDir,
tests: make([]*methodType, 0, suiteNumMethods),
checkTimeout: conf.CheckTimeout,
}
if runner.benchTime == 0 {
runner.benchTime = 1 * time.Second
}
var filterRegexp *regexp.Regexp
if conf.Filter != "" {
if regexp, err := regexp.Compile(conf.Filter); err != nil {
msg := "Bad filter expression: " + err.Error()
runner.tracker.result.RunError = errors.New(msg)
return runner
} else {
filterRegexp = regexp
}
}
for i := 0; i != suiteNumMethods; i++ {
method := newMethod(suiteValue, i)
switch method.Info.Name {
case "SetUpSuite":
runner.setUpSuite = method
case "TearDownSuite":
runner.tearDownSuite = method
case "SetUpTest":
runner.setUpTest = method
case "TearDownTest":
runner.tearDownTest = method
case "OnTimeout":
runner.onTimeout = method
default:
prefix := "Test"
if conf.Benchmark {
prefix = "Benchmark"
}
if !strings.HasPrefix(method.Info.Name, prefix) {
continue
}
if filterRegexp == nil || method.matches(filterRegexp) {
runner.tests = append(runner.tests, method)
}
}
}
return runner
}
// Run all methods in the given suite.
func (runner *suiteRunner) run() *Result {
if runner.tracker.result.RunError == nil && len(runner.tests) > 0 {
runner.tracker.start()
if runner.checkFixtureArgs() {
c := runner.runFixture(runner.setUpSuite, "", nil)
if c == nil || c.status() == succeededSt {
for i := 0; i != len(runner.tests); i++ {
c := runner.runTest(runner.tests[i])
if c.status() == fixturePanickedSt {
runner.skipTests(missedSt, runner.tests[i+1:])
break
}
}
} else if c != nil && c.status() == skippedSt {
runner.skipTests(skippedSt, runner.tests)
} else {
runner.skipTests(missedSt, runner.tests)
}
runner.runFixture(runner.tearDownSuite, "", nil)
} else {
runner.skipTests(missedSt, runner.tests)
}
runner.tracker.waitAndStop()
if runner.keepDir {
runner.tracker.result.WorkDir = runner.tempDir.path
} else {
runner.tempDir.removeAll()
}
}
return &runner.tracker.result
}
// Create a call object with the given suite method, and fork a
// goroutine with the provided dispatcher for running it.
func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
var logw io.Writer
if runner.output.Stream {
logw = runner.output
}
if logb == nil {
logb = new(logger)
}
c := &C{
method: method,
kind: kind,
testName: testName,
logb: logb,
logw: logw,
tempDir: runner.tempDir,
done: make(chan *C, 1),
timer: timer{benchTime: runner.benchTime},
startTime: time.Now(),
benchMem: runner.benchMem,
}
runner.tracker.expectCall(c)
go (func() {
runner.reportCallStarted(c)
defer runner.callDone(c)
dispatcher(c)
})()
return c
}
type timeoutErr struct {
method *methodType
t time.Duration
}
func (e timeoutErr) Error() string {
return fmt.Sprintf("%s test timed out after %v", e.method.String(), e.t)
}
func isTimeout(e error) bool {
if e == nil {
return false
}
_, ok := e.(timeoutErr)
return ok
}
// Same as forkCall(), but wait for call to finish before returning.
func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C {
var timeout <-chan time.Time
if runner.checkTimeout != 0 {
timeout = time.After(runner.checkTimeout)
}
c := runner.forkCall(method, kind, testName, logb, dispatcher)
select {
case <-c.done:
case <-timeout:
if runner.onTimeout != nil {
// run the OnTimeout callback, allowing the suite to collect any sort of debug information it can
// `runFixture` is syncronous, so run this in a separate goroutine with a timeout
cChan := make(chan *C)
go func() {
cChan <- runner.runFixture(runner.onTimeout, c.testName, c.logb)
}()
select {
case <-cChan:
case <-time.After(runner.checkTimeout):
}
}
panic(timeoutErr{method, runner.checkTimeout})
}
return c
}
// Handle a finished call. If there were any panics, update the call status
// accordingly. Then, mark the call as done and report to the tracker.
func (runner *suiteRunner) callDone(c *C) {
value := recover()
if value != nil {
switch v := value.(type) {
case *fixturePanic:
if v.status == skippedSt {
c.setStatus(skippedSt)
} else {
c.logSoftPanic("Fixture has panicked (see related PANIC)")
c.setStatus(fixturePanickedSt)
}
default:
c.logPanic(1, value)
c.setStatus(panickedSt)
}
}
if c.mustFail {
switch c.status() {
case failedSt:
c.setStatus(succeededSt)
case succeededSt:
c.setStatus(failedSt)
c.logString("Error: Test succeeded, but was expected to fail")
c.logString("Reason: " + c.reason)
}
}
runner.reportCallDone(c)
c.done <- c
}
// Runs a fixture call synchronously. The fixture will still be run in a
// goroutine like all suite methods, but this method will not return
// while the fixture goroutine is not done, because the fixture must be
// run in a desired order.
func (runner *suiteRunner) runFixture(method *methodType, testName string, logb *logger) *C {
if method != nil {
c := runner.runFunc(method, fixtureKd, testName, logb, func(c *C) {
c.ResetTimer()
c.StartTimer()
defer c.StopTimer()
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
})
return c
}
return nil
}
// Run the fixture method with runFixture(), but panic with a fixturePanic{}
// in case the fixture method panics. This makes it easier to track the
// fixture panic together with other call panics within forkTest().
func (runner *suiteRunner) runFixtureWithPanic(method *methodType, testName string, logb *logger, skipped *bool) *C {
if skipped != nil && *skipped {
return nil
}
c := runner.runFixture(method, testName, logb)
if c != nil && c.status() != succeededSt {
if skipped != nil {
*skipped = c.status() == skippedSt
}
panic(&fixturePanic{c.status(), method})
}
return c
}
type fixturePanic struct {
status funcStatus
method *methodType
}
// Run the suite test method, together with the test-specific fixture,
// asynchronously.
func (runner *suiteRunner) forkTest(method *methodType) *C {
testName := method.String()
return runner.forkCall(method, testKd, testName, nil, func(c *C) {
var skipped bool
defer runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, &skipped)
defer c.StopTimer()
benchN := 1
for {
runner.runFixtureWithPanic(runner.setUpTest, testName, c.logb, &skipped)
mt := c.method.Type()
if mt.NumIn() != 1 || mt.In(0) != reflect.TypeOf(c) {
// Rather than a plain panic, provide a more helpful message when
// the argument type is incorrect.
c.setStatus(panickedSt)
c.logArgPanic(c.method, "*check.C")
return
}
if strings.HasPrefix(c.method.Info.Name, "Test") {
c.ResetTimer()
c.StartTimer()
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
return
}
if !strings.HasPrefix(c.method.Info.Name, "Benchmark") {
panic("unexpected method prefix: " + c.method.Info.Name)
}
runtime.GC()
c.N = benchN
c.ResetTimer()
c.StartTimer()
c.method.Call([]reflect.Value{reflect.ValueOf(c)})
c.StopTimer()
if c.status() != succeededSt || c.duration >= c.benchTime || benchN >= 1e9 {
return
}
perOpN := int(1e9)
if c.nsPerOp() != 0 {
perOpN = int(c.benchTime.Nanoseconds() / c.nsPerOp())
}
// Logic taken from the stock testing package:
// - Run more iterations than we think we'll need for a second (1.5x).
// - Don't grow too fast in case we had timing errors previously.
// - Be sure to run at least one more than last time.
benchN = max(min(perOpN+perOpN/2, 100*benchN), benchN+1)
benchN = roundUp(benchN)
skipped = true // Don't run the deferred one if this panics.
runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, nil)
skipped = false
}
})
}
// Same as forkTest(), but wait for the test to finish before returning.
func (runner *suiteRunner) runTest(method *methodType) *C {
var timeout <-chan time.Time
if runner.checkTimeout != 0 {
timeout = time.After(runner.checkTimeout)
}
c := runner.forkTest(method)
select {
case <-c.done:
case <-timeout:
if runner.onTimeout != nil {
// run the OnTimeout callback, allowing the suite to collect any sort of debug information it can
// `runFixture` is syncronous, so run this in a separate goroutine with a timeout
cChan := make(chan *C)
go func() {
cChan <- runner.runFixture(runner.onTimeout, c.testName, c.logb)
}()
select {
case <-cChan:
case <-time.After(runner.checkTimeout):
}
}
panic(timeoutErr{method, runner.checkTimeout})
}
return c
}
// Helper to mark tests as skipped or missed. A bit heavy for what
// it does, but it enables homogeneous handling of tracking, including
// nice verbose output.
func (runner *suiteRunner) skipTests(status funcStatus, methods []*methodType) {
for _, method := range methods {
runner.runFunc(method, testKd, "", nil, func(c *C) {
c.setStatus(status)
})
}
}
// Verify if the fixture arguments are *check.C. In case of errors,
// log the error as a panic in the fixture method call, and return false.
func (runner *suiteRunner) checkFixtureArgs() bool {
succeeded := true
argType := reflect.TypeOf(&C{})
for _, method := range []*methodType{runner.setUpSuite, runner.tearDownSuite, runner.setUpTest, runner.tearDownTest, runner.onTimeout} {
if method != nil {
mt := method.Type()
if mt.NumIn() != 1 || mt.In(0) != argType {
succeeded = false
runner.runFunc(method, fixtureKd, "", nil, func(c *C) {
c.logArgPanic(method, "*check.C")
c.setStatus(panickedSt)
})
}
}
}
return succeeded
}
func (runner *suiteRunner) reportCallStarted(c *C) {
runner.output.WriteCallStarted("START", c)
}
func (runner *suiteRunner) reportCallDone(c *C) {
runner.tracker.callDone(c)
switch c.status() {
case succeededSt:
if c.mustFail {
runner.output.WriteCallSuccess("FAIL EXPECTED", c)
} else {
runner.output.WriteCallSuccess("PASS", c)
}
case skippedSt:
runner.output.WriteCallSuccess("SKIP", c)
case failedSt:
runner.output.WriteCallProblem("FAIL", c)
case panickedSt:
runner.output.WriteCallProblem("PANIC", c)
case fixturePanickedSt:
// That's a testKd call reporting that its fixture
// has panicked. The fixture call which caused the
// panic itself was tracked above. We'll report to
// aid debugging.
runner.output.WriteCallProblem("PANIC", c)
case missedSt:
runner.output.WriteCallSuccess("MISS", c)
}
}

View File

@ -1,458 +0,0 @@
package check
import (
"fmt"
"reflect"
"regexp"
)
// -----------------------------------------------------------------------
// CommentInterface and Commentf helper, to attach extra information to checks.
type comment struct {
format string
args []interface{}
}
// Commentf returns an infomational value to use with Assert or Check calls.
// If the checker test fails, the provided arguments will be passed to
// fmt.Sprintf, and will be presented next to the logged failure.
//
// For example:
//
// c.Assert(v, Equals, 42, Commentf("Iteration #%d failed.", i))
//
// Note that if the comment is constant, a better option is to
// simply use a normal comment right above or next to the line, as
// it will also get printed with any errors:
//
// c.Assert(l, Equals, 8192) // Ensure buffer size is correct (bug #123)
//
func Commentf(format string, args ...interface{}) CommentInterface {
return &comment{format, args}
}
// CommentInterface must be implemented by types that attach extra
// information to failed checks. See the Commentf function for details.
type CommentInterface interface {
CheckCommentString() string
}
func (c *comment) CheckCommentString() string {
return fmt.Sprintf(c.format, c.args...)
}
// -----------------------------------------------------------------------
// The Checker interface.
// The Checker interface must be provided by checkers used with
// the Assert and Check verification methods.
type Checker interface {
Info() *CheckerInfo
Check(params []interface{}, names []string) (result bool, error string)
}
// See the Checker interface.
type CheckerInfo struct {
Name string
Params []string
}
func (info *CheckerInfo) Info() *CheckerInfo {
return info
}
// -----------------------------------------------------------------------
// Not checker logic inverter.
// The Not checker inverts the logic of the provided checker. The
// resulting checker will succeed where the original one failed, and
// vice-versa.
//
// For example:
//
// c.Assert(a, Not(Equals), b)
//
func Not(checker Checker) Checker {
return &notChecker{checker}
}
type notChecker struct {
sub Checker
}
func (checker *notChecker) Info() *CheckerInfo {
info := *checker.sub.Info()
info.Name = "Not(" + info.Name + ")"
return &info
}
func (checker *notChecker) Check(params []interface{}, names []string) (result bool, error string) {
result, error = checker.sub.Check(params, names)
result = !result
return
}
// -----------------------------------------------------------------------
// IsNil checker.
type isNilChecker struct {
*CheckerInfo
}
// The IsNil checker tests whether the obtained value is nil.
//
// For example:
//
// c.Assert(err, IsNil)
//
var IsNil Checker = &isNilChecker{
&CheckerInfo{Name: "IsNil", Params: []string{"value"}},
}
func (checker *isNilChecker) Check(params []interface{}, names []string) (result bool, error string) {
return isNil(params[0]), ""
}
func isNil(obtained interface{}) (result bool) {
if obtained == nil {
result = true
} else {
switch v := reflect.ValueOf(obtained); v.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
return v.IsNil()
}
}
return
}
// -----------------------------------------------------------------------
// NotNil checker. Alias for Not(IsNil), since it's so common.
type notNilChecker struct {
*CheckerInfo
}
// The NotNil checker verifies that the obtained value is not nil.
//
// For example:
//
// c.Assert(iface, NotNil)
//
// This is an alias for Not(IsNil), made available since it's a
// fairly common check.
//
var NotNil Checker = &notNilChecker{
&CheckerInfo{Name: "NotNil", Params: []string{"value"}},
}
func (checker *notNilChecker) Check(params []interface{}, names []string) (result bool, error string) {
return !isNil(params[0]), ""
}
// -----------------------------------------------------------------------
// Equals checker.
type equalsChecker struct {
*CheckerInfo
}
// The Equals checker verifies that the obtained value is equal to
// the expected value, according to usual Go semantics for ==.
//
// For example:
//
// c.Assert(value, Equals, 42)
//
var Equals Checker = &equalsChecker{
&CheckerInfo{Name: "Equals", Params: []string{"obtained", "expected"}},
}
func (checker *equalsChecker) Check(params []interface{}, names []string) (result bool, error string) {
defer func() {
if v := recover(); v != nil {
result = false
error = fmt.Sprint(v)
}
}()
return params[0] == params[1], ""
}
// -----------------------------------------------------------------------
// DeepEquals checker.
type deepEqualsChecker struct {
*CheckerInfo
}
// The DeepEquals checker verifies that the obtained value is deep-equal to
// the expected value. The check will work correctly even when facing
// slices, interfaces, and values of different types (which always fail
// the test).
//
// For example:
//
// c.Assert(value, DeepEquals, 42)
// c.Assert(array, DeepEquals, []string{"hi", "there"})
//
var DeepEquals Checker = &deepEqualsChecker{
&CheckerInfo{Name: "DeepEquals", Params: []string{"obtained", "expected"}},
}
func (checker *deepEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) {
return reflect.DeepEqual(params[0], params[1]), ""
}
// -----------------------------------------------------------------------
// HasLen checker.
type hasLenChecker struct {
*CheckerInfo
}
// The HasLen checker verifies that the obtained value has the
// provided length. In many cases this is superior to using Equals
// in conjuction with the len function because in case the check
// fails the value itself will be printed, instead of its length,
// providing more details for figuring the problem.
//
// For example:
//
// c.Assert(list, HasLen, 5)
//
var HasLen Checker = &hasLenChecker{
&CheckerInfo{Name: "HasLen", Params: []string{"obtained", "n"}},
}
func (checker *hasLenChecker) Check(params []interface{}, names []string) (result bool, error string) {
n, ok := params[1].(int)
if !ok {
return false, "n must be an int"
}
value := reflect.ValueOf(params[0])
switch value.Kind() {
case reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.String:
default:
return false, "obtained value type has no length"
}
return value.Len() == n, ""
}
// -----------------------------------------------------------------------
// ErrorMatches checker.
type errorMatchesChecker struct {
*CheckerInfo
}
// The ErrorMatches checker verifies that the error value
// is non nil and matches the regular expression provided.
//
// For example:
//
// c.Assert(err, ErrorMatches, "perm.*denied")
//
var ErrorMatches Checker = errorMatchesChecker{
&CheckerInfo{Name: "ErrorMatches", Params: []string{"value", "regex"}},
}
func (checker errorMatchesChecker) Check(params []interface{}, names []string) (result bool, errStr string) {
if params[0] == nil {
return false, "Error value is nil"
}
err, ok := params[0].(error)
if !ok {
return false, "Value is not an error"
}
params[0] = err.Error()
names[0] = "error"
return matches(params[0], params[1])
}
// -----------------------------------------------------------------------
// Matches checker.
type matchesChecker struct {
*CheckerInfo
}
// The Matches checker verifies that the string provided as the obtained
// value (or the string resulting from obtained.String()) matches the
// regular expression provided.
//
// For example:
//
// c.Assert(err, Matches, "perm.*denied")
//
var Matches Checker = &matchesChecker{
&CheckerInfo{Name: "Matches", Params: []string{"value", "regex"}},
}
func (checker *matchesChecker) Check(params []interface{}, names []string) (result bool, error string) {
return matches(params[0], params[1])
}
func matches(value, regex interface{}) (result bool, error string) {
reStr, ok := regex.(string)
if !ok {
return false, "Regex must be a string"
}
valueStr, valueIsStr := value.(string)
if !valueIsStr {
if valueWithStr, valueHasStr := value.(fmt.Stringer); valueHasStr {
valueStr, valueIsStr = valueWithStr.String(), true
}
}
if valueIsStr {
matches, err := regexp.MatchString("^"+reStr+"$", valueStr)
if err != nil {
return false, "Can't compile regex: " + err.Error()
}
return matches, ""
}
return false, "Obtained value is not a string and has no .String()"
}
// -----------------------------------------------------------------------
// Panics checker.
type panicsChecker struct {
*CheckerInfo
}
// The Panics checker verifies that calling the provided zero-argument
// function will cause a panic which is deep-equal to the provided value.
//
// For example:
//
// c.Assert(func() { f(1, 2) }, Panics, &SomeErrorType{"BOOM"}).
//
//
var Panics Checker = &panicsChecker{
&CheckerInfo{Name: "Panics", Params: []string{"function", "expected"}},
}
func (checker *panicsChecker) Check(params []interface{}, names []string) (result bool, error string) {
f := reflect.ValueOf(params[0])
if f.Kind() != reflect.Func || f.Type().NumIn() != 0 {
return false, "Function must take zero arguments"
}
defer func() {
// If the function has not panicked, then don't do the check.
if error != "" {
return
}
params[0] = recover()
names[0] = "panic"
result = reflect.DeepEqual(params[0], params[1])
}()
f.Call(nil)
return false, "Function has not panicked"
}
type panicMatchesChecker struct {
*CheckerInfo
}
// The PanicMatches checker verifies that calling the provided zero-argument
// function will cause a panic with an error value matching
// the regular expression provided.
//
// For example:
//
// c.Assert(func() { f(1, 2) }, PanicMatches, `open.*: no such file or directory`).
//
//
var PanicMatches Checker = &panicMatchesChecker{
&CheckerInfo{Name: "PanicMatches", Params: []string{"function", "expected"}},
}
func (checker *panicMatchesChecker) Check(params []interface{}, names []string) (result bool, errmsg string) {
f := reflect.ValueOf(params[0])
if f.Kind() != reflect.Func || f.Type().NumIn() != 0 {
return false, "Function must take zero arguments"
}
defer func() {
// If the function has not panicked, then don't do the check.
if errmsg != "" {
return
}
obtained := recover()
names[0] = "panic"
if e, ok := obtained.(error); ok {
params[0] = e.Error()
} else if _, ok := obtained.(string); ok {
params[0] = obtained
} else {
errmsg = "Panic value is not a string or an error"
return
}
result, errmsg = matches(params[0], params[1])
}()
f.Call(nil)
return false, "Function has not panicked"
}
// -----------------------------------------------------------------------
// FitsTypeOf checker.
type fitsTypeChecker struct {
*CheckerInfo
}
// The FitsTypeOf checker verifies that the obtained value is
// assignable to a variable with the same type as the provided
// sample value.
//
// For example:
//
// c.Assert(value, FitsTypeOf, int64(0))
// c.Assert(value, FitsTypeOf, os.Error(nil))
//
var FitsTypeOf Checker = &fitsTypeChecker{
&CheckerInfo{Name: "FitsTypeOf", Params: []string{"obtained", "sample"}},
}
func (checker *fitsTypeChecker) Check(params []interface{}, names []string) (result bool, error string) {
obtained := reflect.ValueOf(params[0])
sample := reflect.ValueOf(params[1])
if !obtained.IsValid() {
return false, ""
}
if !sample.IsValid() {
return false, "Invalid sample value"
}
return obtained.Type().AssignableTo(sample.Type()), ""
}
// -----------------------------------------------------------------------
// Implements checker.
type implementsChecker struct {
*CheckerInfo
}
// The Implements checker verifies that the obtained value
// implements the interface specified via a pointer to an interface
// variable.
//
// For example:
//
// var e os.Error
// c.Assert(err, Implements, &e)
//
var Implements Checker = &implementsChecker{
&CheckerInfo{Name: "Implements", Params: []string{"obtained", "ifaceptr"}},
}
func (checker *implementsChecker) Check(params []interface{}, names []string) (result bool, error string) {
obtained := reflect.ValueOf(params[0])
ifaceptr := reflect.ValueOf(params[1])
if !obtained.IsValid() {
return false, ""
}
if !ifaceptr.IsValid() || ifaceptr.Kind() != reflect.Ptr || ifaceptr.Elem().Kind() != reflect.Interface {
return false, "ifaceptr should be a pointer to an interface variable"
}
return obtained.Type().Implements(ifaceptr.Elem().Type()), ""
}

View File

@ -1,231 +0,0 @@
package check
import (
"fmt"
"strings"
"time"
)
// TestName returns the current test name in the form "SuiteName.TestName"
func (c *C) TestName() string {
return c.testName
}
// -----------------------------------------------------------------------
// Basic succeeding/failing logic.
// Failed returns whether the currently running test has already failed.
func (c *C) Failed() bool {
return c.status() == failedSt
}
// Fail marks the currently running test as failed.
//
// Something ought to have been previously logged so the developer can tell
// what went wrong. The higher level helper functions will fail the test
// and do the logging properly.
func (c *C) Fail() {
c.setStatus(failedSt)
}
// FailNow marks the currently running test as failed and stops running it.
// Something ought to have been previously logged so the developer can tell
// what went wrong. The higher level helper functions will fail the test
// and do the logging properly.
func (c *C) FailNow() {
c.Fail()
c.stopNow()
}
// Succeed marks the currently running test as succeeded, undoing any
// previous failures.
func (c *C) Succeed() {
c.setStatus(succeededSt)
}
// SucceedNow marks the currently running test as succeeded, undoing any
// previous failures, and stops running the test.
func (c *C) SucceedNow() {
c.Succeed()
c.stopNow()
}
// ExpectFailure informs that the running test is knowingly broken for
// the provided reason. If the test does not fail, an error will be reported
// to raise attention to this fact. This method is useful to temporarily
// disable tests which cover well known problems until a better time to
// fix the problem is found, without forgetting about the fact that a
// failure still exists.
func (c *C) ExpectFailure(reason string) {
if reason == "" {
panic("Missing reason why the test is expected to fail")
}
c.mustFail = true
c.reason = reason
}
// Skip skips the running test for the provided reason. If run from within
// SetUpTest, the individual test being set up will be skipped, and if run
// from within SetUpSuite, the whole suite is skipped.
func (c *C) Skip(reason string) {
if reason == "" {
panic("Missing reason why the test is being skipped")
}
c.reason = reason
c.setStatus(skippedSt)
c.stopNow()
}
// -----------------------------------------------------------------------
// Basic logging.
// GetTestLog returns the current test error output.
func (c *C) GetTestLog() string {
return c.logb.String()
}
// Log logs some information into the test error output.
// The provided arguments are assembled together into a string with fmt.Sprint.
func (c *C) Log(args ...interface{}) {
c.log(args...)
}
// Log logs some information into the test error output.
// The provided arguments are assembled together into a string with fmt.Sprintf.
func (c *C) Logf(format string, args ...interface{}) {
c.logf(format, args...)
}
// Output enables *C to be used as a logger in functions that require only
// the minimum interface of *log.Logger.
func (c *C) Output(calldepth int, s string) error {
d := time.Now().Sub(c.startTime)
msec := d / time.Millisecond
sec := d / time.Second
min := d / time.Minute
c.Logf("[LOG] %d:%02d.%03d %s", min, sec%60, msec%1000, s)
return nil
}
// Error logs an error into the test error output and marks the test as failed.
// The provided arguments are assembled together into a string with fmt.Sprint.
func (c *C) Error(args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
c.logNewLine()
c.Fail()
}
// Errorf logs an error into the test error output and marks the test as failed.
// The provided arguments are assembled together into a string with fmt.Sprintf.
func (c *C) Errorf(format string, args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprintf("Error: "+format, args...))
c.logNewLine()
c.Fail()
}
// Fatal logs an error into the test error output, marks the test as failed, and
// stops the test execution. The provided arguments are assembled together into
// a string with fmt.Sprint.
func (c *C) Fatal(args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...)))
c.logNewLine()
c.FailNow()
}
// Fatlaf logs an error into the test error output, marks the test as failed, and
// stops the test execution. The provided arguments are assembled together into
// a string with fmt.Sprintf.
func (c *C) Fatalf(format string, args ...interface{}) {
c.logCaller(1)
c.logString(fmt.Sprint("Error: ", fmt.Sprintf(format, args...)))
c.logNewLine()
c.FailNow()
}
// -----------------------------------------------------------------------
// Generic checks and assertions based on checkers.
// Check verifies if the first value matches the expected value according
// to the provided checker. If they do not match, an error is logged, the
// test is marked as failed, and the test execution continues.
//
// Some checkers may not need the expected argument (e.g. IsNil).
//
// Extra arguments provided to the function are logged next to the reported
// problem when the matching fails.
func (c *C) Check(obtained interface{}, checker Checker, args ...interface{}) bool {
return c.internalCheck("Check", obtained, checker, args...)
}
// Assert ensures that the first value matches the expected value according
// to the provided checker. If they do not match, an error is logged, the
// test is marked as failed, and the test execution stops.
//
// Some checkers may not need the expected argument (e.g. IsNil).
//
// Extra arguments provided to the function are logged next to the reported
// problem when the matching fails.
func (c *C) Assert(obtained interface{}, checker Checker, args ...interface{}) {
if !c.internalCheck("Assert", obtained, checker, args...) {
c.stopNow()
}
}
func (c *C) internalCheck(funcName string, obtained interface{}, checker Checker, args ...interface{}) bool {
if checker == nil {
c.logCaller(2)
c.logString(fmt.Sprintf("%s(obtained, nil!?, ...):", funcName))
c.logString("Oops.. you've provided a nil checker!")
c.logNewLine()
c.Fail()
return false
}
// If the last argument is a bug info, extract it out.
var comment CommentInterface
if len(args) > 0 {
if c, ok := args[len(args)-1].(CommentInterface); ok {
comment = c
args = args[:len(args)-1]
}
}
params := append([]interface{}{obtained}, args...)
info := checker.Info()
if len(params) != len(info.Params) {
names := append([]string{info.Params[0], info.Name}, info.Params[1:]...)
c.logCaller(2)
c.logString(fmt.Sprintf("%s(%s):", funcName, strings.Join(names, ", ")))
c.logString(fmt.Sprintf("Wrong number of parameters for %s: want %d, got %d", info.Name, len(names), len(params)+1))
c.logNewLine()
c.Fail()
return false
}
// Copy since it may be mutated by Check.
names := append([]string{}, info.Params...)
// Do the actual check.
result, error := checker.Check(params, names)
if !result || error != "" {
c.logCaller(2)
for i := 0; i != len(params); i++ {
c.logValue(names[i], params[i])
}
if comment != nil {
c.logString(comment.CheckCommentString())
}
if error != "" {
c.logString(error)
}
c.logNewLine()
c.Fail()
return false
}
return true
}

Some files were not shown because too many files have changed in this diff Show More