// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config // is set the client scheme will be set to https. // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli { //创建cli对象 cli := &DockerCli{ in: in, out: out, err: err, keyFile: clientFlags.Common.TrustKey, } //docker客户端模式的创建过程,如果需要安全认证,需要加载安全认证的证书。 cli.init = func() error { clientFlags.PostParse() configFile, e := cliconfig.Load(cliconfig.ConfigDir()) if e != nil { fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e) } if !configFile.ContainsAuth() { credentials.DetectDefaultStore(configFile) } cli.configFile = configFile host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions) if err != nil { return err } customHeaders := cli.configFile.HTTPHeaders if customHeaders == nil { customHeaders = map[string]string{} } customHeaders["User-Agent"] = clientUserAgent() verStr := api.DefaultVersion.String() if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" { verStr = tmpStr } httpClient, err := newHTTPClient(host, clientFlags.Common.TLSOptions) if err != nil { return err } client, err := client.NewClient(host, verStr, httpClient, customHeaders) if err != nil { return err } cli.client = client if cli.in != nil { cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) } if cli.out != nil { cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) } return nil } return cli }
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config // is set the client scheme will be set to https. // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cliflags.ClientFlags) *DockerCli { cli := &DockerCli{ in: in, out: out, err: err, keyFile: clientFlags.Common.TrustKey, } cli.init = func() error { clientFlags.PostParse() cli.configFile = LoadDefaultConfigFile(err) client, err := NewAPIClientFromFlags(clientFlags, cli.configFile) if err != nil { return err } cli.client = client if cli.in != nil { cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) } if cli.out != nil { cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) } return nil } return cli }
// Initialize the dockerCli runs initialization that must happen after command // line flags are parsed. func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error { cli.configFile = LoadDefaultConfigFile(cli.err) client, err := NewAPIClientFromFlags(opts.Common, cli.configFile) if err != nil { return err } cli.client = client if cli.in != nil { cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) } if cli.out != nil { cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) } if opts.Common.TrustKey == "" { cli.keyFile = filepath.Join(cliconfig.ConfigDir(), cliflags.DefaultTrustKeyFile) } else { cli.keyFile = opts.Common.TrustKey } return nil }
func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli { var ( inFd uintptr outFd uintptr isTerminalIn = false isTerminalOut = false scheme = "http" ) if tlsConfig != nil { scheme = "https" } if in != nil { inFd, isTerminalIn = term.GetFdInfo(in) } if out != nil { outFd, isTerminalOut = term.GetFdInfo(out) } if err == nil { err = out } // The transport is created here for reuse during the client session tr := &http.Transport{ TLSClientConfig: tlsConfig, } // Why 32? See issue 8035 timeout := 32 * time.Second if proto == "unix" { // no need in compressing for local communications tr.DisableCompression = true tr.Dial = func(_, _ string) (net.Conn, error) { return net.DialTimeout(proto, addr, timeout) } } else { tr.Proxy = http.ProxyFromEnvironment tr.Dial = (&net.Dialer{Timeout: timeout}).Dial } return &DockerCli{ proto: proto, addr: addr, in: in, out: out, err: err, keyFile: keyFile, inFd: inFd, outFd: outFd, isTerminalIn: isTerminalIn, isTerminalOut: isTerminalOut, tlsConfig: tlsConfig, scheme: scheme, transport: tr, } }
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config // is set the client scheme will be set to https. // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientFlags) *DockerCli { cli := &DockerCli{ in: in, out: out, err: err, keyFile: clientFlags.Common.TrustKey, } cli.init = func() error { clientFlags.PostParse() configFile, e := cliconfig.Load(cliconfig.ConfigDir()) if e != nil { fmt.Fprintf(cli.err, "WARNING: Error loading config file:%v\n", e) } cli.configFile = configFile host, err := getServerHost(clientFlags.Common.Hosts, clientFlags.Common.TLSOptions) if err != nil { return err } customHeaders := cli.configFile.HTTPHeaders if customHeaders == nil { customHeaders = map[string]string{} } customHeaders["User-Agent"] = "Docker-Client/" + dockerversion.Version + " (" + runtime.GOOS + ")" verStr := api.DefaultVersion.String() if tmpStr := os.Getenv("DOCKER_API_VERSION"); tmpStr != "" { verStr = tmpStr } clientTransport, err := newClientTransport(clientFlags.Common.TLSOptions) if err != nil { return err } client, err := client.NewClient(host, verStr, clientTransport, customHeaders) if err != nil { return err } cli.client = client if cli.in != nil { cli.inFd, cli.isTerminalIn = term.GetFdInfo(cli.in) } if cli.out != nil { cli.outFd, cli.isTerminalOut = term.GetFdInfo(cli.out) } return nil } return cli }
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. // The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config // is set the client scheme will be set to https. // The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli { var ( inFd uintptr outFd uintptr isTerminalIn = false isTerminalOut = false scheme = "http" ) if tlsConfig != nil { scheme = "https" } if in != nil { inFd, isTerminalIn = term.GetFdInfo(in) } if out != nil { outFd, isTerminalOut = term.GetFdInfo(out) } if err == nil { err = out } // The transport is created here for reuse during the client session. tr := &http.Transport{ TLSClientConfig: tlsConfig, } utils.ConfigureTCPTransport(tr, proto, addr) configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker")) if e != nil { fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) } return &DockerCli{ proto: proto, addr: addr, configFile: configFile, in: in, out: out, err: err, keyFile: keyFile, inFd: inFd, outFd: outFd, isTerminalIn: isTerminalIn, isTerminalOut: isTerminalOut, tlsConfig: tlsConfig, scheme: scheme, transport: tr, } }
// Connect func Connect(in io.Reader, out io.Writer) (*term.State, error) { stdInFD, _ := term.GetFdInfo(in) stdOutFD, _ := term.GetFdInfo(out) // handle all incoming os signals and act accordingly; default behavior is to // forward all signals to nanobox server go monitor(stdOutFD) // try to upgrade to a raw terminal; if accessed via a terminal this will upgrade // with no error, if not an error will be returned return term.SetRawTerminal(stdInFD) }
func execInternal(where, params string, in io.Reader, out io.Writer) error { // if we can't connect to the server, lets bail out early conn, err := net.Dial("tcp4", config.ServerURI) if err != nil { return err } defer conn.Close() // get current term info stdInFD, isTerminal := term.GetFdInfo(in) stdOutFD, _ := term.GetFdInfo(out) // terminal.PrintNanoboxHeader(where) // begin watching for changes to the project go func() { if err := notifyutil.Watch(config.CWDir, NotifyServer); err != nil { fmt.Printf(err.Error()) } }() // if we are using a term, lets upgrade it to RawMode if isTerminal { // handle all incoming os signals and act accordingly; default behavior is to // forward all signals to nanobox server go monitorTerminal(stdOutFD) oldState, err := term.SetRawTerminal(stdInFD) // we only use raw mode if it is available. if err == nil { defer term.RestoreTerminal(stdInFD, oldState) } } // make a http request switch where { case "develop": if _, err := fmt.Fprintf(conn, "POST /develop?pid=%d&%v HTTP/1.1\r\n\r\n", os.Getpid(), params); err != nil { return err } default: if _, err := fmt.Fprintf(conn, "POST /exec?pid=%d&%v HTTP/1.1\r\n\r\n", os.Getpid(), params); err != nil { return err } } return pipeToConnection(conn, in, out) }
// PullImage pulls docker image func (c *DockerClient) PullImage(name string) error { var ( image = imagename.NewFromString(name) pipeReader, pipeWriter = io.Pipe() fdOut, isTerminalOut = term.GetFdInfo(c.log.Out) out = c.log.Out errch = make(chan error, 1) ) if !isTerminalOut { out = c.log.Writer() } opts := docker.PullImageOptions{ Repository: image.NameWithRegistry(), Registry: image.Registry, Tag: image.GetTag(), OutputStream: pipeWriter, RawJSONStream: true, } c.log.Infof("| Pull image %s", image) c.log.Debugf("Pull image %s with options: %# v", image, opts) go func() { errch <- jsonmessage.DisplayJSONMessagesStream(pipeReader, out, fdOut, isTerminalOut) }() if err := c.client.PullImage(opts, c.auth); err != nil { return err } return <-errch }
// Safe invokes the provided function and will attempt to ensure that when the // function returns (or a termination signal is sent) that the terminal state // is reset to the condition it was in prior to the function being invoked. If // t.Raw is true the terminal will be put into raw mode prior to calling the function. // If the input file descriptor is not a TTY and TryDev is true, the /dev/tty file // will be opened (if available). func (t TTY) Safe(fn SafeFunc) error { inFd, isTerminal := term.GetFdInfo(t.In) if !isTerminal && t.TryDev { if f, err := os.Open("/dev/tty"); err == nil { defer f.Close() inFd = f.Fd() isTerminal = term.IsTerminal(inFd) } } if !isTerminal { return fn() } var state *term.State var err error if t.Raw { state, err = term.MakeRaw(inFd) } else { state, err = term.SaveState(inFd) } if err != nil { return err } return interrupt.Chain(t.Parent, func() { if t.sizeQueue != nil { t.sizeQueue.stop() } term.RestoreTerminal(inFd, state) }).Run(fn) }
// GetSize returns the current size of the user's terminal. If it isn't a terminal, // nil is returned. func (t TTY) GetSize() *Size { outFd, isTerminal := term.GetFdInfo(t.Out) if !isTerminal { return nil } return GetSize(outFd) }
// DecodeJSONMessageStream wraps an io.Writer to decode a jsonmessage stream into // plain text. Bytes written to w represent the decoded plain text stream. func DecodeJSONMessageStream(w io.Writer) *DecodedJSONMessageWriter { outFd, _ := term.GetFdInfo(w) return &DecodedJSONMessageWriter{ w: w, fd: outFd, } }
func runDeploy(cmd *Command, args []string) { r, w := io.Pipe() if len(args) < 1 { printFatal("You must specify an image to deploy") } image := args[0] message := getMessage() form := &PostDeployForm{Image: image} var endpoint string appName, _ := app() if appName != "" { endpoint = fmt.Sprintf("/apps/%s/deploys", appName) } else { endpoint = "/deploys" } rh := heroku.RequestHeaders{CommitMessage: message} go func() { must(client.PostWithHeaders(w, endpoint, form, rh.Headers())) must(w.Close()) }() outFd, isTerminalOut := term.GetFdInfo(os.Stdout) must(jsonmessage.DisplayJSONMessagesStream(r, os.Stdout, outFd, isTerminalOut)) }
// Run creates, start and attach to the container based on the image name, // the specified configuration. // It will always create a new container. func (c *Container) Run(ctx context.Context, configOverride *config.ServiceConfig) (int, error) { var ( errCh chan error out, stderr io.Writer in io.ReadCloser ) if configOverride.StdinOpen { in = os.Stdin } if configOverride.Tty { out = os.Stdout } if configOverride.Tty { stderr = os.Stderr } options := types.ContainerAttachOptions{ Stream: true, Stdin: configOverride.StdinOpen, Stdout: configOverride.Tty, Stderr: configOverride.Tty, } resp, err := c.client.ContainerAttach(ctx, c.container.ID, options) if err != nil { return -1, err } // set raw terminal inFd, _ := term.GetFdInfo(in) state, err := term.SetRawTerminal(inFd) if err != nil { return -1, err } // restore raw terminal defer term.RestoreTerminal(inFd, state) // holdHijackedConnection (in goroutine) errCh = promise.Go(func() error { return holdHijackedConnection(configOverride.Tty, in, out, stderr, resp) }) if err := c.client.ContainerStart(ctx, c.container.ID, types.ContainerStartOptions{}); err != nil { return -1, err } if err := <-errCh; err != nil { logrus.Debugf("Error hijack: %s", err) return -1, err } exitedContainer, err := c.client.ContainerInspect(ctx, c.container.ID) if err != nil { return -1, err } return exitedContainer.State.ExitCode, nil }
func pullImage(client client.APIClient, service *Service, image string) error { distributionRef, err := reference.ParseNamed(image) if err != nil { return err } switch distributionRef.(type) { case reference.Canonical: case reference.NamedTagged: default: distributionRef, err = reference.WithTag(distributionRef, "latest") if err != nil { return err } } repoInfo, err := registry.ParseRepositoryInfo(distributionRef) if err != nil { return err } authConfig := types.AuthConfig{} if service.context.ConfigFile != nil && repoInfo != nil && repoInfo.Index != nil { authConfig = registry.ResolveAuthConfig(service.context.ConfigFile.AuthConfigs, repoInfo.Index) } encodedAuth, err := encodeAuthToBase64(authConfig) if err != nil { return err } options := types.ImagePullOptions{ RegistryAuth: encodedAuth, } responseBody, err := client.ImagePull(context.Background(), distributionRef.String(), options) if err != nil { logrus.Errorf("Failed to pull image %s: %v", image, err) return err } defer responseBody.Close() var writeBuff io.Writer = os.Stdout outFd, isTerminalOut := term.GetFdInfo(os.Stdout) err = jsonmessage.DisplayJSONMessagesStream(responseBody, writeBuff, outFd, isTerminalOut, nil) if err != nil { if jerr, ok := err.(*jsonmessage.JSONError); ok { // If no error code is set, default to 1 if jerr.Code == 0 { jerr.Code = 1 } fmt.Fprintf(os.Stderr, "%s", writeBuff) return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code) } } return err }
// PullDockerImage pulls an image and streams to a logger respecting terminal features func PullDockerImage(client *docker.Client, image *imagename.ImageName, auth *docker.AuthConfigurations) (*docker.Image, error) { if image.Storage == imagename.StorageS3 { s3storage := s3.New(client, os.TempDir()) if err := s3storage.Pull(image.String()); err != nil { return nil, err } } else { pipeReader, pipeWriter := io.Pipe() pullOpts := docker.PullImageOptions{ Repository: image.NameWithRegistry(), Registry: image.Registry, Tag: image.Tag, OutputStream: pipeWriter, RawJSONStream: true, } repoAuth, err := dockerclient.GetAuthForRegistry(auth, image) if err != nil { return nil, fmt.Errorf("Failed to authenticate registry %s, error: %s", image.Registry, err) } errch := make(chan error, 1) go func() { err := client.PullImage(pullOpts, repoAuth) if err := pipeWriter.Close(); err != nil { log.Errorf("Failed to close pull image stream for %s, error: %s", image, err) } errch <- err }() def := log.StandardLogger() fd, isTerminal := term.GetFdInfo(def.Out) out := def.Out if !isTerminal { out = def.Writer() } if err := jsonmessage.DisplayJSONMessagesStream(pipeReader, out, fd, isTerminal); err != nil { return nil, fmt.Errorf("Failed to process json stream for image: %s, error: %s", image, err) } if err := <-errch; err != nil { return nil, fmt.Errorf("Failed to pull image %s, error: %s", image, err) } } img, err := client.InspectImage(image.String()) if err != nil { return nil, fmt.Errorf("Failed to inspect image %s after pull, error: %s", image, err) } return img, nil }
func ensureImage(cli DockerClient, image string) (string, error) { ctx := context.Background() info, _, err := cli.ImageInspectWithRaw(ctx, image, false) if err == nil { logrus.Debugf("Image found locally %s", image) return info.ID, nil } if !client.IsErrImageNotFound(err) { logrus.Errorf("Error inspecting image %q: %v", image, err) return "", err } // Image must be tagged reference if it does not exist ref, err := reference.Parse(image) if err != nil { logrus.Errorf("Image is not valid reference %q: %v", image, err) return "", err } tagged, ok := ref.(reference.NamedTagged) if !ok { logrus.Errorf("Tagged reference required %q", image) return "", errors.New("invalid reference, tag needed") } pullStart := time.Now() pullOptions := types.ImagePullOptions{ PrivilegeFunc: registryAuthNotSupported, } resp, err := cli.ImagePull(ctx, tagged.String(), pullOptions) if err != nil { logrus.Errorf("Error pulling image %q: %v", tagged.String(), err) return "", err } defer resp.Close() outFd, isTerminalOut := term.GetFdInfo(os.Stdout) if err = jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, outFd, isTerminalOut, nil); err != nil { logrus.Errorf("Error copying pull output: %v", err) return "", err } // TODO: Get pulled digest logFields := logrus.Fields{ timerKey: time.Since(pullStart), "image": tagged.String(), } logrus.WithFields(logFields).Info("image pulled") info, _, err = cli.ImageInspectWithRaw(ctx, tagged.String(), false) if err != nil { return "", nil } return info.ID, nil }
func TestDisplayJSONMessagesStream(t *testing.T) { var ( inFd uintptr ) messages := map[string][]string{ // empty string "": { "", ""}, // Without progress & ID "{ \"status\": \"status\" }": { "status\n", "status\n", }, // Without progress, with ID "{ \"id\": \"ID\",\"status\": \"status\" }": { "ID: status\n", fmt.Sprintf("ID: status\n%c[%dB", 27, 0), }, // With progress "{ \"id\": \"ID\", \"status\": \"status\", \"progress\": \"ProgressMessage\" }": { "ID: status ProgressMessage", fmt.Sprintf("\n%c[%dAID: status ProgressMessage%c[%dB", 27, 0, 27, 0), }, // With progressDetail "{ \"id\": \"ID\", \"status\": \"status\", \"progressDetail\": { \"Current\": 1} }": { "", // progressbar is disabled in non-terminal fmt.Sprintf("\n%c[%dA%c[2K\rID: status 1 B\r%c[%dB", 27, 0, 27, 27, 0), }, } for jsonMessage, expectedMessages := range messages { data := bytes.NewBuffer([]byte{}) reader := strings.NewReader(jsonMessage) inFd, _ = term.GetFdInfo(reader) // Without terminal if err := DisplayJSONMessagesStream(reader, data, inFd, false); err != nil { t.Fatal(err) } if data.String() != expectedMessages[0] { t.Fatalf("Expected an [%v], got [%v]", expectedMessages[0], data.String()) } // With terminal data = bytes.NewBuffer([]byte{}) reader = strings.NewReader(jsonMessage) if err := DisplayJSONMessagesStream(reader, data, inFd, true); err != nil { t.Fatal(err) } if data.String() != expectedMessages[1] { t.Fatalf("Expected an [%v], got [%v]", expectedMessages[1], data.String()) } } }
// Connect func Connect(in io.Reader, out io.Writer) { stdInFD, isTerminal := term.GetFdInfo(in) stdOutFD, _ := term.GetFdInfo(out) // if we are using a term, lets upgrade it to RawMode if isTerminal { // handle all incoming os signals and act accordingly; default behavior is to // forward all signals to nanobox server go monitor(stdOutFD) oldState, err := term.SetRawTerminal(stdInFD) // we only use raw mode if it is available. if err == nil { defer term.RestoreTerminal(stdInFD, oldState) } } }
// Console opens a secure console to a code or database service. For code // services, a command is required. This command is executed as root in the // context of the application root directory. For database services, no command // is needed - instead, the appropriate command for the database type is run. // For example, for a postgres database, psql is run. func Console(serviceLabel string, command string, settings *models.Settings) { helpers.SignIn(settings) service := helpers.RetrieveServiceByLabel(serviceLabel, settings) if service == nil { fmt.Printf("Could not find a service with the label \"%s\"\n", serviceLabel) os.Exit(1) } fmt.Printf("Opening console to %s (%s)\n", serviceLabel, service.ID) task := helpers.RequestConsole(command, service.ID, settings) fmt.Print("Waiting for the console to be ready. This might take a minute.") ch := make(chan string, 1) go helpers.PollConsoleJob(task.ID, service.ID, ch, settings) jobID := <-ch defer helpers.DestroyConsole(jobID, service.ID, settings) creds := helpers.RetrieveConsoleTokens(jobID, service.ID, settings) creds.URL = strings.Replace(creds.URL, "http", "ws", 1) fmt.Println("Connecting...") // BEGIN websocket impl config, _ := websocket.NewConfig(creds.URL, "ws://localhost:9443/") config.TlsConfig = &tls.Config{ MinVersion: tls.VersionTLS12, } config.Header["X-Console-Token"] = []string{creds.Token} ws, err := websocket.DialConfig(config) if err != nil { panic(err) } defer ws.Close() fmt.Println("Connection opened") stdin, stdout, _ := term.StdStreams() fdIn, isTermIn := term.GetFdInfo(stdin) if !isTermIn { panic(errors.New("StdIn is not a terminal")) } oldState, err := term.SetRawTerminal(fdIn) if err != nil { panic(err) } done := make(chan bool) msgCh := make(chan []byte, 2) go webSocketDaemon(ws, &stdout, done, msgCh) signal.Notify(make(chan os.Signal, 1), os.Interrupt) defer term.RestoreTerminal(fdIn, oldState) go termDaemon(&stdin, ws) <-done }
func TestDisplayJSONMessagesStreamInvalidJSON(t *testing.T) { var ( inFd uintptr ) data := bytes.NewBuffer([]byte{}) reader := strings.NewReader("This is not a 'valid' JSON []") inFd, _ = term.GetFdInfo(reader) if err := DisplayJSONMessagesStream(reader, data, inFd, false); err == nil && err.Error()[:17] != "invalid character" { t.Fatalf("Should have thrown an error (invalid character in ..), got [%v]", err) } }
// PushImage pushes the image func (c *DockerClient) PushImage(imageName string) (digest string, err error) { var ( img = imagename.NewFromString(imageName) buf bytes.Buffer pipeReader, pipeWriter = io.Pipe() outStream = io.MultiWriter(pipeWriter, &buf) fdOut, isTerminalOut = term.GetFdInfo(c.log.Out) out = c.log.Out opts = docker.PushImageOptions{ Name: img.NameWithRegistry(), Tag: img.GetTag(), Registry: img.Registry, OutputStream: outStream, RawJSONStream: true, } errch = make(chan error, 1) ) if !isTerminalOut { out = c.log.Writer() } c.log.Infof("| Push %s", img) c.log.Debugf("Push with options: %# v", opts) // TODO: DisplayJSONMessagesStream may fail by client.PushImage run without errors go func() { errch <- jsonmessage.DisplayJSONMessagesStream(pipeReader, out, fdOut, isTerminalOut) }() if err := c.client.PushImage(opts, c.auth); err != nil { return "", err } pipeWriter.Close() if err := <-errch; err != nil { return "", fmt.Errorf("Failed to process json stream, error %s", err) } // It is the best way to have pushed image digest so far matches := captureDigest.FindStringSubmatch(buf.String()) if len(matches) > 0 { digest = matches[1] } return digest, nil }
// PullImage pulls docker image func (c *DockerClient) PullImage(name string) error { image := imagename.NewFromString(name) // e.g. s3:bucket-name/image-name if image.Storage == imagename.StorageS3 { if isOld, warning := imagename.WarnIfOldS3ImageName(name); isOld { c.log.Warn(warning) } return c.s3storage.Pull(name) } var ( pipeReader, pipeWriter = io.Pipe() fdOut, isTerminalOut = term.GetFdInfo(c.log.Out) out = c.log.Out errch = make(chan error, 1) ) if !isTerminalOut { out = c.log.Writer() } opts := docker.PullImageOptions{ Repository: image.NameWithRegistry(), Registry: image.Registry, Tag: image.GetTag(), OutputStream: pipeWriter, RawJSONStream: true, } c.log.Infof("| Pull image %s", image) c.log.Debugf("Pull image %s with options: %# v", image, opts) go func() { errch <- jsonmessage.DisplayJSONMessagesStream(pipeReader, out, fdOut, isTerminalOut) }() auth, err := dockerclient.GetAuthForRegistry(c.auth, image) if err != nil { return fmt.Errorf("Failed to authenticate registry %s, error: %s", image.Registry, err) } if err := c.client.PullImage(opts, auth); err != nil { return err } pipeWriter.Close() return <-errch }
func promptForCredentials(settings *models.Settings) { var username string fmt.Print("Username: "******"Password: "******"windows" { stdIn, _, _ := term.StdStreams() fd, _ = term.GetFdInfo(stdIn) } bytes, _ := terminal.ReadPassword(int(fd)) fmt.Println("") settings.Password = string(bytes) }
// Build implements Builder. It consumes the docker build API endpoint and sends // a tar of the specified service build context. func (d *DaemonBuilder) Build(imageName string, p *project.Project, service project.Service) error { if service.Config().Build == "" { return fmt.Errorf("Specified service does not have a build section") } ctx, err := CreateTar(p, service.Name()) if err != nil { return err } defer ctx.Close() var progBuff io.Writer = os.Stdout var buildBuff io.Writer = os.Stdout // Setup an upload progress bar progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true) var body io.Reader = progress.NewProgressReader(ctx, progressOutput, 0, "", "Sending build context to Docker daemon") client := d.context.ClientFactory.Create(service) logrus.Infof("Building %s...", imageName) outFd, isTerminalOut := term.GetFdInfo(os.Stdout) response, err := client.ImageBuild(context.Background(), types.ImageBuildOptions{ Context: body, Tags: []string{imageName}, NoCache: d.context.NoCache, Remove: true, Dockerfile: service.Config().Dockerfile, AuthConfigs: d.context.ConfigFile.AuthConfigs, }) err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, outFd, isTerminalOut, nil) if err != nil { if jerr, ok := err.(*jsonmessage.JSONError); ok { // If no error code is set, default to 1 if jerr.Code == 0 { jerr.Code = 1 } fmt.Fprintf(os.Stderr, "%s%s", progBuff, buildBuff) return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code) } } return err }
// PullImage pulls the specified image (can be a name, an id or a digest) // to the daemon store with the specified client. func PullImage(ctx context.Context, client client.ImageAPIClient, serviceName string, authLookup auth.Lookup, image string) error { fmt.Fprintf(os.Stderr, "Pulling %s (%s)...\n", serviceName, image) distributionRef, err := reference.ParseNamed(image) if err != nil { return err } repoInfo, err := registry.ParseRepositoryInfo(distributionRef) if err != nil { return err } authConfig := authLookup.Lookup(repoInfo) // Use ConfigFile.SaveToWriter to not re-define encodeAuthToBase64 encodedAuth, err := encodeAuthToBase64(authConfig) if err != nil { return err } options := types.ImagePullOptions{ RegistryAuth: encodedAuth, } responseBody, err := client.ImagePull(ctx, distributionRef.String(), options) if err != nil { logrus.Errorf("Failed to pull image %s: %v", image, err) return err } defer responseBody.Close() var writeBuff io.Writer = os.Stderr outFd, isTerminalOut := term.GetFdInfo(os.Stderr) err = jsonmessage.DisplayJSONMessagesStream(responseBody, writeBuff, outFd, isTerminalOut, nil) if err != nil { if jerr, ok := err.(*jsonmessage.JSONError); ok { // If no error code is set, default to 1 if jerr.Code == 0 { jerr.Code = 1 } fmt.Fprintf(os.Stderr, "%s", writeBuff) return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code) } } return err }
func (t *Task) runContainer(ctx *context.ExecuteContext) error { interactive := t.config.Interactive name := ContainerName(ctx, t.name.Resource()) container, err := ctx.Client.CreateContainer(t.createOptions(ctx, name)) if err != nil { return fmt.Errorf("failed creating container %q: %s", name, err) } chanSig := t.forwardSignals(ctx.Client, container.ID) defer signal.Stop(chanSig) defer RemoveContainer(t.logger(), ctx.Client, container.ID, true) _, err = ctx.Client.AttachToContainerNonBlocking(docker.AttachToContainerOptions{ Container: container.ID, OutputStream: t.output(), ErrorStream: os.Stderr, InputStream: ioutil.NopCloser(os.Stdin), Stream: true, Stdin: t.config.Interactive, RawTerminal: t.config.Interactive, Stdout: true, Stderr: true, }) if err != nil { return fmt.Errorf("failed attaching to container %q: %s", name, err) } if interactive { inFd, _ := term.GetFdInfo(os.Stdin) state, err := term.SetRawTerminal(inFd) if err != nil { return err } defer func() { if err := term.RestoreTerminal(inFd, state); err != nil { t.logger().Warnf("Failed to restore fd %v: %s", inFd, err) } }() } if err := ctx.Client.StartContainer(container.ID, nil); err != nil { return fmt.Errorf("failed starting container %q: %s", name, err) } return t.wait(ctx.Client, container.ID) }
// MonitorSize monitors the terminal's size. It returns a TerminalSizeQueue primed with // initialSizes, or nil if there's no TTY present. func (t *TTY) MonitorSize(initialSizes ...*Size) TerminalSizeQueue { outFd, isTerminal := term.GetFdInfo(t.Out) if !isTerminal { return nil } t.sizeQueue = &sizeQueue{ t: *t, // make it buffered so we can send the initial terminal sizes without blocking, prior to starting // the streaming below resizeChan: make(chan Size, len(initialSizes)), stopResizing: make(chan struct{}), } t.sizeQueue.monitorSize(outFd, initialSizes...) return t.sizeQueue }
func (client *DockerClient) PullImage(name string, auth *AuthConfig, cliOut io.Writer) (err error) { v := url.Values{} v.Set("fromImage", name) uri := fmt.Sprintf("/%s/images/create?%s", APIVersion, v.Encode()) req, _ := http.NewRequest("POST", client.URL.String()+uri, nil) if auth != nil { req.Header.Add("X-Registry-Auth", auth.encode()) } var resp *http.Response resp, err = client.HTTPClient.Do(req) if err != nil { return } defer resp.Body.Close() errorReader := io.Reader(resp.Body) if cliOut != nil { pipeReader, pipeWriter := io.Pipe() streamErrChan := make(chan error) defer func() { pipeWriter.Close() if err == nil { err = <-streamErrChan } }() errorReader = io.TeeReader(resp.Body, pipeWriter) go func() { fd, isTerminalIn := term.GetFdInfo(cliOut) streamErrChan <- jsonmessage.DisplayJSONMessagesStream(pipeReader, cliOut, fd, isTerminalIn) }() } var finalObj map[string]interface{} for decoder := json.NewDecoder(errorReader); err == nil; err = decoder.Decode(&finalObj) { } if err != io.EOF { return } else { err = nil } if errObj, ok := finalObj["error"]; ok { err = fmt.Errorf("%v", errObj) return } return }
// Build implements Builder. It consumes the docker build API endpoint and sends // a tar of the specified service build context. func (d *DaemonBuilder) Build(ctx context.Context, imageName string) error { buildCtx, err := createTar(d.ContextDirectory, d.Dockerfile) if err != nil { return err } defer buildCtx.Close() var progBuff io.Writer = os.Stdout var buildBuff io.Writer = os.Stdout // Setup an upload progress bar progressOutput := streamformatter.NewStreamFormatter().NewProgressOutput(progBuff, true) var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon") logrus.Infof("Building %s...", imageName) outFd, isTerminalOut := term.GetFdInfo(os.Stdout) response, err := d.Client.ImageBuild(ctx, body, types.ImageBuildOptions{ Tags: []string{imageName}, NoCache: d.NoCache, Remove: true, ForceRemove: d.ForceRemove, PullParent: d.Pull, Dockerfile: d.Dockerfile, AuthConfigs: d.AuthConfigs, }) if err != nil { return err } err = jsonmessage.DisplayJSONMessagesStream(response.Body, buildBuff, outFd, isTerminalOut, nil) if err != nil { if jerr, ok := err.(*jsonmessage.JSONError); ok { // If no error code is set, default to 1 if jerr.Code == 0 { jerr.Code = 1 } return fmt.Errorf("Status: %s, Code: %d", jerr.Message, jerr.Code) } } return err }