mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
![Derek McGowan](/assets/img/avatar_default.png)
Add a trusted flag to force the cli to resolve a tag into a digest via the notary trust library and pull by digest. On push the flag the trust flag will indicate the digest and size of a manifest should be signed and push to a notary server. If a tag is given, the cli will resolve the tag into a digest and pull by digest. After pulling, if a tag is given the cli makes a request to tag the image. Use certificate directory for notary requests Read certificates using same logic used by daemon for registry requests. Catch JSON syntax errors from Notary client When an uncaught error occurs in Notary it may show up in Docker as a JSON syntax error, causing a confusing error message to the user. Provide a generic error when a JSON syntax error occurs. Catch expiration errors and wrap in additional context. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
257 lines
7.2 KiB
Go
257 lines
7.2 KiB
Go
package client
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"os"
|
|
"runtime"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
Cli "github.com/docker/docker/cli"
|
|
"github.com/docker/docker/opts"
|
|
"github.com/docker/docker/pkg/promise"
|
|
"github.com/docker/docker/pkg/signal"
|
|
"github.com/docker/docker/runconfig"
|
|
"github.com/docker/libnetwork/resolvconf/dns"
|
|
)
|
|
|
|
func (cid *cidFile) Close() error {
|
|
cid.file.Close()
|
|
|
|
if !cid.written {
|
|
if err := os.Remove(cid.path); err != nil {
|
|
return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (cid *cidFile) Write(id string) error {
|
|
if _, err := cid.file.Write([]byte(id)); err != nil {
|
|
return fmt.Errorf("Failed to write the container ID to the file: %s", err)
|
|
}
|
|
cid.written = true
|
|
return nil
|
|
}
|
|
|
|
// CmdRun runs a command in a new container.
|
|
//
|
|
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
|
func (cli *DockerCli) CmdRun(args ...string) error {
|
|
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, "Run a command in a new container", true)
|
|
addTrustedFlags(cmd, true)
|
|
|
|
// These are flags not stored in Config/HostConfig
|
|
var (
|
|
flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits")
|
|
flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID")
|
|
flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process")
|
|
flName = cmd.String([]string{"-name"}, "", "Assign a name to the container")
|
|
flAttach *opts.ListOpts
|
|
|
|
ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d")
|
|
ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm")
|
|
ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d")
|
|
)
|
|
|
|
config, hostConfig, cmd, err := runconfig.Parse(cmd, args)
|
|
// just in case the Parse does not exit
|
|
if err != nil {
|
|
cmd.ReportError(err.Error(), true)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if len(hostConfig.Dns) > 0 {
|
|
// check the DNS settings passed via --dns against
|
|
// localhost regexp to warn if they are trying to
|
|
// set a DNS to a localhost address
|
|
for _, dnsIP := range hostConfig.Dns {
|
|
if dns.IsLocalhost(dnsIP) {
|
|
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if config.Image == "" {
|
|
cmd.Usage()
|
|
return nil
|
|
}
|
|
|
|
if !*flDetach {
|
|
if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if fl := cmd.Lookup("-attach"); fl != nil {
|
|
flAttach = fl.Value.(*opts.ListOpts)
|
|
if flAttach.Len() != 0 {
|
|
return ErrConflictAttachDetach
|
|
}
|
|
}
|
|
if *flAutoRemove {
|
|
return ErrConflictDetachAutoRemove
|
|
}
|
|
|
|
config.AttachStdin = false
|
|
config.AttachStdout = false
|
|
config.AttachStderr = false
|
|
config.StdinOnce = false
|
|
}
|
|
|
|
// Disable flSigProxy when in TTY mode
|
|
sigProxy := *flSigProxy
|
|
if config.Tty {
|
|
sigProxy = false
|
|
}
|
|
|
|
// Telling the Windows daemon the initial size of the tty during start makes
|
|
// a far better user experience rather than relying on subsequent resizes
|
|
// to cause things to catch up.
|
|
if runtime.GOOS == "windows" {
|
|
hostConfig.ConsoleSize[0], hostConfig.ConsoleSize[1] = cli.getTtySize()
|
|
}
|
|
|
|
createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if sigProxy {
|
|
sigc := cli.forwardAllSignals(createResponse.ID)
|
|
defer signal.StopCatch(sigc)
|
|
}
|
|
var (
|
|
waitDisplayID chan struct{}
|
|
errCh chan error
|
|
)
|
|
if !config.AttachStdout && !config.AttachStderr {
|
|
// Make this asynchronous to allow the client to write to stdin before having to read the ID
|
|
waitDisplayID = make(chan struct{})
|
|
go func() {
|
|
defer close(waitDisplayID)
|
|
fmt.Fprintf(cli.out, "%s\n", createResponse.ID)
|
|
}()
|
|
}
|
|
if *flAutoRemove && (hostConfig.RestartPolicy.IsAlways() || hostConfig.RestartPolicy.IsOnFailure()) {
|
|
return ErrConflictRestartPolicyAndAutoRemove
|
|
}
|
|
// We need to instantiate the chan because the select needs it. It can
|
|
// be closed but can't be uninitialized.
|
|
hijacked := make(chan io.Closer)
|
|
// Block the return until the chan gets closed
|
|
defer func() {
|
|
logrus.Debugf("End of CmdRun(), Waiting for hijack to finish.")
|
|
if _, ok := <-hijacked; ok {
|
|
fmt.Fprintln(cli.err, "Hijack did not finish (chan still open)")
|
|
}
|
|
}()
|
|
if config.AttachStdin || config.AttachStdout || config.AttachStderr {
|
|
var (
|
|
out, stderr io.Writer
|
|
in io.ReadCloser
|
|
v = url.Values{}
|
|
)
|
|
v.Set("stream", "1")
|
|
if config.AttachStdin {
|
|
v.Set("stdin", "1")
|
|
in = cli.in
|
|
}
|
|
if config.AttachStdout {
|
|
v.Set("stdout", "1")
|
|
out = cli.out
|
|
}
|
|
if config.AttachStderr {
|
|
v.Set("stderr", "1")
|
|
if config.Tty {
|
|
stderr = cli.out
|
|
} else {
|
|
stderr = cli.err
|
|
}
|
|
}
|
|
errCh = promise.Go(func() error {
|
|
return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil)
|
|
})
|
|
} else {
|
|
close(hijacked)
|
|
}
|
|
// Acknowledge the hijack before starting
|
|
select {
|
|
case closer := <-hijacked:
|
|
// Make sure that the hijack gets closed when returning (results
|
|
// in closing the hijack chan and freeing server's goroutines)
|
|
if closer != nil {
|
|
defer closer.Close()
|
|
}
|
|
case err := <-errCh:
|
|
if err != nil {
|
|
logrus.Debugf("Error hijack: %s", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
defer func() {
|
|
if *flAutoRemove {
|
|
if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil {
|
|
fmt.Fprintf(cli.err, "Error deleting container: %s\n", err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
//start the container
|
|
if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil {
|
|
return err
|
|
}
|
|
|
|
if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut {
|
|
if err := cli.monitorTtySize(createResponse.ID, false); err != nil {
|
|
fmt.Fprintf(cli.err, "Error monitoring TTY size: %s\n", err)
|
|
}
|
|
}
|
|
|
|
if errCh != nil {
|
|
if err := <-errCh; err != nil {
|
|
logrus.Debugf("Error hijack: %s", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Detached mode: wait for the id to be displayed and return.
|
|
if !config.AttachStdout && !config.AttachStderr {
|
|
// Detached mode
|
|
<-waitDisplayID
|
|
return nil
|
|
}
|
|
|
|
var status int
|
|
|
|
// Attached mode
|
|
if *flAutoRemove {
|
|
// Autoremove: wait for the container to finish, retrieve
|
|
// the exit code and remove the container
|
|
if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil {
|
|
return err
|
|
}
|
|
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// No Autoremove: Simply retrieve the exit code
|
|
if !config.Tty {
|
|
// In non-TTY mode, we can't detach, so we must wait for container exit
|
|
if status, err = waitForExit(cli, createResponse.ID); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// In TTY mode, there is a race: if the process dies too slowly, the state could
|
|
// be updated after the getExitCode call and result in the wrong exit code being reported
|
|
if _, status, err = getExitCode(cli, createResponse.ID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
if status != 0 {
|
|
return Cli.StatusError{StatusCode: status}
|
|
}
|
|
return nil
|
|
}
|