diff --git a/client/llb/imagemetaresolver/resolver.go b/client/llb/imagemetaresolver/resolver.go index d7d0d7460fee..09d4aec1b1a1 100644 --- a/client/llb/imagemetaresolver/resolver.go +++ b/client/llb/imagemetaresolver/resolver.go @@ -12,6 +12,7 @@ import ( "github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/imageutil" digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) var defaultImageMetaResolver llb.ImageMetaResolver @@ -22,12 +23,12 @@ var WithDefault = llb.ImageOptionFunc(func(ii *llb.ImageInfo) { }) type imageMetaResolverOpts struct { - platform string + platform *specs.Platform } type ImageMetaResolverOpt func(o *imageMetaResolverOpts) -func WithPlatform(p string) ImageMetaResolverOpt { +func WithDefaultPlatform(p *specs.Platform) ImageMetaResolverOpt { return func(o *imageMetaResolverOpts) { o.platform = p } @@ -59,7 +60,7 @@ func Default() llb.ImageMetaResolver { type imageMetaResolver struct { resolver remotes.Resolver buffer contentutil.Buffer - platform string + platform *specs.Platform locker *locker.Locker cache map[string]resolveResult } @@ -69,7 +70,7 @@ type resolveResult struct { dgst digest.Digest } -func (imr *imageMetaResolver) ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) { +func (imr *imageMetaResolver) ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) { imr.locker.Lock(ref) defer imr.locker.Unlock(ref) @@ -77,7 +78,11 @@ func (imr *imageMetaResolver) ResolveImageConfig(ctx context.Context, ref string return res.dgst, res.config, nil } - dgst, config, err := imageutil.Config(ctx, ref, imr.resolver, imr.buffer, imr.platform) + if platform == nil { + platform = imr.platform + } + + dgst, config, err := imageutil.Config(ctx, ref, imr.resolver, imr.buffer, platform) if err != nil { return "", nil, err } diff --git a/client/llb/resolver.go b/client/llb/resolver.go index bac738c96732..b539b1159e83 100644 --- a/client/llb/resolver.go +++ b/client/llb/resolver.go @@ -4,6 +4,7 @@ import ( "context" digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) func WithMetaResolver(mr ImageMetaResolver) ImageOption { @@ -13,5 +14,5 @@ func WithMetaResolver(mr ImageMetaResolver) ImageOption { } type ImageMetaResolver interface { - ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) + ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) } diff --git a/client/llb/source.go b/client/llb/source.go index 5ce765087bb4..f0f296849984 100644 --- a/client/llb/source.go +++ b/client/llb/source.go @@ -87,7 +87,7 @@ func Image(ref string, opts ...ImageOption) State { src.err = err } if info.metaResolver != nil { - _, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref) + _, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, info.Constraints.Platform) if err != nil { src.err = err } else { diff --git a/client/llb/state.go b/client/llb/state.go index 0e3c5c92e61e..f9e2138e4256 100644 --- a/client/llb/state.go +++ b/client/llb/state.go @@ -36,8 +36,9 @@ func NewState(o Output) State { } type State struct { - out Output - ctx context.Context + out Output + ctx context.Context + opts []ConstraintsOpt } func (s State) ensurePlatform() State { @@ -62,6 +63,11 @@ func (s State) Value(k interface{}) interface{} { return s.ctx.Value(k) } +func (s State) SetMarhalDefaults(co ...ConstraintsOpt) State { + s.opts = co + return s +} + func (s State) Marshal(co ...ConstraintsOpt) (*Definition, error) { def := &Definition{ Metadata: make(map[digest.Digest]pb.OpMetadata, 0), @@ -74,7 +80,7 @@ func (s State) Marshal(co ...ConstraintsOpt) (*Definition, error) { c := &Constraints{ Platform: &defaultPlatform, } - for _, o := range co { + for _, o := range append(s.opts, co...) { o.SetConstraintsOption(c) } diff --git a/frontend/dockerfile/builder/build.go b/frontend/dockerfile/builder/build.go index 0bf4616d5d71..c618f38402a3 100644 --- a/frontend/dockerfile/builder/build.go +++ b/frontend/dockerfile/builder/build.go @@ -8,10 +8,12 @@ import ( "regexp" "strings" + "github.com/containerd/containerd/platforms" "github.com/docker/docker/builder/dockerignore" "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/frontend/dockerfile/dockerfile2llb" "github.com/moby/buildkit/frontend/gateway/client" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) @@ -28,6 +30,7 @@ const ( buildArgPrefix = "build-arg:" labelPrefix = "label:" keyNoCache = "no-cache" + keyTargetPlatform = "platform" ) var httpPrefix = regexp.MustCompile("^https?://") @@ -36,6 +39,17 @@ var gitUrlPathWithFragmentSuffix = regexp.MustCompile("\\.git(?:#.+)?$") func Build(ctx context.Context, c client.Client) error { opts := c.Opts() + // TODO: read buildPlatforms from workers + buildPlatforms := []specs.Platform{platforms.DefaultSpec()} + targetPlatform := platforms.DefaultSpec() + if v := opts[keyTargetPlatform]; v != "" { + var err error + targetPlatform, err = platforms.Parse(v) + if err != nil { + return errors.Wrapf(err, "failed to parse target platform %s", v) + } + } + filename := opts[keyFilename] if filename == "" { filename = defaultDockerfileName @@ -166,14 +180,16 @@ func Build(ctx context.Context, c client.Client) error { } st, img, err := dockerfile2llb.Dockerfile2LLB(ctx, dtDockerfile, dockerfile2llb.ConvertOpt{ - Target: opts[keyTarget], - MetaResolver: c, - BuildArgs: filter(opts, buildArgPrefix), - Labels: filter(opts, labelPrefix), - SessionID: c.SessionID(), - BuildContext: buildContext, - Excludes: excludes, - IgnoreCache: ignoreCache, + Target: opts[keyTarget], + MetaResolver: c, + BuildArgs: filter(opts, buildArgPrefix), + Labels: filter(opts, labelPrefix), + SessionID: c.SessionID(), + BuildContext: buildContext, + Excludes: excludes, + IgnoreCache: ignoreCache, + TargetPlatform: &targetPlatform, + BuildPlatforms: buildPlatforms, }) if err != nil { diff --git a/frontend/dockerfile/dockerfile2llb/convert.go b/frontend/dockerfile/dockerfile2llb/convert.go index e2913749ff6d..653031a9c139 100644 --- a/frontend/dockerfile/dockerfile2llb/convert.go +++ b/frontend/dockerfile/dockerfile2llb/convert.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" + "github.com/containerd/containerd/platforms" "github.com/docker/distribution/reference" "github.com/docker/docker/pkg/signal" "github.com/docker/go-connections/nat" @@ -20,7 +21,7 @@ import ( "github.com/moby/buildkit/frontend/dockerfile/instructions" "github.com/moby/buildkit/frontend/dockerfile/parser" "github.com/moby/buildkit/frontend/dockerfile/shell" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "golang.org/x/sync/errgroup" ) @@ -46,6 +47,8 @@ type ConvertOpt struct { IgnoreCache []string // CacheIDNamespace scopes the IDs for different cache mounts CacheIDNamespace string + TargetPlatform *specs.Platform + BuildPlatforms []specs.Platform } func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, *Image, error) { @@ -53,6 +56,18 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, return nil, nil, errors.Errorf("the Dockerfile cannot be empty") } + if opt.TargetPlatform != nil && opt.BuildPlatforms == nil { + opt.BuildPlatforms = []specs.Platform{*opt.TargetPlatform} + } + if len(opt.BuildPlatforms) == 0 { + opt.BuildPlatforms = []specs.Platform{platforms.DefaultSpec()} + } + implicitTargetPlatform := false + if opt.TargetPlatform == nil { + implicitTargetPlatform = true + opt.TargetPlatform = &opt.BuildPlatforms[0] + } + dockerfile, err := parser.Parse(bytes.NewReader(dt)) if err != nil { return nil, nil, err @@ -92,6 +107,20 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, deps: make(map[*dispatchState]struct{}), ctxPaths: make(map[string]struct{}), } + + if v := st.Platform; v != "" { + v, err := shlex.ProcessWord(v, toEnvList(metaArgs, nil)) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to process arguments for platform %s", v) + } + + p, err := platforms.Parse(v) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to parse platform %s", v) + } + ds.platform = &p + } + if d, ok := dispatchStatesByName[st.BaseName]; ok { ds.base = d } @@ -150,7 +179,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, if d.base == nil { if d.stage.BaseName == emptyImageName { d.state = llb.Scratch() - d.image = emptyImage() + d.image = emptyImage(*opt.TargetPlatform) continue } func(i int, d *dispatchState) { @@ -159,16 +188,25 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, if err != nil { return err } + platform := d.platform + if platform == nil { + platform = opt.TargetPlatform + } d.stage.BaseName = reference.TagNameOnly(ref).String() var isScratch bool if metaResolver != nil && reachable { - dgst, dt, err := metaResolver.ResolveImageConfig(ctx, d.stage.BaseName) + dgst, dt, err := metaResolver.ResolveImageConfig(ctx, d.stage.BaseName, platform) if err == nil { // handle the error while builder is actually running var img Image if err := json.Unmarshal(dt, &img); err != nil { return err } img.Created = nil + // if there is no explicit target platform, try to match based on image config + if d.platform == nil && implicitTargetPlatform { + p := autoDetectPlatform(img, *platform, opt.BuildPlatforms) + platform = &p + } d.image = img if dgst != "" { ref, err = reference.WithDigest(ref, dgst) @@ -186,7 +224,7 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, if isScratch { d.state = llb.Scratch() } else { - d.state = llb.Image(d.stage.BaseName, dfCmd(d.stage.SourceCode)) + d.state = llb.Image(d.stage.BaseName, dfCmd(d.stage.SourceCode), llb.Platform(*platform)) } return nil }) @@ -242,6 +280,8 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, buildContext: llb.NewState(buildContext), proxyEnv: proxyEnv, cacheIDNamespace: opt.CacheIDNamespace, + buildPlatforms: opt.BuildPlatforms, + targetPlatform: *opt.TargetPlatform, } if err = dispatchOnBuild(d, d.image.Config.OnBuild, opt); err != nil { @@ -280,7 +320,14 @@ func Dockerfile2LLB(ctx context.Context, dt []byte, opt ConvertOpt) (*llb.State, } buildContext.Output = bc.Output() - return &target.state, &target.image, nil + st := target.state.SetMarhalDefaults(llb.Platform(*opt.TargetPlatform)) + + if !implicitTargetPlatform { + target.image.OS = opt.TargetPlatform.OS + target.image.Architecture = opt.TargetPlatform.Architecture + } + + return &st, &target.image, nil } func toCommand(ic instructions.Command, dispatchStatesByName map[string]*dispatchState, allDispatchStates []*dispatchState) (command, error) { @@ -325,6 +372,8 @@ type dispatchOpt struct { buildContext llb.State proxyEnv *llb.ProxyEnv cacheIDNamespace string + targetPlatform specs.Platform + buildPlatforms []specs.Platform } func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { @@ -348,7 +397,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { case *instructions.WorkdirCommand: err = dispatchWorkdir(d, c, true) case *instructions.AddCommand: - err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, "") + err = dispatchCopy(d, c.SourcesAndDest, opt.buildContext, true, c, "", opt) if err == nil { for _, src := range c.Sources() { d.ctxPaths[path.Join("/", filepath.ToSlash(src))] = struct{}{} @@ -381,7 +430,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { if len(cmd.sources) != 0 { l = cmd.sources[0].state } - err = dispatchCopy(d, c.SourcesAndDest, l, false, c, c.Chown) + err = dispatchCopy(d, c.SourcesAndDest, l, false, c, c.Chown, opt) if err == nil && len(cmd.sources) == 0 { for _, src := range c.Sources() { d.ctxPaths[path.Join("/", filepath.ToSlash(src))] = struct{}{} @@ -395,6 +444,7 @@ func dispatch(d *dispatchState, cmd command, opt dispatchOpt) error { type dispatchState struct { state llb.State image Image + platform *specs.Platform stage instructions.Stage base *dispatchState deps map[*dispatchState]struct{} @@ -490,9 +540,9 @@ func dispatchWorkdir(d *dispatchState, c *instructions.WorkdirCommand, commit bo return nil } -func dispatchCopy(d *dispatchState, c instructions.SourcesAndDest, sourceState llb.State, isAddCommand bool, cmdToPrint interface{}, chown string) error { +func dispatchCopy(d *dispatchState, c instructions.SourcesAndDest, sourceState llb.State, isAddCommand bool, cmdToPrint interface{}, chown string, opt dispatchOpt) error { // TODO: this should use CopyOp instead. Current implementation is inefficient - img := llb.Image(CopyImage) + img := llb.Image(CopyImage, llb.Platform(opt.buildPlatforms[0])) dest := path.Join(".", pathRelativeToWorkingDir(d.state, c.Dest())) if c.Dest() == "." || c.Dest()[len(c.Dest())-1] == filepath.Separator { @@ -558,12 +608,12 @@ func dispatchCopy(d *dispatchState, c instructions.SourcesAndDest, sourceState l args = append(args[:1], append([]string{"--unpack"}, args[1:]...)...) } - opt := []llb.RunOption{llb.Args(args), llb.Dir("/dest"), llb.ReadonlyRootFS(), dfCmd(cmdToPrint)} + runOpt := []llb.RunOption{llb.Args(args), llb.Dir("/dest"), llb.ReadonlyRootFS(), dfCmd(cmdToPrint)} if d.ignoreCache { - opt = append(opt, llb.IgnoreCache) + runOpt = append(runOpt, llb.IgnoreCache) } - run := img.Run(append(opt, mounts...)...) - d.state = run.AddMount("/dest", d.state) + run := img.Run(append(runOpt, mounts...)...) + d.state = run.AddMount("/dest", d.state).Platform(opt.targetPlatform) return commitToHistory(&d.image, commitMessage.String(), true, &d.state) } @@ -802,7 +852,7 @@ func commitToHistory(img *Image, msg string, withLayer bool, st *llb.State) erro msg += " # buildkit" } - img.History = append(img.History, ocispec.History{ + img.History = append(img.History, specs.History{ CreatedBy: msg, Comment: historyComment, EmptyLayer: !withLayer, @@ -934,3 +984,17 @@ func withShell(img Image, args []string) []string { } return append(shell, strings.Join(args, " ")) } + +func autoDetectPlatform(img Image, target specs.Platform, supported []specs.Platform) specs.Platform { + os := img.OS + arch := img.Architecture + if target.OS == os && target.Architecture == arch { + return target + } + for _, p := range supported { + if p.OS == os && p.Architecture == arch { + return p + } + } + return target +} diff --git a/frontend/dockerfile/dockerfile2llb/image.go b/frontend/dockerfile/dockerfile2llb/image.go index 59c464a63dd1..6085d1e4d20a 100644 --- a/frontend/dockerfile/dockerfile2llb/image.go +++ b/frontend/dockerfile/dockerfile2llb/image.go @@ -1,12 +1,11 @@ package dockerfile2llb import ( - "runtime" "time" "github.com/docker/docker/api/types/strslice" "github.com/moby/buildkit/util/system" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) // HealthConfig holds configuration settings for the HEALTHCHECK feature. @@ -31,7 +30,7 @@ type HealthConfig struct { } type ImageConfig struct { - ocispec.ImageConfig + specs.ImageConfig Healthcheck *HealthConfig `json:",omitempty"` // Healthcheck describes how to check the container is healthy ArgsEscaped bool `json:",omitempty"` // True if command is already escaped (Windows specific) @@ -46,7 +45,7 @@ type ImageConfig struct { // Image is the JSON structure which describes some basic information about the image. // This provides the `application/vnd.oci.image.config.v1+json` mediatype when marshalled to JSON. type Image struct { - ocispec.Image + specs.Image // Config defines the execution parameters which should be used as a base when running a container using the image. Config ImageConfig `json:"config,omitempty"` @@ -61,11 +60,11 @@ func clone(src Image) Image { return img } -func emptyImage() Image { +func emptyImage(platform specs.Platform) Image { img := Image{ - Image: ocispec.Image{ - Architecture: runtime.GOARCH, - OS: runtime.GOOS, + Image: specs.Image{ + Architecture: platform.Architecture, + OS: platform.OS, }, } img.RootFS.Type = "layers" diff --git a/frontend/dockerfile/instructions/commands.go b/frontend/dockerfile/instructions/commands.go index 3e4617ac918d..b96010f0b1bd 100644 --- a/frontend/dockerfile/instructions/commands.go +++ b/frontend/dockerfile/instructions/commands.go @@ -6,7 +6,6 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/strslice" - specs "github.com/opencontainers/image-spec/specs-go/v1" ) // KeyValuePair represent an arbitrary named value (useful in slice instead of map[string] string to preserve ordering) @@ -382,7 +381,7 @@ type Stage struct { Commands []Command BaseName string SourceCode string - Platform specs.Platform + Platform string } // AddCommand to the stage diff --git a/frontend/dockerfile/instructions/parse.go b/frontend/dockerfile/instructions/parse.go index 8c8199e1f725..d84bb43c23b4 100644 --- a/frontend/dockerfile/instructions/parse.go +++ b/frontend/dockerfile/instructions/parse.go @@ -10,7 +10,6 @@ import ( "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/strslice" - "github.com/docker/docker/pkg/system" "github.com/moby/buildkit/frontend/dockerfile/command" "github.com/moby/buildkit/frontend/dockerfile/parser" "github.com/pkg/errors" @@ -279,13 +278,14 @@ func parseFrom(req parseRequest) (*Stage, error) { if err := req.flags.Parse(); err != nil { return nil, err } + code := strings.TrimSpace(req.original) return &Stage{ BaseName: req.args[0], Name: stageName, SourceCode: code, Commands: []Command{}, - Platform: *system.ParsePlatform(flPlatform.Value), + Platform: flPlatform.Value, }, nil } diff --git a/frontend/frontend.go b/frontend/frontend.go index f522f59964fb..d76742d7c2e5 100644 --- a/frontend/frontend.go +++ b/frontend/frontend.go @@ -9,6 +9,7 @@ import ( "github.com/moby/buildkit/solver" "github.com/moby/buildkit/solver/pb" digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) type Frontend interface { @@ -17,7 +18,7 @@ type Frontend interface { type FrontendLLBBridge interface { Solve(ctx context.Context, req SolveRequest) (solver.CachedResult, map[string][]byte, error) - ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) + ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) Exec(ctx context.Context, meta executor.Meta, rootfs cache.ImmutableRef, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error } diff --git a/frontend/gateway/client/client.go b/frontend/gateway/client/client.go index 784925a0b782..51c11d653339 100644 --- a/frontend/gateway/client/client.go +++ b/frontend/gateway/client/client.go @@ -5,12 +5,13 @@ import ( "github.com/moby/buildkit/solver/pb" digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go/v1" ) // TODO: make this take same options as LLBBridge. Add Return() type Client interface { Solve(ctx context.Context, req SolveRequest, exporterAttr map[string][]byte, final bool) (Reference, error) - ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) + ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) Opts() map[string]string SessionID() string } diff --git a/frontend/gateway/gateway.go b/frontend/gateway/gateway.go index 72831e9294f5..bc7698a6bc96 100644 --- a/frontend/gateway/gateway.go +++ b/frontend/gateway/gateway.go @@ -22,7 +22,7 @@ import ( "github.com/moby/buildkit/solver" "github.com/moby/buildkit/util/tracing" "github.com/moby/buildkit/worker" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "golang.org/x/net/http2" @@ -63,7 +63,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten sid := session.FromContext(ctx) _, isDevel := opts[keyDevel] - var img ocispec.Image + var img specs.Image var rootFS cache.ImmutableRef var readonly bool // TODO: try to switch to read-only by default. @@ -95,7 +95,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten return nil, nil, err } - dgst, config, err := llbBridge.ResolveImageConfig(ctx, reference.TagNameOnly(sourceRef).String()) + dgst, config, err := llbBridge.ResolveImageConfig(ctx, reference.TagNameOnly(sourceRef).String(), nil) // TODO: if err != nil { return nil, nil, err } @@ -262,7 +262,17 @@ type llbBridgeForwarder struct { func (lbf *llbBridgeForwarder) ResolveImageConfig(ctx context.Context, req *pb.ResolveImageConfigRequest) (*pb.ResolveImageConfigResponse, error) { ctx = tracing.ContextWithSpanFromContext(ctx, lbf.callCtx) - dgst, dt, err := lbf.llbBridge.ResolveImageConfig(ctx, req.Ref) + var platform *specs.Platform + if p := req.Platform; p != nil { + platform = &specs.Platform{ + OS: p.OS, + Architecture: p.Architecture, + Variant: p.Variant, + OSVersion: p.OSVersion, + OSFeatures: p.OSFeatures, + } + } + dgst, dt, err := lbf.llbBridge.ResolveImageConfig(ctx, req.Ref, platform) if err != nil { return nil, err } diff --git a/frontend/gateway/grpcclient/client.go b/frontend/gateway/grpcclient/client.go index 04c74c2d68af..cc550c3d787b 100644 --- a/frontend/gateway/grpcclient/client.go +++ b/frontend/gateway/grpcclient/client.go @@ -11,7 +11,9 @@ import ( "github.com/moby/buildkit/frontend/gateway/client" pb "github.com/moby/buildkit/frontend/gateway/pb" + opspb "github.com/moby/buildkit/solver/pb" digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "google.golang.org/grpc" ) @@ -63,8 +65,18 @@ func (c *grpcClient) Solve(ctx context.Context, creq client.SolveRequest, export return &reference{id: resp.Ref, c: c}, nil } -func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) { - resp, err := c.client.ResolveImageConfig(ctx, &pb.ResolveImageConfigRequest{Ref: ref}) +func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) { + var p *opspb.Platform + if platform != nil { + p = &opspb.Platform{ + OS: platform.OS, + Architecture: platform.Architecture, + Variant: platform.Variant, + OSVersion: platform.OSVersion, + OSFeatures: platform.OSFeatures, + } + } + resp, err := c.client.ResolveImageConfig(ctx, &pb.ResolveImageConfigRequest{Ref: ref, Platform: p}) if err != nil { return "", nil, err } diff --git a/frontend/gateway/pb/gateway.pb.go b/frontend/gateway/pb/gateway.pb.go index eb20999563ec..bbd8531ea63a 100644 --- a/frontend/gateway/pb/gateway.pb.go +++ b/frontend/gateway/pb/gateway.pb.go @@ -45,7 +45,8 @@ var _ = math.Inf const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package type ResolveImageConfigRequest struct { - Ref string `protobuf:"bytes,1,opt,name=Ref,proto3" json:"Ref,omitempty"` + Ref string `protobuf:"bytes,1,opt,name=Ref,proto3" json:"Ref,omitempty"` + Platform *pb.Platform `protobuf:"bytes,2,opt,name=Platform" json:"Platform,omitempty"` } func (m *ResolveImageConfigRequest) Reset() { *m = ResolveImageConfigRequest{} } @@ -60,6 +61,13 @@ func (m *ResolveImageConfigRequest) GetRef() string { return "" } +func (m *ResolveImageConfigRequest) GetPlatform() *pb.Platform { + if m != nil { + return m.Platform + } + return nil +} + type ResolveImageConfigResponse struct { Digest github_com_opencontainers_go_digest.Digest `protobuf:"bytes,1,opt,name=Digest,proto3,customtype=github.com/opencontainers/go-digest.Digest" json:"Digest"` Config []byte `protobuf:"bytes,2,opt,name=Config,proto3" json:"Config,omitempty"` @@ -451,6 +459,16 @@ func (m *ResolveImageConfigRequest) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintGateway(dAtA, i, uint64(len(m.Ref))) i += copy(dAtA[i:], m.Ref) } + if m.Platform != nil { + dAtA[i] = 0x12 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Platform.Size())) + n1, err := m.Platform.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + } return i, nil } @@ -503,11 +521,11 @@ func (m *SolveRequest) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGateway(dAtA, i, uint64(m.Definition.Size())) - n1, err := m.Definition.MarshalTo(dAtA[i:]) + n2, err := m.Definition.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n1 + i += n2 } if len(m.Frontend) > 0 { dAtA[i] = 0x12 @@ -627,11 +645,11 @@ func (m *ReadFileRequest) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x1a i++ i = encodeVarintGateway(dAtA, i, uint64(m.Range.Size())) - n2, err := m.Range.MarshalTo(dAtA[i:]) + n3, err := m.Range.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n2 + i += n3 } return i, nil } @@ -740,6 +758,10 @@ func (m *ResolveImageConfigRequest) Size() (n int) { if l > 0 { n += 1 + l + sovGateway(uint64(l)) } + if m.Platform != nil { + l = m.Platform.Size() + n += 1 + l + sovGateway(uint64(l)) + } return n } @@ -929,6 +951,39 @@ func (m *ResolveImageConfigRequest) Unmarshal(dAtA []byte) error { } m.Ref = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Platform", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Platform == nil { + m.Platform = &pb.Platform{} + } + if err := m.Platform.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGateway(dAtA[iNdEx:]) @@ -1998,45 +2053,46 @@ var ( func init() { proto.RegisterFile("gateway.proto", fileDescriptorGateway) } var fileDescriptorGateway = []byte{ - // 629 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0x4d, 0x4f, 0xdb, 0x40, - 0x10, 0xad, 0x63, 0x40, 0x64, 0x12, 0x3e, 0xb4, 0xaa, 0x2a, 0xe3, 0x03, 0x44, 0x56, 0x45, 0x2d, - 0x5a, 0x6c, 0x35, 0x6d, 0x25, 0x44, 0xa5, 0x4a, 0x0d, 0x1f, 0x12, 0x15, 0x12, 0x68, 0x7b, 0xa8, - 0xc4, 0xcd, 0x4e, 0xc6, 0x66, 0x45, 0xb2, 0xeb, 0xda, 0x1b, 0xda, 0xa8, 0x97, 0xf6, 0xe7, 0xf4, - 0x9f, 0x70, 0xec, 0x99, 0x03, 0xaa, 0xf8, 0x25, 0x95, 0xd7, 0xeb, 0x60, 0x48, 0x49, 0xe9, 0x6d, - 0xdf, 0x78, 0xe6, 0xed, 0x9b, 0x79, 0xb3, 0x86, 0x85, 0x38, 0x90, 0xf8, 0x25, 0x18, 0x79, 0x49, - 0x2a, 0xa4, 0x20, 0x2b, 0x03, 0x11, 0x8e, 0xbc, 0x70, 0xc8, 0xfa, 0xbd, 0x33, 0x26, 0xbd, 0xf3, - 0x97, 0x5e, 0x94, 0x0a, 0x2e, 0x91, 0xf7, 0xec, 0xcd, 0x98, 0xc9, 0xd3, 0x61, 0xe8, 0x75, 0xc5, - 0xc0, 0x8f, 0x45, 0x2c, 0x7c, 0x55, 0x11, 0x0e, 0x23, 0x85, 0x14, 0x50, 0xa7, 0x82, 0xc9, 0x7e, - 0x51, 0x49, 0xcf, 0x49, 0xfd, 0x92, 0xd4, 0xcf, 0x44, 0xff, 0x1c, 0x53, 0x3f, 0x09, 0x7d, 0x91, - 0x64, 0x45, 0xb6, 0xb3, 0x09, 0x2b, 0x14, 0xd5, 0x87, 0x83, 0x41, 0x10, 0xe3, 0x8e, 0xe0, 0x11, - 0x8b, 0x29, 0x7e, 0x1e, 0x62, 0x26, 0xc9, 0x32, 0x98, 0x14, 0x23, 0xcb, 0x68, 0x19, 0x6e, 0x9d, - 0xe6, 0x47, 0xe7, 0xbb, 0x01, 0xf6, 0xdf, 0xf2, 0xb3, 0x44, 0xf0, 0x0c, 0xc9, 0x07, 0x98, 0xdb, - 0x65, 0x31, 0x66, 0xb2, 0xa8, 0xe9, 0xb4, 0x2f, 0xae, 0xd6, 0x1e, 0x5d, 0x5e, 0xad, 0x6d, 0x54, - 0x34, 0x89, 0x04, 0x79, 0x57, 0x70, 0x19, 0x30, 0x8e, 0x69, 0xe6, 0xc7, 0x62, 0xb3, 0xa7, 0x4a, - 0xbc, 0xa2, 0x92, 0x6a, 0x06, 0xf2, 0x04, 0xe6, 0x0a, 0x76, 0xab, 0xd6, 0x32, 0xdc, 0x26, 0xd5, - 0xc8, 0xb9, 0xac, 0x41, 0xf3, 0x63, 0x2e, 0xa0, 0x54, 0xe9, 0x01, 0xec, 0x62, 0xc4, 0x38, 0x93, - 0x4c, 0x70, 0x75, 0x71, 0xa3, 0xbd, 0xe8, 0x25, 0xa1, 0x77, 0x13, 0xa5, 0x95, 0x0c, 0x62, 0xc3, - 0xfc, 0xbe, 0x9e, 0xad, 0xa2, 0xae, 0xd3, 0x31, 0x26, 0x27, 0xd0, 0x28, 0xcf, 0x47, 0x89, 0xb4, - 0xcc, 0x96, 0xe9, 0x36, 0xda, 0x5b, 0xde, 0xbd, 0xe6, 0x78, 0x55, 0x25, 0x5e, 0xa5, 0x74, 0x8f, - 0xcb, 0x74, 0x44, 0xab, 0x64, 0xc4, 0x85, 0xa5, 0x83, 0x41, 0x22, 0x52, 0xb9, 0x13, 0x74, 0x4f, - 0x91, 0x62, 0x94, 0x59, 0x33, 0x2d, 0xd3, 0xad, 0xd3, 0xbb, 0x61, 0xf2, 0x18, 0x66, 0xf7, 0x19, - 0x0f, 0xfa, 0x16, 0xb4, 0x0c, 0x77, 0x9e, 0x16, 0x80, 0x38, 0xd0, 0xdc, 0xfb, 0x9a, 0x27, 0x62, - 0xfa, 0x5e, 0xca, 0xd4, 0x6a, 0xa8, 0xb1, 0xdc, 0x8a, 0xd9, 0xef, 0x60, 0xf9, 0xae, 0x88, 0xdc, - 0xc5, 0x33, 0x1c, 0x95, 0x2e, 0x9e, 0xe1, 0x28, 0xe7, 0x3f, 0x0f, 0xfa, 0x43, 0xd4, 0xed, 0x17, - 0x60, 0xbb, 0xb6, 0x65, 0x38, 0x7b, 0xb0, 0xa0, 0x3b, 0xd2, 0x8e, 0x4e, 0xac, 0xc0, 0x84, 0x8c, - 0xda, 0xa4, 0x0c, 0xe7, 0x1b, 0x2c, 0x51, 0x0c, 0x7a, 0xfb, 0xac, 0x8f, 0xf7, 0xee, 0x92, 0xf2, - 0x81, 0xf5, 0xf1, 0x38, 0x90, 0xa7, 0x63, 0x1f, 0x34, 0x26, 0xdb, 0x30, 0x4b, 0x03, 0x1e, 0xa3, - 0x65, 0x2a, 0x3b, 0x9f, 0x4e, 0x71, 0x40, 0x5d, 0x92, 0xe7, 0xd2, 0xa2, 0xc4, 0x79, 0x0b, 0xf5, - 0x71, 0x2c, 0xdf, 0xa2, 0xa3, 0x28, 0xca, 0xb0, 0xd8, 0x48, 0x93, 0x6a, 0x94, 0xc7, 0x0f, 0x91, - 0xc7, 0xfa, 0x6a, 0x93, 0x6a, 0xe4, 0xac, 0xc3, 0xf2, 0x8d, 0x72, 0x3d, 0x03, 0x02, 0x33, 0xbb, - 0x81, 0x0c, 0x14, 0x43, 0x93, 0xaa, 0xb3, 0xb3, 0x00, 0x8d, 0x63, 0xc6, 0xcb, 0x97, 0xe2, 0x2c, - 0x42, 0xf3, 0x58, 0xf0, 0xf1, 0x43, 0x68, 0xff, 0x34, 0xa1, 0x7e, 0x78, 0xd8, 0xe9, 0xa4, 0xac, - 0x17, 0x23, 0xf9, 0x61, 0x00, 0x99, 0x7c, 0x35, 0xe4, 0xf5, 0x94, 0xae, 0xee, 0x7d, 0x94, 0xf6, - 0x9b, 0xff, 0xac, 0xd2, 0x4d, 0x9c, 0xc0, 0xac, 0x72, 0x96, 0x3c, 0x7b, 0xe0, 0x36, 0xdb, 0xee, - 0xbf, 0x13, 0x35, 0x77, 0x17, 0xe6, 0xcb, 0xa1, 0x91, 0x8d, 0xa9, 0xf2, 0x6e, 0xed, 0x84, 0xfd, - 0xfc, 0x41, 0xb9, 0xfa, 0x92, 0x4f, 0x30, 0x93, 0x4f, 0x9c, 0xac, 0x4f, 0x29, 0xaa, 0x58, 0x62, - 0x4f, 0xeb, 0xb3, 0xea, 0x55, 0xa7, 0x79, 0x71, 0xbd, 0x6a, 0xfc, 0xba, 0x5e, 0x35, 0x7e, 0x5f, - 0xaf, 0x1a, 0xe1, 0x9c, 0xfa, 0x2f, 0xbe, 0xfa, 0x13, 0x00, 0x00, 0xff, 0xff, 0xd8, 0x21, 0xd1, - 0x98, 0xa0, 0x05, 0x00, 0x00, + // 652 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0x4d, 0x4f, 0xdb, 0x4a, + 0x14, 0x7d, 0x8e, 0x01, 0x25, 0x37, 0xe6, 0x43, 0xa3, 0xa7, 0x27, 0xe3, 0x05, 0x44, 0xd6, 0x13, + 0xcf, 0xe2, 0x15, 0x5b, 0x4d, 0x5b, 0x09, 0x51, 0xa9, 0x52, 0xc3, 0x87, 0x44, 0x85, 0x44, 0x34, + 0x5d, 0x20, 0xb1, 0x1b, 0x27, 0x63, 0x33, 0xc2, 0x99, 0x71, 0xed, 0x09, 0x6d, 0xd4, 0x4d, 0xfb, + 0x73, 0xfa, 0x4f, 0x58, 0x76, 0xcd, 0x02, 0x55, 0xfc, 0x92, 0xca, 0xe3, 0x71, 0x30, 0x50, 0x52, + 0xba, 0x9b, 0x73, 0x7d, 0xef, 0x99, 0x73, 0xe7, 0xdc, 0x6b, 0x58, 0x8c, 0x89, 0xa4, 0x1f, 0xc9, + 0xc4, 0x4f, 0x33, 0x21, 0x05, 0x5a, 0x1d, 0x89, 0x70, 0xe2, 0x87, 0x63, 0x96, 0x0c, 0xcf, 0x99, + 0xf4, 0x2f, 0x9e, 0xfb, 0x51, 0x26, 0xb8, 0xa4, 0x7c, 0xe8, 0x6c, 0xc5, 0x4c, 0x9e, 0x8d, 0x43, + 0x7f, 0x20, 0x46, 0x41, 0x2c, 0x62, 0x11, 0xa8, 0x8a, 0x70, 0x1c, 0x29, 0xa4, 0x80, 0x3a, 0x95, + 0x4c, 0xce, 0xb3, 0x5a, 0x7a, 0x41, 0x1a, 0x54, 0xa4, 0x41, 0x2e, 0x92, 0x0b, 0x9a, 0x05, 0x69, + 0x18, 0x88, 0x34, 0x2f, 0xb3, 0xdd, 0x13, 0x58, 0xc5, 0x54, 0x7d, 0x38, 0x1c, 0x91, 0x98, 0xee, + 0x0a, 0x1e, 0xb1, 0x18, 0xd3, 0x0f, 0x63, 0x9a, 0x4b, 0xb4, 0x02, 0x26, 0xa6, 0x91, 0x6d, 0x74, + 0x0c, 0xaf, 0x85, 0x8b, 0x23, 0xf2, 0xa0, 0xd9, 0x4f, 0x88, 0x8c, 0x44, 0x36, 0xb2, 0x1b, 0x1d, + 0xc3, 0x6b, 0x77, 0x2d, 0x3f, 0x0d, 0xfd, 0x2a, 0x86, 0xa7, 0x5f, 0xdd, 0x2f, 0x06, 0x38, 0xbf, + 0x62, 0xce, 0x53, 0xc1, 0x73, 0x8a, 0xde, 0xc1, 0xc2, 0x1e, 0x8b, 0x69, 0x2e, 0x4b, 0xf6, 0x5e, + 0xf7, 0xf2, 0x7a, 0xfd, 0xaf, 0xab, 0xeb, 0xf5, 0xcd, 0x9a, 0x7a, 0x91, 0x52, 0x3e, 0x10, 0x5c, + 0x12, 0xc6, 0x69, 0x96, 0x07, 0xb1, 0xd8, 0x1a, 0xaa, 0x12, 0xbf, 0xac, 0xc4, 0x9a, 0x01, 0xfd, + 0x03, 0x0b, 0x25, 0xbb, 0x92, 0x64, 0x61, 0x8d, 0xdc, 0xab, 0x06, 0x58, 0xef, 0x0b, 0x01, 0x55, + 0x3f, 0x3e, 0xc0, 0x1e, 0x8d, 0x18, 0x67, 0x92, 0x09, 0xae, 0x2e, 0x6e, 0x77, 0x97, 0x0a, 0xfd, + 0xb7, 0x51, 0x5c, 0xcb, 0x40, 0x0e, 0x34, 0x0f, 0xb4, 0x0b, 0x8a, 0xba, 0x85, 0xa7, 0x18, 0x9d, + 0x42, 0xbb, 0x3a, 0x1f, 0xa7, 0xd2, 0x36, 0x3b, 0xa6, 0xd7, 0xee, 0x6e, 0xfb, 0x8f, 0xda, 0xe8, + 0xd7, 0x95, 0xf8, 0xb5, 0xd2, 0x7d, 0x2e, 0xb3, 0x09, 0xae, 0x93, 0x21, 0x0f, 0x96, 0x0f, 0x47, + 0xa9, 0xc8, 0xe4, 0x2e, 0x19, 0x9c, 0x51, 0x4c, 0xa3, 0xdc, 0x9e, 0xeb, 0x98, 0x5e, 0x0b, 0xdf, + 0x0f, 0xa3, 0xbf, 0x61, 0xfe, 0x80, 0x71, 0x92, 0xd8, 0xd0, 0x31, 0xbc, 0x26, 0x2e, 0x01, 0x72, + 0xc1, 0xda, 0xff, 0x54, 0x24, 0xd2, 0xec, 0xad, 0x94, 0x99, 0xdd, 0x56, 0xcf, 0x72, 0x27, 0xe6, + 0xbc, 0x81, 0x95, 0xfb, 0x22, 0x0a, 0xbf, 0xcf, 0xe9, 0xa4, 0xf2, 0xfb, 0x9c, 0x4e, 0x0a, 0xfe, + 0x0b, 0x92, 0x8c, 0xa9, 0x6e, 0xbf, 0x04, 0x3b, 0x8d, 0x6d, 0xc3, 0xdd, 0x87, 0x45, 0xdd, 0x91, + 0x76, 0xf4, 0xe1, 0xb0, 0xdc, 0x97, 0xd1, 0x78, 0x28, 0xc3, 0xfd, 0x0c, 0xcb, 0x98, 0x92, 0xe1, + 0x01, 0x4b, 0xe8, 0xe3, 0x53, 0x57, 0xf8, 0xc0, 0x12, 0xda, 0x27, 0xf2, 0x6c, 0xea, 0x83, 0xc6, + 0x68, 0x07, 0xe6, 0x31, 0xe1, 0x31, 0xb5, 0x4d, 0x65, 0xe7, 0xbf, 0x33, 0x1c, 0x50, 0x97, 0x14, + 0xb9, 0xb8, 0x2c, 0x71, 0x5f, 0x43, 0x6b, 0x1a, 0x2b, 0xa6, 0xe8, 0x38, 0x8a, 0x72, 0x5a, 0x4e, + 0xa4, 0x89, 0x35, 0x2a, 0xe2, 0x47, 0x94, 0xc7, 0xfa, 0x6a, 0x13, 0x6b, 0xe4, 0x6e, 0xc0, 0xca, + 0xad, 0x72, 0xfd, 0x06, 0x08, 0xe6, 0xf6, 0x88, 0x24, 0x8a, 0xc1, 0xc2, 0xea, 0xec, 0x2e, 0x42, + 0xbb, 0xcf, 0x78, 0xb5, 0x53, 0xee, 0x12, 0x58, 0x7d, 0xc1, 0xa7, 0x8b, 0xd0, 0xfd, 0x66, 0x42, + 0xeb, 0xe8, 0xa8, 0xd7, 0xcb, 0xd8, 0x30, 0xa6, 0xe8, 0xab, 0x01, 0xe8, 0xe1, 0xd6, 0xa0, 0x97, + 0x33, 0xba, 0x7a, 0x74, 0x7d, 0x9d, 0x57, 0x7f, 0x58, 0xa5, 0x9b, 0x38, 0x85, 0x79, 0xe5, 0x2c, + 0xfa, 0xef, 0x89, 0xd3, 0xec, 0x78, 0xbf, 0x4f, 0xd4, 0xdc, 0x03, 0x68, 0x56, 0x8f, 0x86, 0x36, + 0x67, 0xca, 0xbb, 0x33, 0x13, 0xce, 0xff, 0x4f, 0xca, 0xd5, 0x97, 0x9c, 0xc0, 0x5c, 0xf1, 0xe2, + 0x68, 0x63, 0x46, 0x51, 0xcd, 0x12, 0x67, 0x56, 0x9f, 0x75, 0xaf, 0x7a, 0xd6, 0xe5, 0xcd, 0x9a, + 0xf1, 0xfd, 0x66, 0xcd, 0xf8, 0x71, 0xb3, 0x66, 0x84, 0x0b, 0xea, 0x0f, 0xfa, 0xe2, 0x67, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xbc, 0x68, 0x1b, 0xf0, 0xca, 0x05, 0x00, 0x00, } diff --git a/frontend/gateway/pb/gateway.proto b/frontend/gateway/pb/gateway.proto index 55c05e43973d..9ba88bc79ca4 100644 --- a/frontend/gateway/pb/gateway.proto +++ b/frontend/gateway/pb/gateway.proto @@ -18,6 +18,7 @@ service LLBBridge { message ResolveImageConfigRequest { string Ref = 1; + pb.Platform Platform = 2; } message ResolveImageConfigResponse { diff --git a/solver/llbsolver/bridge.go b/solver/llbsolver/bridge.go index 98dc2ad5d5c1..b71ffb231acd 100644 --- a/solver/llbsolver/bridge.go +++ b/solver/llbsolver/bridge.go @@ -15,6 +15,7 @@ import ( "github.com/moby/buildkit/util/tracing" "github.com/moby/buildkit/worker" digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -108,12 +109,12 @@ func (s *llbBridge) Exec(ctx context.Context, meta executor.Meta, root cache.Imm return err } -func (s *llbBridge) ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) { +func (s *llbBridge) ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) { w, err := s.resolveWorker() if err != nil { return "", nil, err } - return w.ResolveImageConfig(ctx, ref) + return w.ResolveImageConfig(ctx, ref, platform) } type lazyCacheManager struct { diff --git a/solver/llbsolver/ops/source.go b/solver/llbsolver/ops/source.go index 2133a1546313..722861eeb4f0 100644 --- a/solver/llbsolver/ops/source.go +++ b/solver/llbsolver/ops/source.go @@ -14,18 +14,20 @@ import ( const sourceCacheType = "buildkit.source.v0" type sourceOp struct { - mu sync.Mutex - op *pb.Op_Source - sm *source.Manager - src source.SourceInstance - w worker.Worker + mu sync.Mutex + op *pb.Op_Source + platform *pb.Platform + sm *source.Manager + src source.SourceInstance + w worker.Worker } -func NewSourceOp(_ solver.Vertex, op *pb.Op_Source, sm *source.Manager, w worker.Worker) (solver.Op, error) { +func NewSourceOp(_ solver.Vertex, op *pb.Op_Source, platform *pb.Platform, sm *source.Manager, w worker.Worker) (solver.Op, error) { return &sourceOp{ - op: op, - sm: sm, - w: w, + op: op, + sm: sm, + w: w, + platform: platform, }, nil } @@ -35,7 +37,7 @@ func (s *sourceOp) instance(ctx context.Context) (source.SourceInstance, error) if s.src != nil { return s.src, nil } - id, err := source.FromLLB(s.op) + id, err := source.FromLLB(s.op, s.platform) if err != nil { return nil, err } diff --git a/solver/llbsolver/vertex.go b/solver/llbsolver/vertex.go index 4351283791c4..bc80847e420a 100644 --- a/solver/llbsolver/vertex.go +++ b/solver/llbsolver/vertex.go @@ -129,7 +129,7 @@ func loadLLB(def *pb.Definition, fn func(digest.Digest, *pb.Op, func(digest.Dige func llbOpName(op *pb.Op) string { switch op := op.Op.(type) { case *pb.Op_Source: - if id, err := source.FromLLB(op); err == nil { + if id, err := source.FromLLB(op, nil); err == nil { if id, ok := id.(*source.LocalIdentifier); ok { if len(id.IncludePatterns) == 1 { return op.Source.Identifier + " (" + id.IncludePatterns[0] + ")" diff --git a/source/containerimage/pull.go b/source/containerimage/pull.go index 38cd8a96291f..75ea8942d111 100644 --- a/source/containerimage/pull.go +++ b/source/containerimage/pull.go @@ -4,11 +4,11 @@ import ( "context" "encoding/json" "fmt" - "runtime" "github.com/containerd/containerd/content" "github.com/containerd/containerd/diff" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" "github.com/docker/distribution/reference" "github.com/moby/buildkit/cache" "github.com/moby/buildkit/session" @@ -19,7 +19,7 @@ import ( "github.com/moby/buildkit/util/pull" digest "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/identity" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -52,13 +52,13 @@ func (is *imageSource) ID() string { return source.DockerImageScheme } -func (is *imageSource) ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) { +func (is *imageSource) ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) { type t struct { dgst digest.Digest dt []byte } res, err := is.g.Do(ctx, ref, func(ctx context.Context) (interface{}, error) { - dgst, dt, err := imageutil.Config(ctx, ref, pull.NewResolver(ctx, is.SessionManager, is.ImageStore), is.ContentStore, "") + dgst, dt, err := imageutil.Config(ctx, ref, pull.NewResolver(ctx, is.SessionManager, is.ImageStore), is.ContentStore, platform) if err != nil { return nil, err } @@ -77,34 +77,44 @@ func (is *imageSource) Resolve(ctx context.Context, id source.Identifier) (sourc return nil, errors.Errorf("invalid image identifier %v", id) } + platform := platforms.DefaultSpec() + if imageIdentifier.Platform != nil { + platform = *imageIdentifier.Platform + } + pullerUtil := &pull.Puller{ Snapshotter: is.Snapshotter, ContentStore: is.ContentStore, Applier: is.Applier, Src: imageIdentifier.Reference, Resolver: pull.NewResolver(ctx, is.SessionManager, is.ImageStore), + Platform: &platform, } p := &puller{ CacheAccessor: is.CacheAccessor, Puller: pullerUtil, + Platform: platform, } return p, nil } type puller struct { CacheAccessor cache.Accessor + Platform specs.Platform *pull.Puller } -func mainManifestKey(ctx context.Context, desc ocispec.Descriptor) (digest.Digest, error) { +func mainManifestKey(ctx context.Context, desc specs.Descriptor, platform specs.Platform) (digest.Digest, error) { dt, err := json.Marshal(struct { - Digest digest.Digest - OS string - Arch string + Digest digest.Digest + OS string + Arch string + Variant string `json:",omitempty"` }{ - Digest: desc.Digest, - OS: runtime.GOOS, - Arch: runtime.GOARCH, + Digest: desc.Digest, + OS: platform.OS, + Arch: platform.Architecture, + Variant: platform.Variant, }) if err != nil { return "", err @@ -118,7 +128,7 @@ func (p *puller) CacheKey(ctx context.Context, index int) (string, bool, error) return "", false, err } if index == 0 || desc.Digest == "" { - k, err := mainManifestKey(ctx, desc) + k, err := mainManifestKey(ctx, desc, p.Platform) if err != nil { return "", false, err } @@ -132,10 +142,10 @@ func (p *puller) CacheKey(ctx context.Context, index int) (string, bool, error) if err != nil { return "", false, nil } - _, dt, err := imageutil.Config(ctx, ref.String(), p.Resolver, p.ContentStore, "") + _, dt, err := imageutil.Config(ctx, ref.String(), p.Resolver, p.ContentStore, nil) // TODO if err != nil { // this happens on schema1 images - k, err := mainManifestKey(ctx, desc) + k, err := mainManifestKey(ctx, desc, p.Platform) if err != nil { return "", false, err } @@ -158,7 +168,7 @@ func (p *puller) Snapshot(ctx context.Context) (cache.ImmutableRef, error) { // cacheKeyFromConfig returns a stable digest from image config. If image config // is a known oci image we will use chainID of layers. func cacheKeyFromConfig(dt []byte) digest.Digest { - var img ocispec.Image + var img specs.Image err := json.Unmarshal(dt, &img) if err != nil { return digest.FromBytes(dt) diff --git a/source/identifier.go b/source/identifier.go index ae6814c6f0a2..a4d9f5a01bd1 100644 --- a/source/identifier.go +++ b/source/identifier.go @@ -8,6 +8,7 @@ import ( "github.com/containerd/containerd/reference" "github.com/moby/buildkit/solver/pb" digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -50,11 +51,20 @@ func FromString(s string) (Identifier, error) { return nil, errors.Wrapf(errNotFound, "unknown schema %s", parts[0]) } } -func FromLLB(op *pb.Op_Source) (Identifier, error) { +func FromLLB(op *pb.Op_Source, platform *pb.Platform) (Identifier, error) { id, err := FromString(op.Source.Identifier) if err != nil { return nil, err } + if id, ok := id.(*ImageIdentifier); ok && platform != nil { + id.Platform = &specs.Platform{ + OS: platform.OS, + Architecture: platform.Architecture, + Variant: platform.Variant, + OSVersion: platform.OSVersion, + OSFeatures: platform.OSFeatures, + } + } if id, ok := id.(*GitIdentifier); ok { for k, v := range op.Source.Attrs { switch k { @@ -136,6 +146,7 @@ func FromLLB(op *pb.Op_Source) (Identifier, error) { type ImageIdentifier struct { Reference reference.Spec + Platform *specs.Platform } func NewImageIdentifier(str string) (*ImageIdentifier, error) { diff --git a/util/imageutil/config.go b/util/imageutil/config.go index c9d5e391ff4e..2c2e18ba53a3 100644 --- a/util/imageutil/config.go +++ b/util/imageutil/config.go @@ -10,7 +10,7 @@ import ( "github.com/containerd/containerd/reference" "github.com/containerd/containerd/remotes" digest "github.com/opencontainers/go-digest" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" ) @@ -19,16 +19,18 @@ type IngesterProvider interface { content.Provider } -func Config(ctx context.Context, str string, resolver remotes.Resolver, ingester IngesterProvider, platform string) (digest.Digest, []byte, error) { - if platform == "" { - platform = platforms.Default() +func Config(ctx context.Context, str string, resolver remotes.Resolver, ingester IngesterProvider, platform *specs.Platform) (digest.Digest, []byte, error) { + // TODO: fix containerd to take struct instead of string + platformStr := platforms.Default() + if platform != nil { + platformStr = platforms.Format(*platform) } ref, err := reference.Parse(str) if err != nil { return "", nil, errors.WithStack(err) } - desc := ocispec.Descriptor{ + desc := specs.Descriptor{ Digest: ref.Digest(), } if desc.Digest != "" { @@ -56,12 +58,12 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, ingester handlers := []images.Handler{ remotes.FetchHandler(ingester, fetcher), - childrenConfigHandler(ingester, platform), + childrenConfigHandler(ingester, platformStr), } if err := images.Dispatch(ctx, images.Handlers(handlers...), desc); err != nil { return "", nil, err } - config, err := images.Config(ctx, ingester, desc, platform) + config, err := images.Config(ctx, ingester, desc, platformStr) if err != nil { return "", nil, err } @@ -75,10 +77,10 @@ func Config(ctx context.Context, str string, resolver remotes.Resolver, ingester } func childrenConfigHandler(provider content.Provider, platform string) images.HandlerFunc { - return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { - var descs []ocispec.Descriptor + return func(ctx context.Context, desc specs.Descriptor) ([]specs.Descriptor, error) { + var descs []specs.Descriptor switch desc.MediaType { - case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest: + case images.MediaTypeDockerSchema2Manifest, specs.MediaTypeImageManifest: p, err := content.ReadBlob(ctx, provider, desc) if err != nil { return nil, err @@ -86,19 +88,19 @@ func childrenConfigHandler(provider content.Provider, platform string) images.Ha // TODO(stevvooe): We just assume oci manifest, for now. There may be // subtle differences from the docker version. - var manifest ocispec.Manifest + var manifest specs.Manifest if err := json.Unmarshal(p, &manifest); err != nil { return nil, err } descs = append(descs, manifest.Config) - case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: + case images.MediaTypeDockerSchema2ManifestList, specs.MediaTypeImageIndex: p, err := content.ReadBlob(ctx, provider, desc) if err != nil { return nil, err } - var index ocispec.Index + var index specs.Index if err := json.Unmarshal(p, &index); err != nil { return nil, err } @@ -118,7 +120,7 @@ func childrenConfigHandler(provider content.Provider, platform string) images.Ha } else { descs = append(descs, index.Manifests...) } - case images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig: + case images.MediaTypeDockerSchema2Config, specs.MediaTypeImageConfig: // childless data types. return nil, nil default: @@ -129,7 +131,7 @@ func childrenConfigHandler(provider content.Provider, platform string) images.Ha } } -// ocispec.MediaTypeImageManifest, // TODO: detect schema1/manifest-list +// specs.MediaTypeImageManifest, // TODO: detect schema1/manifest-list func DetectManifestMediaType(ra content.ReaderAt) (string, error) { // TODO: schema1 diff --git a/util/pull/pull.go b/util/pull/pull.go index dad2b7c0974e..b1d9c98c2e45 100644 --- a/util/pull/pull.go +++ b/util/pull/pull.go @@ -29,6 +29,7 @@ type Puller struct { ContentStore content.Store Applier diff.Applier Src reference.Spec + Platform *ocispec.Platform // See NewResolver() Resolver remotes.Resolver resolveOnce sync.Once @@ -86,6 +87,11 @@ func (p *Puller) Pull(ctx context.Context) (*Pulled, error) { return nil, err } + platformStr := platforms.Default() + if p.Platform != nil { + platformStr = platforms.Format(*p.Platform) + } + ongoing := newJobs(p.ref) pctx, stopProgress := context.WithCancel(ctx) @@ -117,7 +123,7 @@ func (p *Puller) Pull(ctx context.Context) (*Pulled, error) { // Set any children labels for that content childrenHandler = images.SetChildrenLabels(p.ContentStore, childrenHandler) // Filter the childen by the platform - childrenHandler = images.FilterPlatforms(childrenHandler, platforms.Default()) + childrenHandler = images.FilterPlatforms(childrenHandler, platformStr) handlers = append(handlers, remotes.FetchHandler(p.ContentStore, fetcher), @@ -155,7 +161,7 @@ func (p *Puller) Pull(ctx context.Context) (*Pulled, error) { delete(allBlobs, desc.Digest) return nil, nil }), - images.FilterPlatforms(images.ChildrenHandler(p.ContentStore), platforms.Default()), + images.FilterPlatforms(images.ChildrenHandler(p.ContentStore), platformStr), } if err := images.Dispatch(ctx, images.Handlers(handlers...), p.desc); err != nil { @@ -209,7 +215,7 @@ func (p *Puller) Pull(ctx context.Context) (*Pulled, error) { defer release() unpackProgressDone := oneOffProgress(ctx, "unpacking "+p.Src.String()) - chainid, err := unpack(ctx, p.desc, p.ContentStore, csh, p.Snapshotter, p.Applier) + chainid, err := unpack(ctx, p.desc, p.ContentStore, csh, p.Snapshotter, p.Applier, platformStr) if err != nil { return nil, unpackProgressDone(err) } @@ -222,8 +228,8 @@ func (p *Puller) Pull(ctx context.Context) (*Pulled, error) { }, nil } -func unpack(ctx context.Context, desc ocispec.Descriptor, cs content.Store, csh ctdsnapshot.Snapshotter, s snapshot.Snapshotter, applier diff.Applier) (digest.Digest, error) { - layers, err := getLayers(ctx, cs, desc) +func unpack(ctx context.Context, desc ocispec.Descriptor, cs content.Store, csh ctdsnapshot.Snapshotter, s snapshot.Snapshotter, applier diff.Applier, platform string) (digest.Digest, error) { + layers, err := getLayers(ctx, cs, desc, platform) if err != nil { return "", err } @@ -263,13 +269,13 @@ func fillBlobMapping(ctx context.Context, s snapshot.Snapshotter, layers []rootf return nil } -func getLayers(ctx context.Context, provider content.Provider, desc ocispec.Descriptor) ([]rootfs.Layer, error) { - manifest, err := images.Manifest(ctx, provider, desc, platforms.Default()) +func getLayers(ctx context.Context, provider content.Provider, desc ocispec.Descriptor, platform string) ([]rootfs.Layer, error) { + manifest, err := images.Manifest(ctx, provider, desc, platform) if err != nil { return nil, errors.WithStack(err) } image := images.Image{Target: desc} - diffIDs, err := image.RootFS(ctx, provider, platforms.Default()) + diffIDs, err := image.RootFS(ctx, provider, platform) if err != nil { return nil, errors.Wrap(err, "failed to resolve rootfs") } diff --git a/worker/base/worker.go b/worker/base/worker.go index 9e4e817e0566..d5c39017023d 100644 --- a/worker/base/worker.go +++ b/worker/base/worker.go @@ -211,11 +211,10 @@ func (w *Worker) LoadRef(id string) (cache.ImmutableRef, error) { } func (w *Worker) ResolveOp(v solver.Vertex, s frontend.FrontendLLBBridge) (solver.Op, error) { - // TODO: update this to send full op - if op, ok := v.Sys().(*pb.Op); ok { - switch op := op.Op.(type) { + if baseOp, ok := v.Sys().(*pb.Op); ok { + switch op := baseOp.Op.(type) { case *pb.Op_Source: - return ops.NewSourceOp(v, op, w.SourceManager, w) + return ops.NewSourceOp(v, op, baseOp.Platform, w.SourceManager, w) case *pb.Op_Exec: return ops.NewExecOp(v, op, w.CacheManager, w.MetadataStore, w.Executor, w) case *pb.Op_Build: @@ -225,17 +224,17 @@ func (w *Worker) ResolveOp(v solver.Vertex, s frontend.FrontendLLBBridge) (solve return nil, errors.Errorf("could not resolve %v", v) } -func (w *Worker) ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) { +func (w *Worker) ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) { // ImageSource is typically source/containerimage resolveImageConfig, ok := w.ImageSource.(resolveImageConfig) if !ok { return "", nil, errors.Errorf("worker %q does not implement ResolveImageConfig", w.ID()) } - return resolveImageConfig.ResolveImageConfig(ctx, ref) + return resolveImageConfig.ResolveImageConfig(ctx, ref, platform) } type resolveImageConfig interface { - ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) + ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) } func (w *Worker) Exec(ctx context.Context, meta executor.Meta, rootFS cache.ImmutableRef, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error { diff --git a/worker/worker.go b/worker/worker.go index a48a4598ef75..cd29d5ea667d 100644 --- a/worker/worker.go +++ b/worker/worker.go @@ -22,7 +22,7 @@ type Worker interface { LoadRef(id string) (cache.ImmutableRef, error) // ResolveOp resolves Vertex.Sys() to Op implementation. ResolveOp(v solver.Vertex, s frontend.FrontendLLBBridge) (solver.Op, error) - ResolveImageConfig(ctx context.Context, ref string) (digest.Digest, []byte, error) + ResolveImageConfig(ctx context.Context, ref string, platform *specs.Platform) (digest.Digest, []byte, error) // Exec is similar to executor.Exec but without []mount.Mount Exec(ctx context.Context, meta executor.Meta, rootFS cache.ImmutableRef, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error DiskUsage(ctx context.Context, opt client.DiskUsageInfo) ([]*client.UsageInfo, error)