frontend: make sure inputs support for frontends is detectable from client

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
v0.8
Tonis Tiigi 2020-09-22 23:36:51 -07:00
parent 78f3e86dc1
commit 44f27708b3
9 changed files with 211 additions and 22 deletions

View File

@ -64,6 +64,11 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {
caps := c.BuildOpts().LLBCaps caps := c.BuildOpts().LLBCaps
gwcaps := c.BuildOpts().Caps gwcaps := c.BuildOpts().Caps
allowForward, capsError := validateCaps(opts["frontend.caps"])
if !allowForward && capsError != nil {
return nil, capsError
}
marshalOpts := []llb.ConstraintsOpt{llb.WithCaps(caps)} marshalOpts := []llb.ConstraintsOpt{llb.WithCaps(caps)}
localNameContext := DefaultLocalNameContext localNameContext := DefaultLocalNameContext
@ -334,6 +339,10 @@ func Build(ctx context.Context, c client.Client) (*client.Result, error) {
} }
} }
if capsError != nil {
return nil, capsError
}
exportMap := len(targetPlatforms) > 1 exportMap := len(targetPlatforms) > 1
if v := opts[keyMultiPlatform]; v != "" { if v := opts[keyMultiPlatform]; v != "" {

View File

@ -0,0 +1,33 @@
package builder
import (
"strings"
"github.com/moby/buildkit/solver/errdefs"
"github.com/moby/buildkit/util/grpcerrors"
"github.com/moby/buildkit/util/stack"
"google.golang.org/grpc/codes"
)
var enabledCaps = map[string]struct{}{
"moby.buildkit.frontend.inputs": {},
}
func validateCaps(req string) (forward bool, err error) {
if req == "" {
return
}
caps := strings.Split(req, ",")
for _, c := range caps {
parts := strings.SplitN(c, "+", 2)
if _, ok := enabledCaps[parts[0]]; !ok {
err = stack.Enable(grpcerrors.WrapCode(errdefs.NewUnsupportedFrontendCapError(parts[0]), codes.Unimplemented))
if strings.Contains(c, "+forward") {
forward = true
} else {
return false, err
}
}
}
return
}

View File

@ -28,6 +28,7 @@ RUN --mount=target=. --mount=type=cache,target=/root/.cache \
FROM scratch AS release FROM scratch AS release
LABEL moby.buildkit.frontend.network.none="true" LABEL moby.buildkit.frontend.network.none="true"
LABEL moby.buildkit.frontend.caps="moby.buildkit.frontend.inputs"
COPY --from=build /dockerfile-frontend /bin/dockerfile-frontend COPY --from=build /dockerfile-frontend /bin/dockerfile-frontend
ENTRYPOINT ["/bin/dockerfile-frontend"] ENTRYPOINT ["/bin/dockerfile-frontend"]

View File

@ -37,6 +37,7 @@ import (
"github.com/moby/buildkit/util/stack" "github.com/moby/buildkit/util/stack"
"github.com/moby/buildkit/util/tracing" "github.com/moby/buildkit/util/tracing"
"github.com/moby/buildkit/worker" "github.com/moby/buildkit/worker"
"github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -83,6 +84,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
_, isDevel := opts[keyDevel] _, isDevel := opts[keyDevel]
var img specs.Image var img specs.Image
var mfstDigest digest.Digest
var rootFS cache.MutableRef var rootFS cache.MutableRef
var readonly bool // TODO: try to switch to read-only by default. var readonly bool // TODO: try to switch to read-only by default.
@ -134,6 +136,7 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
if err != nil { if err != nil {
return nil, err return nil, err
} }
mfstDigest = dgst
if err := json.Unmarshal(config, &img); err != nil { if err := json.Unmarshal(config, &img); err != nil {
return nil, err return nil, err
@ -183,12 +186,6 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
defer rootFS.Release(context.TODO()) defer rootFS.Release(context.TODO())
} }
lbf, ctx, err := serveLLBBridgeForwarder(ctx, llbBridge, gf.workers, inputs, sid, sm)
defer lbf.conn.Close() //nolint
if err != nil {
return nil, err
}
args := []string{"/run"} args := []string{"/run"}
env := []string{} env := []string{}
cwd := "/" cwd := "/"
@ -215,8 +212,6 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
} }
env = append(env, "BUILDKIT_WORKERS="+string(dt)) env = append(env, "BUILDKIT_WORKERS="+string(dt))
defer lbf.Discard()
env = append(env, "BUILDKIT_EXPORTEDPRODUCT="+apicaps.ExportedProduct) env = append(env, "BUILDKIT_EXPORTEDPRODUCT="+apicaps.ExportedProduct)
meta := executor.Meta{ meta := executor.Meta{
@ -232,6 +227,26 @@ func (gf *gatewayFrontend) Solve(ctx context.Context, llbBridge frontend.Fronten
} }
} }
curCaps := getCaps(img.Config.Labels["moby.buildkit.frontend.caps"])
addCapsForKnownFrontends(curCaps, mfstDigest)
reqCaps := getCaps(opts["frontend.caps"])
if len(inputs) > 0 {
reqCaps["moby.buildkit.frontend.inputs"] = struct{}{}
}
for c := range reqCaps {
if _, ok := curCaps[c]; !ok {
return nil, stack.Enable(grpcerrors.WrapCode(errdefs.NewUnsupportedFrontendCapError(c), codes.Unimplemented))
}
}
lbf, ctx, err := serveLLBBridgeForwarder(ctx, llbBridge, gf.workers, inputs, sid, sm)
defer lbf.conn.Close() //nolint
if err != nil {
return nil, err
}
defer lbf.Discard()
err = llbBridge.Run(ctx, "", rootFS, nil, executor.ProcessInfo{Meta: meta, Stdin: lbf.Stdin, Stdout: lbf.Stdout, Stderr: os.Stderr}, nil) err = llbBridge.Run(ctx, "", rootFS, nil, executor.ProcessInfo{Meta: meta, Stdin: lbf.Stdin, Stdout: lbf.Stdout, Stderr: os.Stderr}, nil)
if err != nil { if err != nil {
@ -1206,3 +1221,31 @@ func convertToGogoAny(in []*any.Any) []*gogotypes.Any {
} }
return out return out
} }
func getCaps(label string) map[string]struct{} {
if label == "" {
return make(map[string]struct{})
}
caps := strings.Split(label, ",")
out := make(map[string]struct{}, len(caps))
for _, c := range caps {
name := strings.SplitN(c, "+", 2)
if name[0] != "" {
out[name[0]] = struct{}{}
}
}
return out
}
func addCapsForKnownFrontends(caps map[string]struct{}, dgst digest.Digest) {
// these frontends were built without caps detection but do support inputs
defaults := map[digest.Digest]struct{}{
"sha256:9ac1c43a60e31dca741a6fe8314130a9cd4c4db0311fbbc636ff992ef60ae76d": {}, // docker/dockerfile:1.1.6
"sha256:080bd74d8778f83e7b670de193362d8c593c8b14f5c8fb919d28ee8feda0d069": {}, // docker/dockerfile:1.1.7
"sha256:60543a9d92b92af5088fb2938fb09b2072684af8384399e153e137fe081f8ab4": {}, // docker/dockerfile:1.1.6-experimental
"sha256:de85b2f3a3e8a2f7fe48e8e84a65f6fdd5cd5183afa6412fff9caa6871649c44": {}, // docker/dockerfile:1.1.7-experimental
}
if _, ok := defaults[dgst]; ok {
caps["moby.buildkit.frontend.inputs"] = struct{}{}
}
}

