mirror of https://github.com/docker/cli.git
Merge pull request #11 from tiborvass/update-cli
Update cli folder with newer changes from moby/moby
This commit is contained in:
commit
a6feb55a48
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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.
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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"))
|
||||||
|
}
|
|
@ -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 != "" {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
1234567890123456789
|
|
@ -0,0 +1 @@
|
||||||
|
tag
|
|
@ -0,0 +1,2 @@
|
||||||
|
IMAGE CREATED CREATED BY SIZE COMMENT
|
||||||
|
123456789012 Less than a second ago 0B
|
|
@ -0,0 +1 @@
|
||||||
|
file input test
|
|
@ -0,0 +1 @@
|
||||||
|
'image'
|
|
@ -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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -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": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1 @@
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
@ -0,0 +1 @@
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
@ -0,0 +1 @@
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
|
@ -0,0 +1 @@
|
||||||
|
Success
|
|
@ -0,0 +1 @@
|
||||||
|
file input test
|
|
@ -0,0 +1 @@
|
||||||
|
1:
|
|
@ -0,0 +1 @@
|
||||||
|
Success
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
||||||
|
Deleted Images:
|
||||||
|
deleted: image1
|
||||||
|
|
||||||
|
Total reclaimed space: 1B
|
|
@ -0,0 +1,4 @@
|
||||||
|
Deleted Images:
|
||||||
|
untagged: image1
|
||||||
|
|
||||||
|
Total reclaimed space: 2B
|
|
@ -0,0 +1 @@
|
||||||
|
Using default tag: latest
|
4
cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden
vendored
Normal file
4
cli/command/image/testdata/remove-command-success.Image Deleted and Untagged.golden
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
Untagged: image1
|
||||||
|
Deleted: image2
|
||||||
|
Untagged: image1
|
||||||
|
Deleted: image2
|
|
@ -0,0 +1,2 @@
|
||||||
|
Deleted: image1
|
||||||
|
Deleted: image1
|
|
@ -0,0 +1,2 @@
|
||||||
|
Untagged: image1
|
||||||
|
Untagged: image1
|
|
@ -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)
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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"})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 it’s 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 Relay Chat (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.
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"`
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
47
vendor/github.com/docker/docker/pkg/streamformatter/streamwriter.go
generated
vendored
Normal file
47
vendor/github.com/docker/docker/pkg/streamformatter/streamwriter.go
generated
vendored
Normal 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"
|
||||||
|
}}
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
|
|
@ -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`
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 ¬Checker{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 = ¬NilChecker{
|
|
||||||
&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()), ""
|
|
||||||
}
|
|
|
@ -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
Loading…
Reference in New Issue