File: dial_stdio.go

package info (click to toggle)
docker-buildx 0.19.3%2Bds1-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,852 kB
  • sloc: sh: 318; makefile: 73
file content (131 lines) | stat: -rw-r--r-- 3,116 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package commands

import (
	"io"
	"net"
	"os"

	"github.com/containerd/platforms"
	"github.com/docker/buildx/build"
	"github.com/docker/buildx/builder"
	"github.com/docker/buildx/util/progress"
	"github.com/docker/cli/cli/command"
	"github.com/moby/buildkit/util/appcontext"
	"github.com/moby/buildkit/util/progress/progressui"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"golang.org/x/sync/errgroup"
)

type stdioOptions struct {
	builder  string
	platform string
	progress string
}

func runDialStdio(dockerCli command.Cli, opts stdioOptions) error {
	ctx := appcontext.Context()

	contextPathHash, _ := os.Getwd()
	b, err := builder.New(dockerCli,
		builder.WithName(opts.builder),
		builder.WithContextPathHash(contextPathHash),
	)
	if err != nil {
		return err
	}

	if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
		return errors.Wrapf(err, "failed to update builder last activity time")
	}
	nodes, err := b.LoadNodes(ctx)
	if err != nil {
		return err
	}

	printer, err := progress.NewPrinter(ctx, os.Stderr, progressui.DisplayMode(opts.progress), progress.WithPhase("dial-stdio"), progress.WithDesc("builder: "+b.Name, "builder:"+b.Name))
	if err != nil {
		return err
	}

	var p *v1.Platform
	if opts.platform != "" {
		pp, err := platforms.Parse(opts.platform)
		if err != nil {
			return errors.Wrapf(err, "invalid platform %q", opts.platform)
		}
		p = &pp
	}

	defer printer.Wait()

	return progress.Wrap("Proxying to builder", printer.Write, func(sub progress.SubLogger) error {
		var conn net.Conn

		err := sub.Wrap("Dialing builder", func() error {
			conn, err = build.Dial(ctx, nodes, printer, p)
			if err != nil {
				return err
			}
			return nil
		})
		if err != nil {
			return err
		}

		defer conn.Close()

		go func() {
			<-ctx.Done()
			closeWrite(conn)
		}()

		var eg errgroup.Group

		eg.Go(func() error {
			_, err := io.Copy(conn, os.Stdin)
			closeWrite(conn)
			return err
		})
		eg.Go(func() error {
			_, err := io.Copy(os.Stdout, conn)
			closeRead(conn)
			return err
		})
		return eg.Wait()
	})
}

func closeRead(conn net.Conn) error {
	if c, ok := conn.(interface{ CloseRead() error }); ok {
		return c.CloseRead()
	}
	return conn.Close()
}

func closeWrite(conn net.Conn) error {
	if c, ok := conn.(interface{ CloseWrite() error }); ok {
		return c.CloseWrite()
	}
	return conn.Close()
}

func dialStdioCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
	opts := stdioOptions{}

	cmd := &cobra.Command{
		Use:   "dial-stdio",
		Short: "Proxy current stdio streams to builder instance",
		Args:  cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			opts.builder = rootOpts.builder
			return runDialStdio(dockerCli, opts)
		},
	}

	flags := cmd.Flags()
	flags.StringVar(&opts.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Target platform: this is used for node selection")
	flags.StringVar(&opts.progress, "progress", "quiet", `Set type of progress output ("auto", "plain", "tty", "rawjson"). Use plain to show container output`)
	return cmd
}