View File

@ -39,6 +39,9 @@ const (
// CapGatewayExec is the capability to create and interact with new // CapGatewayExec is the capability to create and interact with new
// containers directly through the gateway // containers directly through the gateway
CapGatewayExec apicaps.CapID = "gateway.exec" CapGatewayExec apicaps.CapID = "gateway.exec"
// CapFrontendCaps can be used to check that frontends define support for certain capabilities
CapFrontendCaps apicaps.CapID = "frontend.caps"
) )
func init() { func init() {
@ -147,4 +150,11 @@ func init() {
Enabled: true, Enabled: true,
Status: apicaps.CapStatusExperimental, Status: apicaps.CapStatusExperimental,
}) })
Caps.Init(apicaps.Cap{
ID: CapFrontendCaps,
Name: "frontend capabilities",
Enabled: true,
Status: apicaps.CapStatusExperimental,
})
} }

View File

@ -105,25 +105,65 @@ func (m *Source) GetRanges() []*pb.Range {
return nil return nil
} }
type FrontendCap struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *FrontendCap) Reset() { *m = FrontendCap{} }
func (m *FrontendCap) String() string { return proto.CompactTextString(m) }
func (*FrontendCap) ProtoMessage() {}
func (*FrontendCap) Descriptor() ([]byte, []int) {
return fileDescriptor_689dc58a5060aff5, []int{2}
}
func (m *FrontendCap) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_FrontendCap.Unmarshal(m, b)
}
func (m *FrontendCap) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_FrontendCap.Marshal(b, m, deterministic)
}
func (m *FrontendCap) XXX_Merge(src proto.Message) {
xxx_messageInfo_FrontendCap.Merge(m, src)
}
func (m *FrontendCap) XXX_Size() int {
return xxx_messageInfo_FrontendCap.Size(m)
}
func (m *FrontendCap) XXX_DiscardUnknown() {
xxx_messageInfo_FrontendCap.DiscardUnknown(m)
}
var xxx_messageInfo_FrontendCap proto.InternalMessageInfo
func (m *FrontendCap) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func init() { func init() {
proto.RegisterType((*Vertex)(nil), "errdefs.Vertex") proto.RegisterType((*Vertex)(nil), "errdefs.Vertex")
proto.RegisterType((*Source)(nil), "errdefs.Source") proto.RegisterType((*Source)(nil), "errdefs.Source")
proto.RegisterType((*FrontendCap)(nil), "errdefs.FrontendCap")
} }
func init() { proto.RegisterFile("errdefs.proto", fileDescriptor_689dc58a5060aff5) } func init() { proto.RegisterFile("errdefs.proto", fileDescriptor_689dc58a5060aff5) }
var fileDescriptor_689dc58a5060aff5 = []byte{ var fileDescriptor_689dc58a5060aff5 = []byte{
// 177 bytes of a gzipped FileDescriptorProto // 200 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x2c, 0xcd, 0xc1, 0x8a, 0x83, 0x30, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x2c, 0xce, 0xc1, 0x4a, 0x04, 0x31,
0x10, 0x80, 0x61, 0xdc, 0x5d, 0xb2, 0x18, 0xd9, 0x3d, 0xe4, 0x50, 0xa4, 0x27, 0xeb, 0xc9, 0x43, 0x0c, 0x06, 0x60, 0x56, 0x97, 0xca, 0x66, 0xd0, 0x43, 0x0f, 0xb2, 0x78, 0x9a, 0xed, 0x69, 0x0f,
0x49, 0xc0, 0x3e, 0x45, 0x4f, 0x85, 0x14, 0x7a, 0x6f, 0x74, 0xb4, 0xa1, 0xea, 0x84, 0x49, 0x2c, 0xd2, 0xc2, 0xfa, 0x08, 0x82, 0xe0, 0x49, 0xa8, 0xe0, 0x7d, 0xba, 0xcd, 0x8c, 0xc5, 0x9d, 0xa6,
0xed, 0xdb, 0x17, 0x6d, 0x8e, 0xff, 0x7c, 0x33, 0x0c, 0xff, 0x03, 0xa2, 0x16, 0x3a, 0x2f, 0x1d, 0xa4, 0x1d, 0xd1, 0xb7, 0x97, 0xa9, 0xbd, 0xe5, 0xcf, 0x97, 0x90, 0xc0, 0x2d, 0x32, 0x7b, 0x1c,
0x61, 0x40, 0xf1, 0x1b, 0x73, 0xbb, 0xef, 0x6d, 0xb8, 0xcd, 0x46, 0x36, 0x38, 0xaa, 0x11, 0xcd, 0xb3, 0x4e, 0x4c, 0x85, 0xe4, 0x4d, 0x8b, 0x0f, 0x8f, 0x53, 0x28, 0x9f, 0x8b, 0xd3, 0x67, 0x9a,
0x4b, 0x99, 0xd9, 0x0e, 0xed, 0xdd, 0x06, 0xe5, 0x71, 0x78, 0x00, 0x29, 0x67, 0x14, 0xba, 0x78, 0xcd, 0x4c, 0xee, 0xd7, 0xb8, 0x25, 0x5c, 0xfc, 0x57, 0x28, 0x26, 0xd3, 0xe5, 0x1b, 0xd9, 0x24,
0x56, 0x16, 0x9c, 0x5d, 0x80, 0x02, 0x3c, 0xc5, 0x86, 0xb3, 0xd6, 0xf6, 0xe0, 0x43, 0x9e, 0x14, 0x67, 0x28, 0xb5, 0x35, 0xd5, 0x83, 0xf8, 0x40, 0x2e, 0xf8, 0x23, 0xef, 0x41, 0xf8, 0x30, 0x61,
0x49, 0x95, 0xea, 0x58, 0xe5, 0x89, 0xb3, 0x33, 0xce, 0xd4, 0x80, 0x28, 0xf9, 0x8f, 0x9d, 0x3a, 0x2e, 0xfb, 0x4d, 0xbf, 0x39, 0xee, 0x6c, 0x4b, 0xea, 0x0d, 0xc4, 0x3b, 0x2d, 0x7c, 0x46, 0xa9,
0x5c, 0x3d, 0xab, 0xff, 0xa5, 0x33, 0xf2, 0x23, 0xc7, 0xa9, 0x43, 0xbd, 0x9a, 0xd8, 0x71, 0x46, 0x60, 0x1b, 0xe2, 0x48, 0xd5, 0xbb, 0xd3, 0x9d, 0x4e, 0x4e, 0xff, 0xcb, 0x6b, 0x1c, 0xc9, 0x56,
0xd7, 0xa9, 0x07, 0x9f, 0x7f, 0x15, 0xdf, 0x55, 0x56, 0xa7, 0xcb, 0x96, 0x5e, 0x26, 0x3a, 0x82, 0x93, 0x07, 0x10, 0x3c, 0xc4, 0x09, 0xf3, 0xfe, 0xaa, 0xbf, 0x3e, 0x76, 0xa7, 0xdd, 0x3a, 0x65,
0x61, 0xeb, 0xe7, 0xc3, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x93, 0xb5, 0x8b, 0x2a, 0xc1, 0x00, 0x00, 0xd7, 0x8e, 0x6d, 0xa0, 0x0e, 0xd0, 0xbd, 0x30, 0xc5, 0x82, 0xd1, 0x3f, 0x0f, 0x49, 0x4a, 0xd8,
0x00, 0xc6, 0x61, 0xc6, 0x76, 0xb5, 0xd6, 0x4e, 0xd4, 0xe7, 0x9e, 0xfe, 0x02, 0x00, 0x00, 0xff, 0xff,
0x80, 0xf5, 0x33, 0xf9, 0xe4, 0x00, 0x00, 0x00,
} }

