Skip to content

Commit

Permalink
Merge pull request #108 from depot/feat/proxy-image-via-api
Browse files Browse the repository at this point in the history
feat(load): allow proxy image to update
  • Loading branch information
goller authored Apr 27, 2023
2 parents 6eca91f + 1a76d6b commit 24f8482
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 226 deletions.
2 changes: 2 additions & 0 deletions pkg/buildx/commands/bake.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ func RunBake(dockerCli command.Cli, targets []string, in BakeOptions) (err error
buildOpts,
load.DepotLoadOptions{
UseLocalRegistry: in.DepotOptions.useLocalRegistry,
ProxyImage: in.DepotOptions.proxyImage,
Project: in.DepotOptions.project,
BuildID: in.DepotOptions.buildID,
IsBake: true,
Expand Down Expand Up @@ -298,6 +299,7 @@ func BakeCmd(dockerCli command.Cli) *cobra.Command {
options.buildID = build.ID
options.token = build.Token
options.useLocalRegistry = build.UseLocalRegistry
options.proxyImage = build.ProxyImage

if options.allowNoOutput {
_ = os.Setenv("BUILDX_NO_DEFAULT_LOAD", "1")
Expand Down
3 changes: 3 additions & 0 deletions pkg/buildx/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ type DepotOptions struct {
buildID string
buildPlatform string
useLocalRegistry bool
proxyImage string
allowNoOutput bool
builderOptions []builder.Option
}
Expand Down Expand Up @@ -343,6 +344,7 @@ func buildTargets(ctx context.Context, dockerCli command.Cli, nodes []builder.No
opts,
load.DepotLoadOptions{
UseLocalRegistry: depotOpts.useLocalRegistry,
ProxyImage: depotOpts.proxyImage,
Project: depotOpts.project,
BuildID: depotOpts.buildID,
IsBake: false,
Expand Down Expand Up @@ -570,6 +572,7 @@ func BuildCmd(dockerCli command.Cli) *cobra.Command {
options.buildID = build.ID
options.token = build.Token
options.useLocalRegistry = build.UseLocalRegistry
options.proxyImage = build.ProxyImage

if options.allowNoOutput {
_ = os.Setenv("BUILDX_NO_DEFAULT_LOAD", "1")
Expand Down
12 changes: 12 additions & 0 deletions pkg/helpers/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/bufbuild/connect-go"
depotapi "github.com/depot/cli/pkg/api"
"github.com/depot/cli/pkg/load"
"github.com/depot/cli/pkg/profiler"
cliv1 "github.com/depot/cli/pkg/proto/depot/cli/v1"
"github.com/moby/buildkit/util/grpcerrors"
Expand All @@ -18,6 +19,7 @@ type Build struct {
ID string
Token string
UseLocalRegistry bool
ProxyImage string
Finish func(error)
}

Expand Down Expand Up @@ -45,6 +47,16 @@ func BeginBuild(ctx context.Context, project string, token string) (build Build,
if os.Getenv("DEPOT_USE_LOCAL_REGISTRY") != "" {
build.UseLocalRegistry = true
}

if b.Msg.GetRegistry() != nil {
build.ProxyImage = b.Msg.GetRegistry().ProxyImage
}
if proxyImage := os.Getenv("DEPOT_PROXY_IMAGE"); proxyImage != "" {
build.ProxyImage = proxyImage
}
if build.ProxyImage == "" {
build.ProxyImage = load.DefaultProxyImageName
}
}

profiler.StartProfiler(build.ID, profilerToken)
Expand Down
11 changes: 7 additions & 4 deletions pkg/load/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
// DepotLoadOptions are options to load images from the depot hosted registry.
type DepotLoadOptions struct {
UseLocalRegistry bool // Backwards-compat with buildx that uses tar loads.
ProxyImage string // Image to use as a proxy for the depot registry.
Project string // Depot project name; used to tag images.
BuildID string // Depot build ID; used to tag images.
IsBake bool // If run from bake, we add the bake target to the image tag.
Expand All @@ -20,8 +21,9 @@ type DepotLoadOptions struct {

// Options to download from the Depot hosted registry and tag the image with the user provide tag.
type PullOptions struct {
UserTags []string // Tags the user wishes the image to have.
Quiet bool // No logs plz
UserTags []string // Tags the user wishes the image to have.
Quiet bool // No logs plz
ProxyImage string // Image to use as a proxy for the depot registry.
}

// WithDepotImagePull updates buildOpts to push to the depot user's personal registry.
Expand Down Expand Up @@ -82,8 +84,9 @@ func WithDepotImagePull(buildOpts map[string]build.Options, loadOpts DepotLoadOp
}

pullOpt := PullOptions{
UserTags: userTags,
Quiet: loadOpts.ProgressMode == progress.PrinterModeQuiet,
UserTags: userTags,
Quiet: loadOpts.ProgressMode == progress.PrinterModeQuiet,
ProxyImage: loadOpts.ProxyImage,
}
toPull[target] = pullOpt
}
Expand Down
54 changes: 31 additions & 23 deletions pkg/load/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,21 @@ func DepotFastLoad(ctx context.Context, dockerapi docker.APIClient, resp []depot
}

architecture := nodeRes.Node.DriverOpts["platform"]
manifestConfig, err := decodeNodeResponse(architecture, nodeRes)
manifest, config, err := decodeNodeResponse(architecture, nodeRes)
if err != nil {
return err
}
pullOpt := pullOpts[buildRes.Name]
proxyOpts := &ProxyOpts{
RawManifest: manifest,
RawConfig: config,
ProxyImage: pullOpt.ProxyImage,
}

// Start the depot CLI hosted registry and socat proxy.
var registry LocalRegistryProxy
err = progress.Wrap("preparing to load", pw.Write, func(logger progress.SubLogger) error {
registry, err = NewLocalRegistryProxy(ctx, manifestConfig, dockerapi, contentClient, logger)
registry, err = NewLocalRegistryProxy(ctx, proxyOpts, dockerapi, contentClient, logger)
return err
})
if err != nil {
Expand All @@ -62,7 +68,6 @@ func DepotFastLoad(ctx context.Context, dockerapi docker.APIClient, resp []depot
}()

// Pull the image and relabel it with the user specified tags.
pullOpt := pullOpts[buildRes.Name]
err = PullImages(ctx, dockerapi, registry.ImageToPull, pullOpt, pw)
if err != nil {
return fmt.Errorf("failed to pull image: %w", err)
Expand All @@ -88,27 +93,28 @@ func chooseNodeResponse(nodeResponses []depotbuild.DepotNodeResponse) depotbuild
return nodeResponses[nodeIdx]
}

type ManifestConfig struct {
type ProxyOpts struct {
RawManifest []byte
RawConfig []byte
ProxyImage string
}

// We encode the image manifest and image config within the buildkitd Solve response
// because the content may be GCed by the time this load occurs.
func decodeNodeResponse(architecture string, nodeRes depotbuild.DepotNodeResponse) (*ManifestConfig, error) {
func decodeNodeResponse(architecture string, nodeRes depotbuild.DepotNodeResponse) (rawManifest, rawConfig []byte, err error) {
encodedDesc, ok := nodeRes.SolveResponse.ExporterResponse[exptypes.ExporterImageDescriptorKey]
if !ok {
return nil, errors.New("missing image descriptor")
return nil, nil, errors.New("missing image descriptor")
}

jsonImageDesc, err := base64.StdEncoding.DecodeString(encodedDesc)
if err != nil {
return nil, fmt.Errorf("invalid image descriptor: %w", err)
return nil, nil, fmt.Errorf("invalid image descriptor: %w", err)
}

var imageDesc ocispecs.Descriptor
if err := json.Unmarshal(jsonImageDesc, &imageDesc); err != nil {
return nil, fmt.Errorf("invalid image descriptor json: %w", err)
return nil, nil, fmt.Errorf("invalid image descriptor json: %w", err)
}

var imageManifest ocispecs.Descriptor = imageDesc
Expand All @@ -121,37 +127,39 @@ func decodeNodeResponse(architecture string, nodeRes depotbuild.DepotNodeRespons
if ok {
var index ocispecs.Index
if err := json.Unmarshal([]byte(encodedIndex), &index); err != nil {
return nil, fmt.Errorf("invalid image index json: %w", err)
return nil, nil, fmt.Errorf("invalid image index json: %w", err)
}

imageManifest, err = chooseBestImageManifest(architecture, &index)
if err != nil {
return nil, err
return nil, nil, err
}
}
}

rawManifest, ok := imageManifest.Annotations["depot.containerimage.manifest"]
m, ok := imageManifest.Annotations["depot.containerimage.manifest"]
if !ok {
return nil, errors.New("missing image manifest")
return nil, nil, errors.New("missing image manifest")
}
rawManifest = []byte(m)

rawConfig, ok := imageManifest.Annotations["depot.containerimage.config"]
c, ok := imageManifest.Annotations["depot.containerimage.config"]
if !ok {
return nil, errors.New("missing image config")
return nil, nil, errors.New("missing image config")
}
rawConfig = []byte(c)

// Decoding both the manifest and config to ensure they are valid.
var manifest ocispecs.Manifest
if err := json.Unmarshal([]byte(rawManifest), &manifest); err != nil {
return nil, fmt.Errorf("invalid image manifest json: %w", err)
if err := json.Unmarshal(rawManifest, &manifest); err != nil {
return nil, nil, fmt.Errorf("invalid image manifest json: %w", err)
}

var ii ocispecs.Image
if err := json.Unmarshal([]byte(rawConfig), &ii); err != nil {
return nil, fmt.Errorf("invalid image config json: %w", err)
var image ocispecs.Image
if err := json.Unmarshal(rawConfig, &image); err != nil {
return nil, nil, fmt.Errorf("invalid image config json: %w", err)
}
return &ManifestConfig{RawManifest: []byte(rawManifest), RawConfig: []byte(rawConfig)}, nil
return rawManifest, rawConfig, nil
}

func contentClient(ctx context.Context, nodeResponse depotbuild.DepotNodeResponse) (contentapi.ContentClient, error) {
Expand Down Expand Up @@ -192,8 +200,8 @@ type LocalRegistryProxy struct {
// by running a proxy container with socat forwarding to the running server.
//
// The running server and proxy container will be cleaned-up when Close() is called.
func NewLocalRegistryProxy(ctx context.Context, manifestConfig *ManifestConfig, dockerapi docker.APIClient, contentClient contentapi.ContentClient, logger progress.SubLogger) (LocalRegistryProxy, error) {
registryHandler := NewRegistry(contentClient, manifestConfig.RawConfig, manifestConfig.RawManifest, logger)
func NewLocalRegistryProxy(ctx context.Context, opts *ProxyOpts, dockerapi docker.APIClient, contentClient contentapi.ContentClient, logger progress.SubLogger) (LocalRegistryProxy, error) {
registryHandler := NewRegistry(contentClient, opts.RawConfig, opts.RawManifest, logger)

ctx, cancel := context.WithCancel(ctx)
registryPort, err := serveRegistry(ctx, registryHandler)
Expand All @@ -202,7 +210,7 @@ func NewLocalRegistryProxy(ctx context.Context, manifestConfig *ManifestConfig,
return LocalRegistryProxy{}, err
}

proxyContainerID, proxyExposedPort, err := RunProxyImage(ctx, dockerapi, registryPort)
proxyContainerID, proxyExposedPort, err := RunProxyImage(ctx, dockerapi, opts.ProxyImage, registryPort)
if err != nil {
cancel()
return LocalRegistryProxy{}, err
Expand Down
8 changes: 4 additions & 4 deletions pkg/load/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ import (
"github.com/docker/go-connections/nat"
)

const ProxyImageName = "ghcr.io/depot/helper:1"
const DefaultProxyImageName = "ghcr.io/depot/helper:1"

// Runs a proxy container via the docker API so that the docker daemon can pull from the local depot registry.
// This is specifically to handle docker for desktop running in a VM restricting access to the host network.
func RunProxyImage(ctx context.Context, dockerapi docker.APIClient, registryPort int) (string, string, error) {
if err := PullProxyImage(ctx, dockerapi, ProxyImageName); err != nil {
func RunProxyImage(ctx context.Context, dockerapi docker.APIClient, proxyImage string, registryPort int) (string, string, error) {
if err := PullProxyImage(ctx, dockerapi, proxyImage); err != nil {
return "", "", err
}

resp, err := dockerapi.ContainerCreate(ctx,
&container.Config{
Image: ProxyImageName,
Image: proxyImage,
ExposedPorts: nat.PortSet{
nat.Port("8888/tcp"): struct{}{},
},
Expand Down
Loading

0 comments on commit 24f8482

Please sign in to comment.