From 7aae22dd753a336c4a55bbce6d9a301eded9709f Mon Sep 17 00:00:00 2001 From: Chris Goller Date: Wed, 22 May 2024 13:39:14 -0500 Subject: [PATCH 1/8] Add container image building with depot This borrows code from dockerfileBuilder, but has some differences as depot is remote and can build and push to the fly registry directly. The CLI arguments followed the style of the nixpacks flags. To deploy using depot: - flyctl deploy --depot To run a machine: * flyctl machines run --build-depot Signed-off-by: Chris Goller --- go.mod | 7 +- go.sum | 6 + internal/build/imgsrc/depot.go | 342 ++++++++++++++++++++++++ internal/build/imgsrc/docker.go | 12 +- internal/build/imgsrc/docker_test.go | 19 +- internal/build/imgsrc/resolver.go | 4 +- internal/command/command_run.go | 2 +- internal/command/console/console.go | 5 + internal/command/deploy/deploy.go | 1 + internal/command/deploy/deploy_build.go | 2 +- internal/command/machine/run.go | 4 + internal/flag/flag.go | 8 + 12 files changed, 398 insertions(+), 14 deletions(-) create mode 100644 internal/build/imgsrc/depot.go diff --git a/go.mod b/go.mod index 2684816398..0bc6e2ca47 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/superfly/flyctl -go 1.21.7 +go 1.22 + +toolchain go1.22.2 require ( github.com/AlecAivazis/survey/v2 v2.3.7 @@ -20,6 +22,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/chzyer/readline v1.5.1 github.com/cli/safeexec v1.0.1 + github.com/depot/depot-go v0.3.0 github.com/docker/docker v25.0.3+incompatible github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 @@ -94,6 +97,7 @@ require ( ) require ( + connectrpc.com/connect v1.16.1 // indirect dario.cat/mergo v1.0.0 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect @@ -109,6 +113,7 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/adrg/xdg v0.4.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect github.com/alexflint/go-arg v1.4.2 // indirect diff --git a/go.sum b/go.sum index e10ea90da7..ca4193fa58 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJd cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +connectrpc.com/connect v1.16.1 h1:rOdrK/RTI/7TVnn3JsVxt3n028MlTRwmK5Q4heSpjis= +connectrpc.com/connect v1.16.1/go.mod h1:XpZAduBQUySsb4/KO5JffORVkDI4B6/EYPi7N8xpNZw= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= @@ -59,6 +61,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCv github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/rehttp v1.4.0 h1:rIN7A2s+O9fmHUM1vUcInvlHj9Ysql4hE+Y0wcl/xk8= github.com/PuerkitoBio/rehttp v1.4.0/go.mod h1:LUwKPoDbDIA2RL5wYZCNsQ90cx4OJ4AWBmq6KzWZL1s= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -214,6 +218,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/depot/depot-go v0.3.0 h1:OEGwIZS5nqfy4YHZVDduGxTX/IG+0VgHTx+kjxZ/SYU= +github.com/depot/depot-go v0.3.0/go.mod h1:9xKcGBd3HlDFcFkRbbdOWF/+2bBG0aFtpZAI+5rvfDc= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= diff --git a/internal/build/imgsrc/depot.go b/internal/build/imgsrc/depot.go new file mode 100644 index 0000000000..60b324db80 --- /dev/null +++ b/internal/build/imgsrc/depot.go @@ -0,0 +1,342 @@ +package imgsrc + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "log" + "os" + "path/filepath" + "time" + + depotauth "github.com/depot/depot-go/auth" + depotbuild "github.com/depot/depot-go/build" + depotmachine "github.com/depot/depot-go/machine" + depotproject "github.com/depot/depot-go/project" + cliv1 "github.com/depot/depot-go/proto/depot/cli/v1" + "github.com/moby/buildkit/client" + "github.com/moby/buildkit/session/secrets/secretsprovider" + "github.com/moby/buildkit/util/progress/progressui" + "github.com/pkg/errors" + "github.com/superfly/flyctl/helpers" + "github.com/superfly/flyctl/internal/cmdfmt" + "github.com/superfly/flyctl/internal/config" + "github.com/superfly/flyctl/internal/metrics" + "github.com/superfly/flyctl/internal/render" + "github.com/superfly/flyctl/internal/tracing" + "github.com/superfly/flyctl/iostreams" + "github.com/superfly/flyctl/terminal" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" + "golang.org/x/sync/errgroup" +) + +var _ imageBuilder = (*DepotBuilder)(nil) + +type DepotBuilder struct{} + +func (d *DepotBuilder) Name() string { return "depot.dev" } + +func (d *DepotBuilder) Run(ctx context.Context, _ *dockerClientFactory, streams *iostreams.IOStreams, opts ImageOptions, build *build) (*DeploymentImage, string, error) { + ctx, span := tracing.GetTracer().Start(ctx, "depot_builder", trace.WithAttributes(opts.ToSpanAttributes()...)) + defer span.End() + + build.BuildStart() + + var dockerfile string + + if opts.DockerfilePath != "" { + if !helpers.FileExists(opts.DockerfilePath) { + build.BuildFinish() + err := fmt.Errorf("dockerfile '%s' not found", opts.DockerfilePath) + tracing.RecordError(span, err, "failed to find dockerfile") + return nil, "", err + } + dockerfile = opts.DockerfilePath + } else { + dockerfile = ResolveDockerfile(opts.WorkingDir) + } + + if dockerfile == "" { + span.AddEvent("dockerfile not found, skipping") + terminal.Debug("dockerfile not found, skipping") + build.BuildFinish() + return nil, "", nil + } + + var relDockerfile string + if isPathInRoot(dockerfile, opts.WorkingDir) { + // pass the relative path to Dockerfile within the context + p, err := filepath.Rel(opts.WorkingDir, dockerfile) + if err != nil { + tracing.RecordError(span, err, "failed to get relative dockerfile path") + build.BuildFinish() + return nil, "", err + } + // On Windows, convert \ to a slash / as the docker build will + // run in a Linux VM at the end. + relDockerfile = filepath.ToSlash(p) + } + span.SetAttributes(attribute.String("relative_dockerfile_path", relDockerfile)) + + build.ImageBuildStart() + + image, err := depotBuild(ctx, streams, opts, dockerfile, build) + if err != nil { + metrics.SendNoData(ctx, "remote_builder_failure") + build.ImageBuildFinish() + build.BuildFinish() + tracing.RecordError(span, err, "failed to build image") + return nil, "", errors.Wrap(err, "error building") + } + + build.ImageBuildFinish() + build.BuildFinish() + cmdfmt.PrintDone(streams.ErrOut, "Building image done") + + span.SetAttributes(image.ToSpanAttributes()...) + return image, "", nil +} + +func depotBuild(ctx context.Context, streams *iostreams.IOStreams, opts ImageOptions, dockerfilePath string, buildState *build) (*DeploymentImage, error) { + buildState.BuilderInitStart() + + token, err := depotauth.ResolveToken(ctx, "") + if err != nil { + buildState.BuilderInitFinish() + return nil, err + } + project := depotproject.ResolveProjectID("") + + { + msg := "Waiting for depot builder...\n" + if streams.IsInteractive() { + streams.StartProgressIndicatorMsg(msg) + } else { + fmt.Fprintln(streams.ErrOut, msg) + } + } + + req := &cliv1.CreateBuildRequest{ + ProjectId: project, + Options: []*cliv1.BuildOptions{ + { + Command: cliv1.Command_COMMAND_FLYCTL, + Tags: []string{opts.Tag}, + TargetName: &opts.Target, + Push: true, + Outputs: []*cliv1.BuildOutput{ + { + Kind: "image", + Attributes: map[string]string{"name": opts.Tag, "push": "true", "oci-mediatypes": "true"}, + }, + }, + }, + }, + } + + build, err := depotbuild.NewBuild(ctx, req, token) + if err != nil { + streams.StopProgressIndicator() + buildState.BuilderInitFinish() + return nil, err + } + + // Set the buildErr to any error that represents the build failing. + var buildErr error + defer build.Finish(buildErr) + + var buildkit *depotmachine.Machine + buildkit, buildErr = depotmachine.Acquire(ctx, build.ID, build.Token, "amd64") + if buildErr != nil { + streams.StopProgressIndicator() + buildState.BuilderInitFinish() + return nil, buildErr + } + defer buildkit.Release() + buildState.BuilderInitFinish() + + connectCtx, cancelConnect := context.WithTimeout(ctx, 5*time.Minute) + defer cancelConnect() + + var buildkitClient *client.Client + buildkitClient, buildErr = buildkit.Connect(connectCtx) + if buildErr != nil { + streams.StopProgressIndicator() + return nil, buildErr + } + + streams.StopProgressIndicator() + + tb := render.NewTextBlock(ctx, "Building image with Depot") + link := streams.CreateLink("build: ", build.BuildURL) + tb.Done(link) + + buildState.BuildAndPushStart() + res, buildErr := buildImage(ctx, buildkitClient, opts, dockerfilePath) + if buildErr != nil { + buildState.BuildAndPushFinish() + return nil, buildErr + } + buildState.BuildAndPushFinish() + + link = streams.CreateLink("Build Summary: ", build.BuildURL) + tb.Done(link) + + return newDeploymentImage(res, opts.Tag) +} + +func buildImage(ctx context.Context, buildkitClient *client.Client, opts ImageOptions, dockerfilePath string) (*client.SolveResponse, error) { + var ( + res *client.SolveResponse + err error + ) + + exportEntry := client.ExportEntry{ + Type: "image", + Attrs: map[string]string{ + "name": opts.Tag, + "oci-mediatypes": "true", + }, + } + + if opts.Publish { + exportEntry.Attrs["push"] = "true" + } + + ch := make(chan *client.SolveStatus) + eg, ctx := errgroup.WithContext(ctx) + eg.Go(func() error { + solverOptions := client.SolveOpt{ + Frontend: "dockerfile.v0", + FrontendAttrs: map[string]string{ + "filename": filepath.Base(dockerfilePath), + "target": opts.Target, + "platform": "linux/amd64", + }, + LocalDirs: map[string]string{ + "dockerfile": filepath.Dir(dockerfilePath), + "context": opts.WorkingDir, + }, + Exports: []client.ExportEntry{exportEntry}, + // Prevent recording the build steps and traces in buildkit as it is _very_ slow. + Internal: true, + } + if opts.NoCache { + solverOptions.FrontendAttrs["no-cache"] = "" + } + for k, v := range opts.Label { + solverOptions.FrontendAttrs["label:"+k] = v + } + for k, v := range opts.BuildArgs { + solverOptions.FrontendAttrs["build-arg:"+k] = v + } + + secrets := make(map[string][]byte) + for k, v := range opts.BuildSecrets { + secrets[k] = []byte(v) + } + solverOptions.Session = append( + solverOptions.Session, + newBuildkitAuthProvider(config.Tokens(ctx).Docker()), + secretsprovider.FromMap(secrets), + ) + + res, err = buildkitClient.Solve(ctx, nil, solverOptions, ch) + return err + }) + + eg.Go(func() error { + display, err := progressui.NewDisplay(os.Stderr, progressui.AutoMode) + if err != nil { + return err + } + + _, err = display.UpdateFrom(context.Background(), ch) + return err + }) + + if err := eg.Wait(); err != nil { + return nil, err + } + + return res, nil +} + +func newDeploymentImage(res *client.SolveResponse, tag string) (*DeploymentImage, error) { + id := res.ExporterResponse["containerimage.digest"] + encoded := res.ExporterResponse["containerimage.descriptor"] + output, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, err + } + + descriptor := &Descriptor{} + err = json.Unmarshal(output, descriptor) + if err != nil { + return nil, err + } + + image := &DeploymentImage{ + ID: id, + Tag: tag, + Size: descriptor.Bytes(), + } + + return image, nil +} + +type Descriptor struct { + MediaType string `json:"mediaType,omitempty"` + Digest string `json:"digest,omitempty"` + Size int64 `json:"size,omitempty"` + Annotations Annotations `json:"annotations,omitempty"` +} + +func (d *Descriptor) Bytes() int64 { + return d.Size + d.Annotations.Bytes() +} + +type Annotations struct { + RawManifest string `json:"depot.containerimage.manifest,omitempty"` +} + +func (a *Annotations) Manifest() (*Manifest, error) { + manifest := &Manifest{} + err := json.Unmarshal([]byte(a.RawManifest), manifest) + if err != nil { + return nil, err + } + return manifest, nil +} + +func (a *Annotations) Bytes() int64 { + manifest, err := a.Manifest() + if err != nil { + log.Printf("failed to get manifest: %v", err) + return 0 + } + return manifest.Bytes() +} + +type Manifest struct { + SchemaVersion int `json:"schemaVersion,omitempty"` + MediaType string `json:"mediaType,omitempty"` + Config OCIDescriptor `json:"config,omitempty"` + Layers []OCIDescriptor `json:"layers,omitempty"` +} + +func (m *Manifest) Bytes() int64 { + size := m.Config.Size + for _, layer := range m.Layers { + size += layer.Size + } + return size +} + +type OCIDescriptor struct { + MediaType string `json:"mediaType,omitempty"` + Digest string `json:"digest,omitempty"` + Size int64 `json:"size,omitempty"` +} diff --git a/internal/build/imgsrc/docker.go b/internal/build/imgsrc/docker.go index 25efe7fc05..b40ec8f8b2 100644 --- a/internal/build/imgsrc/docker.go +++ b/internal/build/imgsrc/docker.go @@ -107,7 +107,7 @@ func newDockerClientFactory(daemonType DockerDaemonType, apiClient flyutil.Clien } } -func NewDockerDaemonType(allowLocal, allowRemote, prefersLocal, useNixpacks bool) DockerDaemonType { +func NewDockerDaemonType(allowLocal, allowRemote, prefersLocal, useDepot, useNixpacks bool) DockerDaemonType { daemonType := DockerDaemonTypeNone if allowLocal { daemonType = daemonType | DockerDaemonTypeLocal @@ -115,6 +115,9 @@ func NewDockerDaemonType(allowLocal, allowRemote, prefersLocal, useNixpacks bool if allowRemote { daemonType = daemonType | DockerDaemonTypeRemote } + if useDepot { + daemonType = daemonType | DockerDaemonTypeDepot + } if useNixpacks { daemonType = daemonType | DockerDaemonTypeNixpacks } @@ -132,6 +135,7 @@ const ( DockerDaemonTypeNone DockerDaemonTypePrefersLocal DockerDaemonTypeNixpacks + DockerDaemonTypeDepot ) func (t DockerDaemonType) String() string { @@ -144,6 +148,8 @@ func (t DockerDaemonType) String() string { return "none" case DockerDaemonTypePrefersLocal: return "prefers-local" + case DockerDaemonTypeDepot: + return "depot" case DockerDaemonTypeNixpacks: return "nix-packs" default: @@ -175,6 +181,10 @@ func (t DockerDaemonType) UseNixpacks() bool { return (t & DockerDaemonTypeNixpacks) != 0 } +func (t DockerDaemonType) UseDepot() bool { + return (t & DockerDaemonTypeDepot) != 0 +} + func (t DockerDaemonType) PrefersLocal() bool { return (t & DockerDaemonTypePrefersLocal) != 0 } diff --git a/internal/build/imgsrc/docker_test.go b/internal/build/imgsrc/docker_test.go index 6ae4f14090..01ff1b1193 100644 --- a/internal/build/imgsrc/docker_test.go +++ b/internal/build/imgsrc/docker_test.go @@ -11,21 +11,22 @@ func TestAllowedDockerDaemonMode(t *testing.T) { allowLocal bool allowRemote bool preferslocal bool + useDepot bool useNixpacks bool expected DockerDaemonType }{ - {false, false, false, false, DockerDaemonTypeNone}, - {false, false, true, false, DockerDaemonTypeNone | DockerDaemonTypePrefersLocal}, - {false, true, false, false, DockerDaemonTypeNone | DockerDaemonTypeRemote}, - {false, true, true, false, DockerDaemonTypeNone | DockerDaemonTypeRemote | DockerDaemonTypePrefersLocal}, - {true, false, false, false, DockerDaemonTypeNone | DockerDaemonTypeLocal}, - {true, false, true, false, DockerDaemonTypeNone | DockerDaemonTypeLocal | DockerDaemonTypePrefersLocal}, - {true, true, false, false, DockerDaemonTypeNone | DockerDaemonTypeLocal | DockerDaemonTypeRemote}, - {true, true, true, false, DockerDaemonTypeNone | DockerDaemonTypeLocal | DockerDaemonTypeRemote | DockerDaemonTypePrefersLocal}, + {false, false, false, false, false, DockerDaemonTypeNone}, + {false, false, true, false, false, DockerDaemonTypeNone | DockerDaemonTypePrefersLocal}, + {false, true, false, false, false, DockerDaemonTypeNone | DockerDaemonTypeRemote}, + {false, true, true, false, false, DockerDaemonTypeNone | DockerDaemonTypeRemote | DockerDaemonTypePrefersLocal}, + {true, false, false, false, false, DockerDaemonTypeNone | DockerDaemonTypeLocal}, + {true, false, true, false, false, DockerDaemonTypeNone | DockerDaemonTypeLocal | DockerDaemonTypePrefersLocal}, + {true, true, false, false, false, DockerDaemonTypeNone | DockerDaemonTypeLocal | DockerDaemonTypeRemote}, + {true, true, true, false, false, DockerDaemonTypeNone | DockerDaemonTypeLocal | DockerDaemonTypeRemote | DockerDaemonTypePrefersLocal}, } for _, test := range tests { - m := NewDockerDaemonType(test.allowLocal, test.allowRemote, test.preferslocal, test.useNixpacks) + m := NewDockerDaemonType(test.allowLocal, test.allowRemote, test.preferslocal, test.useDepot, test.useNixpacks) assert.Equal(t, test.expected, m) } } diff --git a/internal/build/imgsrc/resolver.go b/internal/build/imgsrc/resolver.go index 9c99b434f5..1abf374057 100644 --- a/internal/build/imgsrc/resolver.go +++ b/internal/build/imgsrc/resolver.go @@ -223,6 +223,8 @@ func (r *Resolver) BuildImage(ctx context.Context, streams *iostreams.IOStreams, if r.dockerFactory.mode.UseNixpacks() { strategies = append(strategies, &nixpacksBuilder{}) + } else if r.dockerFactory.mode.UseDepot() { + strategies = append(strategies, &DepotBuilder{}) } else { strategies = []imageBuilder{ &buildpacksBuilder{}, @@ -622,7 +624,7 @@ func (r *Resolver) StartHeartbeat(ctx context.Context) (*StopSignal, error) { ctx, span := tracing.GetTracer().Start(ctx, "start_heartbeat") defer span.End() - if !r.dockerFactory.remote { + if !r.dockerFactory.remote || r.dockerFactory.mode.UseDepot() { span.AddEvent("won't check heartbeart of non-remote build") return nil, nil } diff --git a/internal/command/command_run.go b/internal/command/command_run.go index b54ce29bd5..ce65c9fffb 100644 --- a/internal/command/command_run.go +++ b/internal/command/command_run.go @@ -36,7 +36,7 @@ func DetermineImage(ctx context.Context, appName string, imageOrPath string) (im cfg = appconfig.ConfigFromContext(ctx) ) - daemonType := imgsrc.NewDockerDaemonType(!flag.GetBool(ctx, "build-remote-only"), !flag.GetBool(ctx, "build-local-only"), env.IsCI(), flag.GetBool(ctx, "build-nixpacks")) + daemonType := imgsrc.NewDockerDaemonType(!flag.GetBool(ctx, "build-remote-only"), !flag.GetBool(ctx, "build-local-only"), env.IsCI(), flag.GetBool(ctx, "build-depot"), flag.GetBool(ctx, "build-nixpacks")) resolver := imgsrc.NewResolver(daemonType, client, appName, io, flag.GetWireguard(ctx)) // build if relative or absolute path diff --git a/internal/command/console/console.go b/internal/command/console/console.go index fdec9d741c..bead8e9e5a 100644 --- a/internal/command/console/console.go +++ b/internal/command/console/console.go @@ -90,6 +90,11 @@ func New() *cobra.Command { Description: "Only perform builds locally using the local docker daemon", Hidden: true, }, + flag.Bool{ + Name: "build-depot", + Description: "Build your image with depot.dev", + Hidden: true, + }, flag.Bool{ Name: "build-nixpacks", Description: "Build your image with nixpacks", diff --git a/internal/command/deploy/deploy.go b/internal/command/deploy/deploy.go index d203c6a802..e129547bd8 100644 --- a/internal/command/deploy/deploy.go +++ b/internal/command/deploy/deploy.go @@ -48,6 +48,7 @@ var CommonFlags = flag.Set{ flag.BuildSecret(), flag.BuildTarget(), flag.NoCache(), + flag.Depot(), flag.Nixpacks(), flag.BuildOnly(), flag.BpDockerHost(), diff --git a/internal/command/deploy/deploy_build.go b/internal/command/deploy/deploy_build.go index 26b4f7dd53..871f28aa7c 100644 --- a/internal/command/deploy/deploy_build.go +++ b/internal/command/deploy/deploy_build.go @@ -55,7 +55,7 @@ func determineImage(ctx context.Context, appConfig *appconfig.Config, useWG bool span.SetAttributes(attribute.Bool("builder.using_wireguard", useWG)) tb := render.NewTextBlock(ctx, "Building image") - daemonType := imgsrc.NewDockerDaemonType(!flag.GetRemoteOnly(ctx), !flag.GetLocalOnly(ctx), env.IsCI(), flag.GetBool(ctx, "nixpacks")) + daemonType := imgsrc.NewDockerDaemonType(!flag.GetRemoteOnly(ctx), !flag.GetLocalOnly(ctx), env.IsCI(), flag.GetBool(ctx, "depot"), flag.GetBool(ctx, "nixpacks")) client := flyutil.ClientFromContext(ctx) io := iostreams.FromContext(ctx) diff --git a/internal/command/machine/run.go b/internal/command/machine/run.go index be81405ce0..9484360ef6 100644 --- a/internal/command/machine/run.go +++ b/internal/command/machine/run.go @@ -62,6 +62,10 @@ var sharedFlags = flag.Set{ Description: "Only perform builds locally using the local docker daemon", Hidden: true, }, + flag.Bool{ + Name: "build-depot", + Description: "Build your image with depot.dev", + }, flag.Bool{ Name: "build-nixpacks", Description: "Build your image with nixpacks", diff --git a/internal/flag/flag.go b/internal/flag/flag.go index 07b63029a7..a964891750 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -537,6 +537,14 @@ func BuildTarget() String { } } +func Depot() Bool { + return Bool{ + Name: "depot", + Default: false, + Description: "Deploy using depot to build the image", + } +} + func Nixpacks() Bool { return Bool{ Name: "nixpacks", From deb927c1cf6d6495c91a318f4236f0147f12acf2 Mon Sep 17 00:00:00 2001 From: William Batista Date: Wed, 24 Jul 2024 12:31:26 -0400 Subject: [PATCH 2/8] Use EnsureDepotRemoteBuilder mutation to generate a project and build From there, we can just use the FromBuild mutation --- go.mod | 3 ++- go.sum | 6 ++--- internal/build/imgsrc/depot.go | 40 ++++++++++------------------------ internal/flyutil/client.go | 1 + internal/inmem/client.go | 4 ++++ internal/mock/client.go | 5 +++++ 6 files changed, 26 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index a9e2a15285..4443d3f7a3 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/chzyer/readline v1.5.1 github.com/cli/safeexec v1.0.1 + github.com/depot/depot-go v0.3.0 github.com/docker/docker v26.1.4+incompatible github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 @@ -114,7 +115,6 @@ require ( github.com/Masterminds/semver v1.5.0 // indirect github.com/Microsoft/hcsshim v0.11.4 // indirect github.com/ProtonMail/go-crypto v1.0.0 // indirect - github.com/adrg/xdg v0.4.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/agnivade/levenshtein v1.1.1 // indirect github.com/alexflint/go-arg v1.5.1 // indirect @@ -266,4 +266,5 @@ require ( replace ( github.com/loadsmart/calver-go => github.com/ndarilek/calver-go v0.0.0-20230710153822-893bbd83a936 github.com/nats-io/nats.go => github.com/btoews/nats.go v0.0.0-20240401180931-476bea7f4158 + github.com/superfly/fly-go => github.com/superfly/fly-go v0.1.19-0.20240724162007-8bcc00062656 ) diff --git a/go.sum b/go.sum index b810a36fb5..e7e322f6cc 100644 --- a/go.sum +++ b/go.sum @@ -58,8 +58,6 @@ github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0k github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/PuerkitoBio/rehttp v1.4.0 h1:rIN7A2s+O9fmHUM1vUcInvlHj9Ysql4hE+Y0wcl/xk8= github.com/PuerkitoBio/rehttp v1.4.0/go.mod h1:LUwKPoDbDIA2RL5wYZCNsQ90cx4OJ4AWBmq6KzWZL1s= -github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= -github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= @@ -620,8 +618,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/superfly/fly-go v0.1.19-0.20240716210409-e3d434ec3f18 h1:lXtwecpu2Ynwm1mkSl7pcJFFM86HzJNUsrNC4vswrYg= -github.com/superfly/fly-go v0.1.19-0.20240716210409-e3d434ec3f18/go.mod h1:JQke/BwoZqrWurqYkypSlcSo7bIUgCI3eVnqMC6AUj0= +github.com/superfly/fly-go v0.1.19-0.20240724162007-8bcc00062656 h1:eCn5Ip114F1EdDzEwhdYA9j6ionM2edgWBEupWH7h4U= +github.com/superfly/fly-go v0.1.19-0.20240724162007-8bcc00062656/go.mod h1:JQke/BwoZqrWurqYkypSlcSo7bIUgCI3eVnqMC6AUj0= github.com/superfly/graphql v0.2.4 h1:Av8hSk4x8WvKJ6MTnEwrLknSVSGPc7DWpgT3z/kt3PU= github.com/superfly/graphql v0.2.4/go.mod h1:CVfDl31srm8HnJ9udwLu6hFNUW/P6GUM2dKcG1YQ8jc= github.com/superfly/lfsc-go v0.1.1 h1:dGjLgt81D09cG+aR9lJZIdmonjZSR5zYCi7s54+ZU2Q= diff --git a/internal/build/imgsrc/depot.go b/internal/build/imgsrc/depot.go index 60b324db80..92b621a677 100644 --- a/internal/build/imgsrc/depot.go +++ b/internal/build/imgsrc/depot.go @@ -10,18 +10,17 @@ import ( "path/filepath" "time" - depotauth "github.com/depot/depot-go/auth" depotbuild "github.com/depot/depot-go/build" depotmachine "github.com/depot/depot-go/machine" - depotproject "github.com/depot/depot-go/project" - cliv1 "github.com/depot/depot-go/proto/depot/cli/v1" "github.com/moby/buildkit/client" "github.com/moby/buildkit/session/secrets/secretsprovider" "github.com/moby/buildkit/util/progress/progressui" "github.com/pkg/errors" + "github.com/superfly/fly-go" "github.com/superfly/flyctl/helpers" "github.com/superfly/flyctl/internal/cmdfmt" "github.com/superfly/flyctl/internal/config" + "github.com/superfly/flyctl/internal/flyutil" "github.com/superfly/flyctl/internal/metrics" "github.com/superfly/flyctl/internal/render" "github.com/superfly/flyctl/internal/tracing" @@ -102,13 +101,6 @@ func (d *DepotBuilder) Run(ctx context.Context, _ *dockerClientFactory, streams func depotBuild(ctx context.Context, streams *iostreams.IOStreams, opts ImageOptions, dockerfilePath string, buildState *build) (*DeploymentImage, error) { buildState.BuilderInitStart() - token, err := depotauth.ResolveToken(ctx, "") - if err != nil { - buildState.BuilderInitFinish() - return nil, err - } - project := depotproject.ResolveProjectID("") - { msg := "Waiting for depot builder...\n" if streams.IsInteractive() { @@ -118,27 +110,19 @@ func depotBuild(ctx context.Context, streams *iostreams.IOStreams, opts ImageOpt } } - req := &cliv1.CreateBuildRequest{ - ProjectId: project, - Options: []*cliv1.BuildOptions{ - { - Command: cliv1.Command_COMMAND_FLYCTL, - Tags: []string{opts.Tag}, - TargetName: &opts.Target, - Push: true, - Outputs: []*cliv1.BuildOutput{ - { - Kind: "image", - Attributes: map[string]string{"name": opts.Tag, "push": "true", "oci-mediatypes": "true"}, - }, - }, - }, - }, + apiClient := flyutil.ClientFromContext(ctx) + buildInfo, err := apiClient.EnsureDepotRemoteBuilder(ctx, &fly.EnsureDepotRemoteBuilderInput{ + AppName: &opts.AppName, + Region: fly.StringPointer("us-east-1"), + }) + if err != nil { + streams.StopProgressIndicator() + buildState.BuilderInitFinish() + return nil, err } - build, err := depotbuild.NewBuild(ctx, req, token) + build, err := depotbuild.FromExistingBuild(ctx, *buildInfo.EnsureDepotRemoteBuilder.BuildId, *buildInfo.EnsureDepotRemoteBuilder.BuildToken) if err != nil { - streams.StopProgressIndicator() buildState.BuilderInitFinish() return nil, err } diff --git a/internal/flyutil/client.go b/internal/flyutil/client.go index 2b5381783c..e3f680ff3e 100644 --- a/internal/flyutil/client.go +++ b/internal/flyutil/client.go @@ -41,6 +41,7 @@ type Client interface { DetachPostgresCluster(ctx context.Context, input fly.DetachPostgresClusterInput) error EnablePostgresConsul(ctx context.Context, appName string) (*fly.PostgresEnableConsulPayload, error) EnsureRemoteBuilder(ctx context.Context, orgID, appName, region string) (*fly.GqlMachine, *fly.App, error) + EnsureDepotRemoteBuilder(ctx context.Context, input *fly.EnsureDepotRemoteBuilderInput) (*fly.EnsureDepotRemoteBuilderResponse, error) ExportDNSRecords(ctx context.Context, domainId string) (string, error) FinishBuild(ctx context.Context, input fly.FinishBuildInput) (*fly.FinishBuildResponse, error) GetApp(ctx context.Context, appName string) (*fly.App, error) diff --git a/internal/inmem/client.go b/internal/inmem/client.go index 48ae1c1127..11781feb24 100644 --- a/internal/inmem/client.go +++ b/internal/inmem/client.go @@ -164,6 +164,10 @@ func (m *Client) EnsureRemoteBuilder(ctx context.Context, orgID, appName, region panic("TODO") } +func (m *Client) EnsureDepotRemoteBuilder(ctx context.Context, input *fly.EnsureDepotRemoteBuilderInput) (*fly.EnsureDepotRemoteBuilderResponse, error) { + panic("TODO") +} + func (m *Client) ExportDNSRecords(ctx context.Context, domainId string) (string, error) { panic("TODO") } diff --git a/internal/mock/client.go b/internal/mock/client.go index 3eea775864..be4129605f 100644 --- a/internal/mock/client.go +++ b/internal/mock/client.go @@ -41,6 +41,7 @@ type Client struct { DeleteOrganizationMembershipFunc func(ctx context.Context, orgId, userId string) (string, string, error) DetachPostgresClusterFunc func(ctx context.Context, input fly.DetachPostgresClusterInput) error EnablePostgresConsulFunc func(ctx context.Context, appName string) (*fly.PostgresEnableConsulPayload, error) + EnsureDepotRemoteBuilderFunc func(ctx context.Context, input *fly.EnsureDepotRemoteBuilderInput) (*fly.EnsureDepotRemoteBuilderResponse, error) EnsureRemoteBuilderFunc func(ctx context.Context, orgID, appName, region string) (*fly.GqlMachine, *fly.App, error) ExportDNSRecordsFunc func(ctx context.Context, domainId string) (string, error) FinishBuildFunc func(ctx context.Context, input fly.FinishBuildInput) (*fly.FinishBuildResponse, error) @@ -214,6 +215,10 @@ func (m *Client) EnsureRemoteBuilder(ctx context.Context, orgID, appName, region return m.EnsureRemoteBuilderFunc(ctx, orgID, appName, region) } +func (m *Client) EnsureDepotRemoteBuilder(ctx context.Context, input *fly.EnsureDepotRemoteBuilderInput) (*fly.EnsureDepotRemoteBuilderResponse, error) { + return m.EnsureDepotRemoteBuilderFunc(ctx, input) +} + func (m *Client) ExportDNSRecords(ctx context.Context, domainId string) (string, error) { return m.ExportDNSRecordsFunc(ctx, domainId) } From 55394105f421534f5f07179d1283fe534a6ce74b Mon Sep 17 00:00:00 2001 From: William Batista Date: Fri, 26 Jul 2024 10:29:07 -0400 Subject: [PATCH 3/8] Switch to fly-go v0.1.19 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 5d1ede4767..9c82cc674a 100644 --- a/go.mod +++ b/go.mod @@ -71,7 +71,7 @@ require ( github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - github.com/superfly/fly-go v0.1.19-0.20240716210409-e3d434ec3f18 + github.com/superfly/fly-go v0.1.19 github.com/superfly/graphql v0.2.4 github.com/superfly/lfsc-go v0.1.1 github.com/superfly/macaroon v0.2.14-0.20240702184853-b8ac52a1fc77 From 50504982362ba60f20af9aa2c7a7defc354767db Mon Sep 17 00:00:00 2001 From: William Batista Date: Fri, 26 Jul 2024 10:40:02 -0400 Subject: [PATCH 4/8] added a preflight test for building w. depot --- test/preflight/fly_deploy_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test/preflight/fly_deploy_test.go b/test/preflight/fly_deploy_test.go index 79be98b522..a1d0400d4e 100644 --- a/test/preflight/fly_deploy_test.go +++ b/test/preflight/fly_deploy_test.go @@ -196,6 +196,35 @@ func TestFlyDeployNodeAppWithRemoteBuilderWithoutWireguard(t *testing.T) { require.Contains(t, string(body), fmt.Sprintf("Hello, World! %s", f.ID())) } +func TestFlyDeployNodeAppWithDepotRemoteBuilder(t *testing.T) { + f := testlib.NewTestEnvFromEnv(t) + err := copyFixtureIntoWorkDir(f.WorkDir(), "deploy-node", []string{}) + require.NoError(t, err) + + flyTomlPath := fmt.Sprintf("%s/fly.toml", f.WorkDir()) + + appName := f.CreateRandomAppMachines() + require.NotEmpty(t, appName) + + err = testlib.OverwriteConfig(flyTomlPath, map[string]any{ + "app": appName, + "region": f.PrimaryRegion(), + "env": map[string]string{ + "TEST_ID": f.ID(), + }, + }) + require.NoError(t, err) + + f.Fly("deploy --depot --ha=false") + + f.Fly("deploy --depot --strategy immediate --ha=false") + + body, err := testlib.RunHealthCheck(fmt.Sprintf("https://%s.fly.dev", appName)) + require.NoError(t, err) + + require.Contains(t, string(body), fmt.Sprintf("Hello, World! %s", f.ID())) +} + func TestFlyDeployBasicNodeWithWGEnabled(t *testing.T) { f := testlib.NewTestEnvFromEnv(t) From 9f0938f400feaae84defcc81b31fb940bdaafa8f Mon Sep 17 00:00:00 2001 From: William Batista Date: Fri, 26 Jul 2024 12:22:55 -0400 Subject: [PATCH 5/8] make sure to use depot on CI when specified --- internal/build/imgsrc/docker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/build/imgsrc/docker.go b/internal/build/imgsrc/docker.go index 08ffbb7355..bf43d0c9fe 100644 --- a/internal/build/imgsrc/docker.go +++ b/internal/build/imgsrc/docker.go @@ -122,7 +122,7 @@ func NewDockerDaemonType(allowLocal, allowRemote, prefersLocal, useDepot, useNix if useNixpacks { daemonType = daemonType | DockerDaemonTypeNixpacks } - if prefersLocal { + if prefersLocal && !useDepot { daemonType = daemonType | DockerDaemonTypePrefersLocal } return daemonType From 76e6236ae2791bbbe67bec6de60ee2d103ad5f24 Mon Sep 17 00:00:00 2001 From: William Batista Date: Tue, 30 Jul 2024 11:52:29 -0400 Subject: [PATCH 6/8] allow user to specify remote builder region --- internal/build/imgsrc/depot.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/internal/build/imgsrc/depot.go b/internal/build/imgsrc/depot.go index 92b621a677..f0c615b7a4 100644 --- a/internal/build/imgsrc/depot.go +++ b/internal/build/imgsrc/depot.go @@ -111,9 +111,14 @@ func depotBuild(ctx context.Context, streams *iostreams.IOStreams, opts ImageOpt } apiClient := flyutil.ClientFromContext(ctx) + region := os.Getenv("FLY_REMOTE_BUILDER_REGION") + if region != "" { + region = "fly-" + region + } + buildInfo, err := apiClient.EnsureDepotRemoteBuilder(ctx, &fly.EnsureDepotRemoteBuilderInput{ AppName: &opts.AppName, - Region: fly.StringPointer("us-east-1"), + Region: ®ion, }) if err != nil { streams.StopProgressIndicator() From 6bbda284be4d20461ca1a25c8cbbe74218a3a207 Mon Sep 17 00:00:00 2001 From: William Batista Date: Tue, 30 Jul 2024 12:12:34 -0400 Subject: [PATCH 7/8] add a test for CI that specifies --depot --- internal/build/imgsrc/docker_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/build/imgsrc/docker_test.go b/internal/build/imgsrc/docker_test.go index 01ff1b1193..6f115e6560 100644 --- a/internal/build/imgsrc/docker_test.go +++ b/internal/build/imgsrc/docker_test.go @@ -23,6 +23,7 @@ func TestAllowedDockerDaemonMode(t *testing.T) { {true, false, true, false, false, DockerDaemonTypeNone | DockerDaemonTypeLocal | DockerDaemonTypePrefersLocal}, {true, true, false, false, false, DockerDaemonTypeNone | DockerDaemonTypeLocal | DockerDaemonTypeRemote}, {true, true, true, false, false, DockerDaemonTypeNone | DockerDaemonTypeLocal | DockerDaemonTypeRemote | DockerDaemonTypePrefersLocal}, + {true, true, false, true, false, DockerDaemonTypeNone | DockerDaemonTypeDepot | DockerDaemonTypeRemote | DockerDaemonTypeLocal}, } for _, test := range tests { From 331280a5dad943df2f187cb0cd5296109731d631 Mon Sep 17 00:00:00 2001 From: William Batista Date: Tue, 30 Jul 2024 14:25:12 -0400 Subject: [PATCH 8/8] Make the code a little nicer to read --- internal/build/imgsrc/depot.go | 84 +++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/internal/build/imgsrc/depot.go b/internal/build/imgsrc/depot.go index f0c615b7a4..20ea02218b 100644 --- a/internal/build/imgsrc/depot.go +++ b/internal/build/imgsrc/depot.go @@ -45,15 +45,15 @@ func (d *DepotBuilder) Run(ctx context.Context, _ *dockerClientFactory, streams var dockerfile string - if opts.DockerfilePath != "" { - if !helpers.FileExists(opts.DockerfilePath) { - build.BuildFinish() - err := fmt.Errorf("dockerfile '%s' not found", opts.DockerfilePath) - tracing.RecordError(span, err, "failed to find dockerfile") - return nil, "", err - } + switch { + case opts.DockerfilePath != "" && !helpers.FileExists(opts.DockerfilePath): + build.BuildFinish() + err := fmt.Errorf("dockerfile '%s' not found", opts.DockerfilePath) + tracing.RecordError(span, err, "failed to find dockerfile") + return nil, "", err + case opts.DockerfilePath != "": dockerfile = opts.DockerfilePath - } else { + default: dockerfile = ResolveDockerfile(opts.WorkingDir) } @@ -110,41 +110,12 @@ func depotBuild(ctx context.Context, streams *iostreams.IOStreams, opts ImageOpt } } - apiClient := flyutil.ClientFromContext(ctx) - region := os.Getenv("FLY_REMOTE_BUILDER_REGION") - if region != "" { - region = "fly-" + region - } - - buildInfo, err := apiClient.EnsureDepotRemoteBuilder(ctx, &fly.EnsureDepotRemoteBuilderInput{ - AppName: &opts.AppName, - Region: ®ion, - }) - if err != nil { - streams.StopProgressIndicator() - buildState.BuilderInitFinish() - return nil, err - } - - build, err := depotbuild.FromExistingBuild(ctx, *buildInfo.EnsureDepotRemoteBuilder.BuildId, *buildInfo.EnsureDepotRemoteBuilder.BuildToken) - if err != nil { - buildState.BuilderInitFinish() - return nil, err - } - - // Set the buildErr to any error that represents the build failing. - var buildErr error - defer build.Finish(buildErr) - - var buildkit *depotmachine.Machine - buildkit, buildErr = depotmachine.Acquire(ctx, build.ID, build.Token, "amd64") + buildkit, build, buildErr := initBuilder(ctx, buildState, opts.AppName, streams) if buildErr != nil { streams.StopProgressIndicator() - buildState.BuilderInitFinish() return nil, buildErr } defer buildkit.Release() - buildState.BuilderInitFinish() connectCtx, cancelConnect := context.WithTimeout(ctx, 5*time.Minute) defer cancelConnect() @@ -176,6 +147,43 @@ func depotBuild(ctx context.Context, streams *iostreams.IOStreams, opts ImageOpt return newDeploymentImage(res, opts.Tag) } +func initBuilder(ctx context.Context, buildState *build, appName string, streams *iostreams.IOStreams) (*depotmachine.Machine, *depotbuild.Build, error) { + apiClient := flyutil.ClientFromContext(ctx) + region := os.Getenv("FLY_REMOTE_BUILDER_REGION") + if region != "" { + region = "fly-" + region + } + + defer buildState.BuilderInitFinish() + + buildInfo, err := apiClient.EnsureDepotRemoteBuilder(ctx, &fly.EnsureDepotRemoteBuilderInput{ + AppName: &appName, + Region: ®ion, + }) + if err != nil { + streams.StopProgressIndicator() + return nil, nil, err + } + + build, err := depotbuild.FromExistingBuild(ctx, *buildInfo.EnsureDepotRemoteBuilder.BuildId, *buildInfo.EnsureDepotRemoteBuilder.BuildToken) + if err != nil { + return nil, nil, err + } + + // Set the buildErr to any error that represents the build failing. + var buildErr error + defer build.Finish(buildErr) + + var buildkit *depotmachine.Machine + buildkit, buildErr = depotmachine.Acquire(ctx, build.ID, build.Token, "amd64") + if buildErr != nil { + streams.StopProgressIndicator() + return nil, nil, buildErr + } + + return buildkit, &build, err +} + func buildImage(ctx context.Context, buildkitClient *client.Client, opts ImageOptions, dockerfilePath string) (*client.SolveResponse, error) { var ( res *client.SolveResponse