Пример #1
0
// 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
}
Пример #2
0
// 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
}
Пример #3
0
// 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
}
Пример #4
0
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,
	}
}
Пример #5
0
// 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
}
Пример #6
0
// 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,
	}
}
Пример #7
0
// 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)
}
Пример #8
0
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)
}
Пример #9
0
// 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
}
Пример #10
0
// 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)
}
Пример #11
0
// 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)
}
Пример #12
0
// 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,
	}
}
Пример #13
0
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))
}
Пример #14
0
// 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
}
Пример #15
0
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
}
Пример #16
0
// 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
}
Пример #17
0
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
}
Пример #18
0
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())
		}
	}

}
Пример #19
0
// 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)
		}
	}
}
Пример #20
0
// 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
}
Пример #21
0
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)
	}
}
Пример #22
0
// 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
}
Пример #23
0
// 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
}
Пример #24
0
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)
}
Пример #25
0
// 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
}
Пример #26
0
// 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
}
Пример #27
0
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)
}
Пример #28
0
// 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
}
Пример #29
0
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
}
Пример #30
0
// 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
}