View File

@ -12,3 +12,7 @@ message Source {
pb.SourceInfo info = 1; pb.SourceInfo info = 1;
repeated pb.Range ranges = 2; repeated pb.Range ranges = 2;
} }
message FrontendCap {
string name = 1;
}

View File

@ -0,0 +1,41 @@
package errdefs
import (
fmt "fmt"
"github.com/containerd/typeurl"
"github.com/moby/buildkit/util/grpcerrors"
)
func init() {
typeurl.Register((*FrontendCap)(nil), "github.com/moby/buildkit", "errdefs.FrontendCap+json")
}
type UnsupportedFrontendCapError struct {
FrontendCap
error
}
func (e *UnsupportedFrontendCapError) Error() string {
msg := fmt.Sprintf("unsupported frontend capability %s", e.FrontendCap.Name)
if e.error != nil {
msg += ": " + e.error.Error()
}
return msg
}
func (e *UnsupportedFrontendCapError) Unwrap() error {
return e.error
}
func (e *UnsupportedFrontendCapError) ToProto() grpcerrors.TypedErrorProto {
return &e.FrontendCap
}
func NewUnsupportedFrontendCapError(name string) error {
return &UnsupportedFrontendCapError{FrontendCap: FrontendCap{Name: name}}
}
func (v *FrontendCap) WrapError(err error) error {
return &UnsupportedFrontendCapError{error: err, FrontendCap: *v}
}

View File

@ -33,8 +33,12 @@ func ToGRPC(err error) error {
st = status.New(Code(err), err.Error()) st = status.New(Code(err), err.Error())
} }
if st.Code() != Code(err) { if st.Code() != Code(err) {
code := Code(err)
if code == codes.OK {
code = codes.Unknown
}
pb := st.Proto() pb := st.Proto()
pb.Code = int32(Code(err)) pb.Code = int32(code)
st = status.FromProto(pb) st = status.FromProto(pb)
} }
@ -182,6 +186,10 @@ type withCode struct {
error error
} }
func (e *withCode) Code() codes.Code {
return e.code
}
func (e *withCode) Unwrap() error { func (e *withCode) Unwrap() error {
return e.error return e.error
} }