Skip to content

Commit 5e22a2d

Browse files
committed
feat: add experimental proxy command
The new proxy command will start a layer 7 remote buildkit proxy and execute a command in its context. Example: ```sh depot proxy buildctl build --local context=. --local dockerfile=. --opt filename=./Dockerfile --frontend dockerfile.v0 ``` Signed-off-by: Chris Goller <[email protected]>
1 parent 37c4635 commit 5e22a2d

File tree

4 files changed

+1079
-0
lines changed

4 files changed

+1079
-0
lines changed

pkg/cmd/proxy/proxy.go

+210
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package proxy
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"os"
8+
"os/exec"
9+
"os/signal"
10+
"runtime"
11+
"strings"
12+
"syscall"
13+
14+
"github.com/depot/cli/pkg/connection"
15+
"github.com/depot/cli/pkg/helpers"
16+
"github.com/depot/cli/pkg/machine"
17+
"github.com/depot/cli/pkg/progresshelper"
18+
cliv1 "github.com/depot/cli/pkg/proto/depot/cli/v1"
19+
"github.com/docker/buildx/util/progress"
20+
"github.com/docker/cli/cli"
21+
"github.com/docker/cli/cli/command"
22+
"github.com/spf13/cobra"
23+
)
24+
25+
func NewCmdProxy(dockerCli command.Cli) *cobra.Command {
26+
var (
27+
envVar string
28+
token string
29+
projectID string
30+
platform string
31+
progressMode string
32+
)
33+
34+
run := func(cmd *cobra.Command, args []string) error {
35+
ctx := cmd.Context()
36+
37+
token, err := helpers.ResolveToken(ctx, token)
38+
if err != nil {
39+
return err
40+
}
41+
projectID = helpers.ResolveProjectID(projectID)
42+
if projectID == "" {
43+
selectedProject, err := helpers.OnboardProject(ctx, token)
44+
if err != nil {
45+
return err
46+
}
47+
projectID = selectedProject.ID
48+
}
49+
50+
if token == "" {
51+
return fmt.Errorf("missing API token, please run `depot login`")
52+
}
53+
54+
platform, err = ResolveMachinePlatform(platform)
55+
if err != nil {
56+
return err
57+
}
58+
59+
req := &cliv1.CreateBuildRequest{
60+
ProjectId: &projectID,
61+
Options: []*cliv1.BuildOptions{{Command: cliv1.Command_COMMAND_EXEC}},
62+
}
63+
64+
if len(args) > 0 && args[0] == "dagger" {
65+
daggerVersion, _ := helpers.ResolveDaggerVersion()
66+
if daggerVersion != "" {
67+
req = helpers.NewDaggerRequest(projectID, daggerVersion)
68+
}
69+
}
70+
71+
build, err := helpers.BeginBuild(ctx, req, token)
72+
if err != nil {
73+
return fmt.Errorf("unable to begin build: %w", err)
74+
}
75+
76+
var buildErr error
77+
defer func() {
78+
build.Finish(buildErr)
79+
}()
80+
81+
printCtx, cancel := context.WithCancel(ctx)
82+
printer, buildErr := progress.NewPrinter(printCtx, os.Stderr, os.Stderr, progressMode)
83+
if buildErr != nil {
84+
cancel()
85+
return buildErr
86+
}
87+
88+
reportingWriter := progresshelper.NewReportingWriter(printer, build.ID, build.Token)
89+
90+
var builder *machine.Machine
91+
buildErr = progresshelper.WithLog(reportingWriter, fmt.Sprintf("[depot] launching %s machine", platform), func() error {
92+
for i := 0; i < 2; i++ {
93+
builder, buildErr = machine.Acquire(ctx, build.ID, build.Token, platform)
94+
if buildErr == nil {
95+
break
96+
}
97+
}
98+
return buildErr
99+
})
100+
if buildErr != nil {
101+
cancel()
102+
return buildErr
103+
}
104+
105+
defer func() { _ = builder.Release() }()
106+
107+
// Wait for connection to be ready.
108+
var conn net.Conn
109+
buildErr = progresshelper.WithLog(reportingWriter, fmt.Sprintf("[depot] connecting to %s machine", platform), func() error {
110+
conn, buildErr = connection.TLSConn(ctx, builder)
111+
if buildErr != nil {
112+
return fmt.Errorf("unable to connect: %w", buildErr)
113+
}
114+
_ = conn.Close()
115+
return nil
116+
})
117+
cancel()
118+
119+
listener, localAddr, buildErr := connection.LocalListener()
120+
if buildErr != nil {
121+
return buildErr
122+
}
123+
proxy := connection.NewGRPCProxy(listener, builder)
124+
125+
proxyCtx, proxyCancel := context.WithCancel(ctx)
126+
defer proxyCancel()
127+
go func() { _ = proxy.Start(proxyCtx) }()
128+
129+
sigChan := make(chan os.Signal, 1)
130+
signal.Notify(sigChan)
131+
132+
subCmd := exec.CommandContext(ctx, args[0], args[1:]...)
133+
134+
env := os.Environ()
135+
subCmd.Env = append(env, fmt.Sprintf("%s=%s", envVar, localAddr))
136+
subCmd.Stdin = os.Stdin
137+
subCmd.Stdout = os.Stdout
138+
subCmd.Stderr = os.Stderr
139+
140+
buildErr = subCmd.Start()
141+
if buildErr != nil {
142+
return buildErr
143+
}
144+
145+
go func() {
146+
for {
147+
sig := <-sigChan
148+
_ = subCmd.Process.Signal(sig)
149+
}
150+
}()
151+
152+
buildErr = subCmd.Wait()
153+
if buildErr != nil {
154+
return buildErr
155+
}
156+
157+
return nil
158+
}
159+
160+
cmd := &cobra.Command{
161+
Hidden: true,
162+
Use: "proxy [flags] command [args...]",
163+
Short: "Execute a command with proxied BuildKit connection",
164+
Args: cli.RequiresMinArgs(1),
165+
Run: func(cmd *cobra.Command, args []string) {
166+
if err := run(cmd, args); err != nil {
167+
if exitErr, ok := err.(*exec.ExitError); ok {
168+
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
169+
os.Exit(status.ExitStatus())
170+
}
171+
}
172+
173+
fmt.Fprintln(os.Stderr, err)
174+
os.Exit(1)
175+
}
176+
},
177+
}
178+
179+
cmd.Flags().SetInterspersed(false)
180+
cmd.Flags().StringVar(&envVar, "env-var", "BUILDKIT_HOST", "Environment variable name for the BuildKit connection")
181+
cmd.Flags().StringVar(&platform, "platform", "", "Platform to execute the command on")
182+
cmd.Flags().StringVar(&projectID, "project", "", "Depot project ID")
183+
cmd.Flags().StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty")`)
184+
cmd.Flags().StringVar(&token, "token", "", "Depot token")
185+
186+
return cmd
187+
}
188+
189+
func ResolveMachinePlatform(platform string) (string, error) {
190+
if platform == "" {
191+
platform = os.Getenv("DEPOT_BUILD_PLATFORM")
192+
}
193+
194+
switch platform {
195+
case "linux/arm64":
196+
platform = "arm64"
197+
case "linux/amd64":
198+
platform = "amd64"
199+
case "":
200+
if strings.HasPrefix(runtime.GOARCH, "arm") {
201+
platform = "arm64"
202+
} else {
203+
platform = "amd64"
204+
}
205+
default:
206+
return "", fmt.Errorf("invalid platform: %s (must be one of: linux/amd64, linux/arm64)", platform)
207+
}
208+
209+
return platform, nil
210+
}

pkg/cmd/root/root.go

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
loginCmd "github.com/depot/cli/pkg/cmd/login"
1717
logout "github.com/depot/cli/pkg/cmd/logout"
1818
"github.com/depot/cli/pkg/cmd/projects"
19+
"github.com/depot/cli/pkg/cmd/proxy"
1920
"github.com/depot/cli/pkg/cmd/pull"
2021
"github.com/depot/cli/pkg/cmd/pulltoken"
2122
"github.com/depot/cli/pkg/cmd/push"
@@ -66,6 +67,7 @@ func NewCmdRoot(version, buildDate string) *cobra.Command {
6667
cmd.AddCommand(registry.NewCmdRegistry())
6768
cmd.AddCommand(projects.NewCmdProjects())
6869
cmd.AddCommand(exec.NewCmdExec(dockerCli))
70+
cmd.AddCommand(proxy.NewCmdProxy(dockerCli))
6971

7072
return cmd
7173
}

0 commit comments

Comments
 (0)