frontend: make sure inputs support for frontends is detectable from client
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>v0.8
parent
78f3e86dc1
commit
44f27708b3
|
@ -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 != "" {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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"]
|
||||||
|
|
||||||
|
|
|
@ -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{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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}
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue