vendor: github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353

full diff:

- https://github.com/moby/buildkit/compare/20230620112432...v0.12.0
- https://github.com/moby/buildkit/compare/v0.12.0...faa0cc7da3536923d85b74b2bb2d13c12a6ecc99

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
pull/1948/head
Sebastiaan van Stijn 2 years ago
parent 2666bd6996
commit 130bbda00e
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C

@ -23,7 +23,7 @@ require (
github.com/google/uuid v1.3.0
github.com/hashicorp/go-cty-funcs v0.0.0-20200930094925-2721b1e36840
github.com/hashicorp/hcl/v2 v2.8.2
github.com/moby/buildkit v0.11.0-rc3.0.20230620112432-2d91ddcceedc
github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353 // v0.12.1-dev
github.com/moby/sys/mountinfo v0.6.2
github.com/moby/sys/signal v0.7.0
github.com/morikuni/aec v1.0.0
@ -76,6 +76,7 @@ require (
github.com/cenkalti/backoff/v4 v4.2.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cloudflare/cfssl v0.0.0-20181213083726-b94e044bb51e // indirect
github.com/containerd/ttrpc v1.2.2 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa // indirect

@ -136,6 +136,7 @@ github.com/containerd/nydus-snapshotter v0.8.2 h1:7SOrMU2YmLzfbsr5J7liMZJlNi5WT6
github.com/containerd/stargz-snapshotter v0.14.3 h1:OTUVZoPSPs8mGgmQUE1dqw3WX/3nrsmsurW7UPLWl1U=
github.com/containerd/stargz-snapshotter/estargz v0.14.3 h1:OqlDCK3ZVUO6C3B/5FSkDwbkEETK84kQgEeFwDC+62k=
github.com/containerd/ttrpc v1.2.2 h1:9vqZr0pxwOF5koz6N0N3kJ0zDHokrcPxIR/ZR2YFtOs=
github.com/containerd/ttrpc v1.2.2/go.mod h1:sIT6l32Ph/H9cvnJsfXM5drIVzTr5A2flTf1G5tYZak=
github.com/containerd/typeurl/v2 v2.1.1 h1:3Q4Pt7i8nYwy2KmQWIw2+1hTvwTE/6w9FqcttATPO/4=
github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@ -269,6 +270,7 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -370,8 +372,8 @@ github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzC
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/moby/buildkit v0.11.0-rc3.0.20230620112432-2d91ddcceedc h1:79EnLqFEkPn6sTBXDHE546AHmYeb/QzXgGyIpli8w34=
github.com/moby/buildkit v0.11.0-rc3.0.20230620112432-2d91ddcceedc/go.mod h1:6Y1HYDrxg3sY5gBY2FVaEvQpswBj3g/ck7aKYCjOkk0=
github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353 h1:/ZIwqvOF3QKObJbjX96xVvAKtnWdw/AuEqysbbujaZA=
github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353/go.mod h1:+n9GmkxwBCjVz4u7wmiyh+oqvjIjQM+1zk3iJrWfdos=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo=
@ -436,6 +438,7 @@ github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
@ -453,6 +456,7 @@ github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh
github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@ -633,6 +637,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -650,6 +655,7 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -674,6 +680,7 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

@ -0,0 +1,73 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package identifiers provides common validation for identifiers and keys
// across containerd.
//
// Identifiers in containerd must be a alphanumeric, allowing limited
// underscores, dashes and dots.
//
// While the character set may be expanded in the future, identifiers
// are guaranteed to be safely used as filesystem path components.
package identifiers
import (
"fmt"
"regexp"
"github.com/containerd/containerd/errdefs"
)
const (
maxLength = 76
alphanum = `[A-Za-z0-9]+`
separators = `[._-]`
)
var (
// identifierRe defines the pattern for valid identifiers.
identifierRe = regexp.MustCompile(reAnchor(alphanum + reGroup(separators+reGroup(alphanum)) + "*"))
)
// Validate returns nil if the string s is a valid identifier.
//
// identifiers are similar to the domain name rules according to RFC 1035, section 2.3.1. However
// rules in this package are relaxed to allow numerals to follow period (".") and mixed case is
// allowed.
//
// In general identifiers that pass this validation should be safe for use as filesystem path components.
func Validate(s string) error {
if len(s) == 0 {
return fmt.Errorf("identifier must not be empty: %w", errdefs.ErrInvalidArgument)
}
if len(s) > maxLength {
return fmt.Errorf("identifier %q greater than maximum length (%d characters): %w", s, maxLength, errdefs.ErrInvalidArgument)
}
if !identifierRe.MatchString(s) {
return fmt.Errorf("identifier %q must match %v: %w", s, identifierRe, errdefs.ErrInvalidArgument)
}
return nil
}
func reGroup(s string) string {
return `(?:` + s + `)`
}
func reAnchor(s string) string {
return `^` + s + `$`
}

@ -0,0 +1,40 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leases
import "context"
type leaseKey struct{}
// WithLease sets a given lease on the context
func WithLease(ctx context.Context, lid string) context.Context {
ctx = context.WithValue(ctx, leaseKey{}, lid)
// also store on the grpc headers so it gets picked up by any clients that
// are using this.
return withGRPCLeaseHeader(ctx, lid)
}
// FromContext returns the lease from the context.
func FromContext(ctx context.Context) (string, bool) {
lid, ok := ctx.Value(leaseKey{}).(string)
if !ok {
return fromGRPCHeader(ctx)
}
return lid, ok
}

@ -0,0 +1,58 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leases
import (
"context"
"google.golang.org/grpc/metadata"
)
const (
// GRPCHeader defines the header name for specifying a containerd lease.
GRPCHeader = "containerd-lease"
)
func withGRPCLeaseHeader(ctx context.Context, lid string) context.Context {
// also store on the grpc headers so it gets picked up by any clients
// that are using this.
txheader := metadata.Pairs(GRPCHeader, lid)
md, ok := metadata.FromOutgoingContext(ctx) // merge with outgoing context.
if !ok {
md = txheader
} else {
// order ensures the latest is first in this list.
md = metadata.Join(txheader, md)
}
return metadata.NewOutgoingContext(ctx, md)
}
func fromGRPCHeader(ctx context.Context) (string, bool) {
// try to extract for use in grpc servers.
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return "", false
}
values := md[GRPCHeader]
if len(values) == 0 {
return "", false
}
return values[0], true
}

@ -0,0 +1,43 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leases
import (
"crypto/rand"
"encoding/base64"
"fmt"
"time"
)
// WithRandomID sets the lease ID to a random unique value
func WithRandomID() Opt {
return func(l *Lease) error {
t := time.Now()
var b [3]byte
rand.Read(b[:])
l.ID = fmt.Sprintf("%d-%s", t.Nanosecond(), base64.URLEncoding.EncodeToString(b[:]))
return nil
}
}
// WithID sets the ID for the lease
func WithID(id string) Opt {
return func(l *Lease) error {
l.ID = id
return nil
}
}

@ -0,0 +1,91 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package leases
import (
"context"
"time"
)
// Opt is used to set options on a lease
type Opt func(*Lease) error
// DeleteOpt allows configuring a delete operation
type DeleteOpt func(context.Context, *DeleteOptions) error
// Manager is used to create, list, and remove leases
type Manager interface {
Create(context.Context, ...Opt) (Lease, error)
Delete(context.Context, Lease, ...DeleteOpt) error
List(context.Context, ...string) ([]Lease, error)
AddResource(context.Context, Lease, Resource) error
DeleteResource(context.Context, Lease, Resource) error
ListResources(context.Context, Lease) ([]Resource, error)
}
// Lease retains resources to prevent cleanup before
// the resources can be fully referenced.
type Lease struct {
ID string
CreatedAt time.Time
Labels map[string]string
}
// Resource represents low level resource of image, like content, ingest and
// snapshotter.
type Resource struct {
ID string
Type string
}
// DeleteOptions provide options on image delete
type DeleteOptions struct {
Synchronous bool
}
// SynchronousDelete is used to indicate that a lease deletion and removal of
// any unreferenced resources should occur synchronously before returning the
// result.
func SynchronousDelete(ctx context.Context, o *DeleteOptions) error {
o.Synchronous = true
return nil
}
// WithLabels merges labels on a lease
func WithLabels(labels map[string]string) Opt {
return func(l *Lease) error {
if l.Labels == nil {
l.Labels = map[string]string{}
}
for k, v := range labels {
l.Labels[k] = v
}
return nil
}
}
// WithExpiration sets an expiration on the lease
func WithExpiration(d time.Duration) Opt {
return func(l *Lease) error {
if l.Labels == nil {
l.Labels = map[string]string{}
}
l.Labels["containerd.io/gc.expire"] = time.Now().Add(d).Format(time.RFC3339)
return nil
}
}

@ -0,0 +1,78 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package namespaces
import (
"context"
"fmt"
"os"
"github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/identifiers"
)
const (
// NamespaceEnvVar is the environment variable key name
NamespaceEnvVar = "CONTAINERD_NAMESPACE"
// Default is the name of the default namespace
Default = "default"
)
type namespaceKey struct{}
// WithNamespace sets a given namespace on the context
func WithNamespace(ctx context.Context, namespace string) context.Context {
ctx = context.WithValue(ctx, namespaceKey{}, namespace) // set our key for namespace
// also store on the grpc and ttrpc headers so it gets picked up by any clients that
// are using this.
return withTTRPCNamespaceHeader(withGRPCNamespaceHeader(ctx, namespace), namespace)
}
// NamespaceFromEnv uses the namespace defined in CONTAINERD_NAMESPACE or
// default
func NamespaceFromEnv(ctx context.Context) context.Context {
namespace := os.Getenv(NamespaceEnvVar)
if namespace == "" {
namespace = Default
}
return WithNamespace(ctx, namespace)
}
// Namespace returns the namespace from the context.
//
// The namespace is not guaranteed to be valid.
func Namespace(ctx context.Context) (string, bool) {
namespace, ok := ctx.Value(namespaceKey{}).(string)
if !ok {
if namespace, ok = fromGRPCHeader(ctx); !ok {
return fromTTRPCHeader(ctx)
}
}
return namespace, ok
}
// NamespaceRequired returns the valid namespace from the context or an error.
func NamespaceRequired(ctx context.Context) (string, error) {
namespace, ok := Namespace(ctx)
if !ok || namespace == "" {
return "", fmt.Errorf("namespace is required: %w", errdefs.ErrFailedPrecondition)
}
if err := identifiers.Validate(namespace); err != nil {
return "", fmt.Errorf("namespace validation: %w", err)
}
return namespace, nil
}

@ -0,0 +1,61 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package namespaces
import (
"context"
"google.golang.org/grpc/metadata"
)
const (
// GRPCHeader defines the header name for specifying a containerd namespace.
GRPCHeader = "containerd-namespace"
)
// NOTE(stevvooe): We can stub this file out if we don't want a grpc dependency here.
func withGRPCNamespaceHeader(ctx context.Context, namespace string) context.Context {
// also store on the grpc headers so it gets picked up by any clients that
// are using this.
nsheader := metadata.Pairs(GRPCHeader, namespace)
md, ok := metadata.FromOutgoingContext(ctx) // merge with outgoing context.
if !ok {
md = nsheader
} else {
// order ensures the latest is first in this list.
md = metadata.Join(nsheader, md)
}
return metadata.NewOutgoingContext(ctx, md)
}
func fromGRPCHeader(ctx context.Context) (string, bool) {
// try to extract for use in grpc servers.
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
// TODO(stevvooe): Check outgoing context?
return "", false
}
values := md[GRPCHeader]
if len(values) == 0 {
return "", false
}
return values[0], true
}

@ -0,0 +1,44 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package namespaces
import "context"
// Store provides introspection about namespaces.
//
// Note that these are slightly different than other objects, which are record
// oriented. A namespace is really just a name and a set of labels. Objects
// that belong to a namespace are returned when the namespace is assigned to a
// given context.
type Store interface {
Create(ctx context.Context, namespace string, labels map[string]string) error
Labels(ctx context.Context, namespace string) (map[string]string, error)
SetLabel(ctx context.Context, namespace, key, value string) error
List(ctx context.Context) ([]string, error)
// Delete removes the namespace. The namespace must be empty to be deleted.
Delete(ctx context.Context, namespace string, opts ...DeleteOpts) error
}
// DeleteInfo specifies information for the deletion of a namespace
type DeleteInfo struct {
// Name of the namespace
Name string
}
// DeleteOpts allows the caller to set options for namespace deletion
type DeleteOpts func(context.Context, *DeleteInfo) error

@ -0,0 +1,51 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package namespaces
import (
"context"
"github.com/containerd/ttrpc"
)
const (
// TTRPCHeader defines the header name for specifying a containerd namespace
TTRPCHeader = "containerd-namespace-ttrpc"
)
func copyMetadata(src ttrpc.MD) ttrpc.MD {
md := ttrpc.MD{}
for k, v := range src {
md[k] = append(md[k], v...)
}
return md
}
func withTTRPCNamespaceHeader(ctx context.Context, namespace string) context.Context {
md, ok := ttrpc.GetMetadata(ctx)
if !ok {
md = ttrpc.MD{}
} else {
md = copyMetadata(md)
}
md.Set(TTRPCHeader, namespace)
return ttrpc.WithMetadata(ctx, md)
}
func fromTTRPCHeader(ctx context.Context) (string, bool) {
return ttrpc.GetMetadataValue(ctx, TTRPCHeader)
}

@ -0,0 +1 @@
*.go text eol=lf

@ -0,0 +1,13 @@
# Binaries for programs and plugins
/bin/
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
coverage.txt

@ -0,0 +1,52 @@
linters:
enable:
- staticcheck
- unconvert
- gofmt
- goimports
- revive
- ineffassign
- vet
- unused
- misspell
disable:
- errcheck
linters-settings:
revive:
ignore-generated-headers: true
rules:
- name: blank-imports
- name: context-as-argument
- name: context-keys-type
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
- name: if-return
- name: increment-decrement
- name: var-naming
arguments: [["UID", "GID"], []]
- name: var-declaration
- name: package-comments
- name: range
- name: receiver-naming
- name: time-naming
- name: unexported-return
- name: indent-error-flow
- name: errorf
- name: empty-block
- name: superfluous-else
- name: unused-parameter
- name: unreachable-code
- name: redefines-builtin-id
issues:
include:
- EXC0002
run:
timeout: 8m
skip-dirs:
- example

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

@ -0,0 +1,180 @@
# Copyright The containerd Authors.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Go command to use for build
GO ?= go
INSTALL ?= install
# Root directory of the project (absolute path).
ROOTDIR=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))
WHALE = "🇩"
ONI = "👹"
# Project binaries.
COMMANDS=protoc-gen-go-ttrpc protoc-gen-gogottrpc
ifdef BUILDTAGS
GO_BUILDTAGS = ${BUILDTAGS}
endif
GO_BUILDTAGS ?=
GO_TAGS=$(if $(GO_BUILDTAGS),-tags "$(strip $(GO_BUILDTAGS))",)
# Project packages.
PACKAGES=$(shell $(GO) list ${GO_TAGS} ./... | grep -v /example)
TESTPACKAGES=$(shell $(GO) list ${GO_TAGS} ./... | grep -v /cmd | grep -v /integration | grep -v /example)
BINPACKAGES=$(addprefix ./cmd/,$(COMMANDS))
#Replaces ":" (*nix), ";" (windows) with newline for easy parsing
GOPATHS=$(shell echo ${GOPATH} | tr ":" "\n" | tr ";" "\n")
TESTFLAGS_RACE=
GO_BUILD_FLAGS=
# See Golang issue re: '-trimpath': https://github.com/golang/go/issues/13809
GO_GCFLAGS=$(shell \
set -- ${GOPATHS}; \
echo "-gcflags=-trimpath=$${1}/src"; \
)
BINARIES=$(addprefix bin/,$(COMMANDS))
# Flags passed to `go test`
TESTFLAGS ?= $(TESTFLAGS_RACE) $(EXTRA_TESTFLAGS)
TESTFLAGS_PARALLEL ?= 8
# Use this to replace `go test` with, for instance, `gotestsum`
GOTEST ?= $(GO) test
.PHONY: clean all AUTHORS build binaries test integration generate protos check-protos coverage ci check help install vendor install-protobuf install-protobuild
.DEFAULT: default
# Forcibly set the default goal to all, in case an include above brought in a rule definition.
.DEFAULT_GOAL := all
all: binaries
check: proto-fmt ## run all linters
@echo "$(WHALE) $@"
GOGC=75 golangci-lint run
ci: check binaries check-protos coverage # coverage-integration ## to be used by the CI
AUTHORS: .mailmap .git/HEAD
git log --format='%aN <%aE>' | sort -fu > $@
generate: protos
@echo "$(WHALE) $@"
@PATH="${ROOTDIR}/bin:${PATH}" $(GO) generate -x ${PACKAGES}
protos: bin/protoc-gen-gogottrpc bin/protoc-gen-go-ttrpc ## generate protobuf
@echo "$(WHALE) $@"
@(PATH="${ROOTDIR}/bin:${PATH}" protobuild --quiet ${PACKAGES})
check-protos: protos ## check if protobufs needs to be generated again
@echo "$(WHALE) $@"
@test -z "$$(git status --short | grep ".pb.go" | tee /dev/stderr)" || \
((git diff | cat) && \
(echo "$(ONI) please run 'make protos' when making changes to proto files" && false))
check-api-descriptors: protos ## check that protobuf changes aren't present.
@echo "$(WHALE) $@"
@test -z "$$(git status --short | grep ".pb.txt" | tee /dev/stderr)" || \
((git diff $$(find . -name '*.pb.txt') | cat) && \
(echo "$(ONI) please run 'make protos' when making changes to proto files and check-in the generated descriptor file changes" && false))
proto-fmt: ## check format of proto files
@echo "$(WHALE) $@"
@test -z "$$(find . -name '*.proto' -type f -exec grep -Hn -e "^ " {} \; | tee /dev/stderr)" || \
(echo "$(ONI) please indent proto files with tabs only" && false)
@test -z "$$(find . -name '*.proto' -type f -exec grep -Hn "Meta meta = " {} \; | grep -v '(gogoproto.nullable) = false' | tee /dev/stderr)" || \
(echo "$(ONI) meta fields in proto files must have option (gogoproto.nullable) = false" && false)
build: ## build the go packages
@echo "$(WHALE) $@"
@$(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} ${EXTRA_FLAGS} ${PACKAGES}
test: ## run tests, except integration tests and tests that require root
@echo "$(WHALE) $@"
@$(GOTEST) ${TESTFLAGS} ${TESTPACKAGES}
integration: ## run integration tests
@echo "$(WHALE) $@"
@cd "${ROOTDIR}/integration" && $(GOTEST) -v ${TESTFLAGS} -parallel ${TESTFLAGS_PARALLEL} .
benchmark: ## run benchmarks tests
@echo "$(WHALE) $@"
@$(GO) test ${TESTFLAGS} -bench . -run Benchmark
FORCE:
define BUILD_BINARY
@echo "$(WHALE) $@"
@$(GO) build ${DEBUG_GO_GCFLAGS} ${GO_GCFLAGS} ${GO_BUILD_FLAGS} -o $@ ${GO_TAGS} ./$<
endef
# Build a binary from a cmd.
bin/%: cmd/% FORCE
$(call BUILD_BINARY)
binaries: $(BINARIES) ## build binaries
@echo "$(WHALE) $@"
clean: ## clean up binaries
@echo "$(WHALE) $@"
@rm -f $(BINARIES)
install: ## install binaries
@echo "$(WHALE) $@ $(BINPACKAGES)"
@$(GO) install $(BINPACKAGES)
install-protobuf:
@echo "$(WHALE) $@"
@script/install-protobuf
install-protobuild:
@echo "$(WHALE) $@"
@$(GO) install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28.1
@$(GO) install github.com/containerd/protobuild@14832ccc41429f5c4f81028e5af08aa233a219cf
coverage: ## generate coverprofiles from the unit tests, except tests that require root
@echo "$(WHALE) $@"
@rm -f coverage.txt
@$(GO) test ${TESTFLAGS} ${TESTPACKAGES} 2> /dev/null
@( for pkg in ${PACKAGES}; do \
$(GO) test ${TESTFLAGS} \
-cover \
-coverprofile=profile.out \
-covermode=atomic $$pkg || exit; \
if [ -f profile.out ]; then \
cat profile.out >> coverage.txt; \
rm profile.out; \
fi; \
done )
vendor: ## ensure all the go.mod/go.sum files are up-to-date
@echo "$(WHALE) $@"
@$(GO) mod tidy
@$(GO) mod verify
verify-vendor: ## verify if all the go.mod/go.sum files are up-to-date
@echo "$(WHALE) $@"
@$(GO) mod tidy
@$(GO) mod verify
@test -z "$$(git status --short | grep "go.sum" | tee /dev/stderr)" || \
((git diff | cat) && \
(echo "$(ONI) make sure to checkin changes after go mod tidy" && false))
help: ## this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort

@ -0,0 +1,240 @@
# Protocol Specification
The ttrpc protocol is client/server protocol to support multiple request streams
over a single connection with lightweight framing. The client represents the
process which initiated the underlying connection and the server is the process
which accepted the connection. The protocol is currently defined as
asymmetrical, with clients sending requests and servers sending responses. Both
clients and servers are able to send stream data. The roles are also used in
determining the stream identifiers, with client initiated streams using odd
number identifiers and server initiated using even number. The protocol may be
extended in the future to support server initiated streams, that is not
supported in the latest version.
## Purpose
The ttrpc protocol is designed to be lightweight and optimized for low latency
and reliable connections between processes on the same host. The protocol does
not include features for handling unreliable connections such as handshakes,
resets, pings, or flow control. The protocol is designed to make low-overhead
implementations as simple as possible. It is not intended as a suitable
replacement for HTTP2/3 over the network.
## Message Frame
Each Message Frame consists of a 10-byte message header followed
by message data. The data length and stream ID are both big-endian
4-byte unsigned integers. The message type is an unsigned 1-byte
integer. The flags are also an unsigned 1-byte integer and
use is defined by the message type.
+---------------------------------------------------------------+
| Data Length (32) |
+---------------------------------------------------------------+
| Stream ID (32) |
+---------------+-----------------------------------------------+
| Msg Type (8) |
+---------------+
| Flags (8) |
+---------------+-----------------------------------------------+
| Data (*) |
+---------------------------------------------------------------+
The Data Length field represents the number of bytes in the Data field. The
total frame size will always be Data Length + 10 bytes. The maximum data length
is 4MB and any larger size should be rejected. Due to the maximum data size
being less than 16MB, the first frame byte should always be zero. This first
byte should be considered reserved for future use.
The Stream ID must be odd for client initiated streams and even for server
initiated streams. Server initiated streams are not currently supported.
## Mesage Types
| Message Type | Name | Description |
|--------------|----------|----------------------------------|
| 0x01 | Request | Initiates stream |
| 0x02 | Response | Final stream data and terminates |
| 0x03 | Data | Stream data |
### Request
The request message is used to initiate stream and send along request data for
properly routing and handling the stream. The stream may indicate unary without
any inbound or outbound stream data with only a response is expected on the
stream. The request may also indicate the stream is still open for more data and
no response is expected until data is finished. If the remote indicates the
stream is closed, the request may be considered non-unary but without anymore
stream data sent. In the case of `remote closed`, the remote still expects to
receive a response or stream data. For compatibility with non streaming clients,
a request with empty flags indicates a unary request.
#### Request Flags
| Flag | Name | Description |
|------|-----------------|--------------------------------------------------|
| 0x01 | `remote closed` | Non-unary, but no more data expected from remote |
| 0x02 | `remote open` | Non-unary, remote is still sending data |
### Response
The response message is used to end a stream with data, an empty response, or
an error. A response message is the only expected message after a unary request.
A non-unary request does not require a response message if the server is sending
back stream data. A non-unary stream may return a single response message but no
other stream data may follow.
#### Response Flags
No response flags are defined at this time, flags should be empty.
### Data
The data message is used to send data on an already initialized stream. Either
client or server may send data. A data message is not allowed on a unary stream.
A data message should not be sent after indicating `remote closed` to the peer.
The last data message on a stream must set the `remote closed` flag.
The `no data` flag is used to indicate that the data message does not include
any data. This is normally used with the `remote closed` flag to indicate the
stream is now closed without transmitting any data. Since ttrpc normally
transmits a single object per message, a zero length data message may be
interpreted as an empty object. For example, transmitting the number zero as a
protobuf message ends up with a data length of zero, but the message is still
considered data and should be processed.
#### Data Flags
| Flag | Name | Description |
|------|-----------------|-----------------------------------|
| 0x01 | `remote closed` | No more data expected from remote |
| 0x04 | `no data` | This message does not have data |
## Streaming
All ttrpc requests use streams to transfer data. Unary streams will only have
two messages sent per stream, a request from a client and a response from the
server. Non-unary streams, however, may send any numbers of messages from the
client and the server. This makes stream management more complicated than unary
streams since both client and server need to track additional state. To keep
this management as simple as possible, ttrpc minimizes the number of states and
uses two flags instead of control frames. Each stream has two states while a
stream is still alive: `local closed` and `remote closed`. Each peer considers
local and remote from their own perspective and sets flags from the other peer's
perspective. For example, if a client sends a data frame with the
`remote closed` flag, that is indicating that the client is now `local closed`
and the server will be `remote closed`. A unary operation does not need to send
these flags since each received message always indicates `remote closed`. Once a
peer is both `local closed` and `remote closed`, the stream is considered
finished and may be cleaned up.
Due to the asymmetric nature of the current protocol, a client should
always be in the `local closed` state before `remote closed` and a server should
always be in the `remote closed` state before `local closed`. This happens
because the client is always initiating requests and a client always expects a
final response back from a server to indicate the initiated request has been
fulfilled. This may mean server sends a final empty response to finish a stream
even after it has already completed sending data before the client.
### Unary State Diagram
+--------+ +--------+
| Client | | Server |
+---+----+ +----+---+
| +---------+ |
local >---------------+ Request +--------------------> remote
closed | +---------+ | closed
| |
| +----------+ |
finished <--------------+ Response +--------------------< finished
| +----------+ |
| |
### Non-Unary State Diagrams
RC: `remote closed` flag
RO: `remote open` flag
+--------+ +--------+
| Client | | Server |
+---+----+ +----+---+
| +--------------+ |
>-------------+ Request [RO] +----------------->
| +--------------+ |
| |
| +------+ |
>-----------------+ Data +--------------------->
| +------+ |
| |
| +-----------+ |
local >---------------+ Data [RC] +------------------> remote
closed | +-----------+ | closed
| |
| +----------+ |
finished <--------------+ Response +--------------------< finished
| +----------+ |
| |
+--------+ +--------+
| Client | | Server |
+---+----+ +----+---+
| +--------------+ |
local >-------------+ Request [RC] +-----------------> remote
closed | +--------------+ | closed
| |
| +------+ |
<-----------------+ Data +---------------------<
| +------+ |
| |
| +-----------+ |
finished <---------------+ Data [RC] +------------------< finished
| +-----------+ |
| |
+--------+ +--------+
| Client | | Server |
+---+----+ +----+---+
| +--------------+ |
>-------------+ Request [RO] +----------------->
| +--------------+ |
| |
| +------+ |
>-----------------+ Data +--------------------->
| +------+ |
| |
| +------+ |
<-----------------+ Data +---------------------<
| +------+ |
| |
| +------+ |
>-----------------+ Data +--------------------->
| +------+ |
| |
| +-----------+ |
local >---------------+ Data [RC] +------------------> remote
closed | +-----------+ | closed
| |
| +------+ |
<-----------------+ Data +---------------------<
| +------+ |
| |
| +-----------+ |
finished <---------------+ Data [RC] +------------------< finished
| +-----------+ |
| |
## RPC
While this protocol is defined primarily to support Remote Procedure Calls, the
protocol does not define the request and response types beyond the messages
defined in the protocol. The implementation provides a default protobuf
definition of request and response which may be used for cross language rpc.
All implementations should at least define a request type which support
routing by procedure name and a response type which supports call status.
## Version History
| Version | Features |
|---------|---------------------|
| 1.0 | Unary requests only |
| 1.2 | Streaming support |

@ -0,0 +1,28 @@
version = "2"
generators = ["go"]
# Control protoc include paths. Below are usually some good defaults, but feel
# free to try it without them if it works for your project.
[includes]
# Include paths that will be added before all others. Typically, you want to
# treat the root of the project as an include, but this may not be necessary.
before = ["."]
# Paths that will be added untouched to the end of the includes. We use
# `/usr/local/include` to pickup the common install location of protobuf.
# This is the default.
after = ["/usr/local/include"]
# This section maps protobuf imports to Go packages. These will become
# `-M` directives in the call to the go protobuf generator.
[packages]
"google/protobuf/any.proto" = "github.com/gogo/protobuf/types"
"proto/status.proto" = "google.golang.org/genproto/googleapis/rpc/status"
[[overrides]]
# enable ttrpc and disable fieldpath and grpc for the shim
prefixes = ["github.com/containerd/ttrpc/integration/streaming"]
generators = ["go", "go-ttrpc"]
[overrides.parameters.go-ttrpc]
prefix = "TTRPC"

@ -0,0 +1,59 @@
# ttrpc
[![Build Status](https://github.com/containerd/ttrpc/workflows/CI/badge.svg)](https://github.com/containerd/ttrpc/actions?query=workflow%3ACI)
GRPC for low-memory environments.
The existing grpc-go project requires a lot of memory overhead for importing
packages and at runtime. While this is great for many services with low density
requirements, this can be a problem when running a large number of services on
a single machine or on a machine with a small amount of memory.
Using the same GRPC definitions, this project reduces the binary size and
protocol overhead required. We do this by eliding the `net/http`, `net/http2`
and `grpc` package used by grpc replacing it with a lightweight framing
protocol. The result are smaller binaries that use less resident memory with
the same ease of use as GRPC.
Please note that while this project supports generating either end of the
protocol, the generated service definitions will be incompatible with regular
GRPC services, as they do not speak the same protocol.
# Protocol
See the [protocol specification](./PROTOCOL.md).
# Usage
Create a gogo vanity binary (see
[`cmd/protoc-gen-gogottrpc/main.go`](cmd/protoc-gen-gogottrpc/main.go) for an
example with the ttrpc plugin enabled.
It's recommended to use [`protobuild`](https://github.com/containerd/protobuild)
to build the protobufs for this project, but this will work with protoc
directly, if required.
# Differences from GRPC
- The protocol stack has been replaced with a lighter protocol that doesn't
require http, http2 and tls.
- The client and server interface are identical whereas in GRPC there is a
client and server interface that are different.
- The Go stdlib context package is used instead.
# Status
TODO:
- [ ] Add testing under concurrent load to ensure
- [ ] Verify connection error handling
# Project details
ttrpc is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
As a containerd sub-project, you will find the:
* [Project governance](https://github.com/containerd/project/blob/main/GOVERNANCE.md),
* [Maintainers](https://github.com/containerd/project/blob/main/MAINTAINERS),
* and [Contributing guidelines](https://github.com/containerd/project/blob/main/CONTRIBUTING.md)
information in our [`containerd/project`](https://github.com/containerd/project) repository.

@ -0,0 +1,182 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import (
"bufio"
"encoding/binary"
"fmt"
"io"
"net"
"sync"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
messageHeaderLength = 10
messageLengthMax = 4 << 20
)
type messageType uint8
const (
messageTypeRequest messageType = 0x1
messageTypeResponse messageType = 0x2
messageTypeData messageType = 0x3
)
func (mt messageType) String() string {
switch mt {
case messageTypeRequest:
return "request"
case messageTypeResponse:
return "response"
case messageTypeData:
return "data"
default:
return "unknown"
}
}
const (
flagRemoteClosed uint8 = 0x1
flagRemoteOpen uint8 = 0x2
flagNoData uint8 = 0x4
)
// messageHeader represents the fixed-length message header of 10 bytes sent
// with every request.
type messageHeader struct {
Length uint32 // length excluding this header. b[:4]
StreamID uint32 // identifies which request stream message is a part of. b[4:8]
Type messageType // message type b[8]
Flags uint8 // type specific flags b[9]
}
func readMessageHeader(p []byte, r io.Reader) (messageHeader, error) {
_, err := io.ReadFull(r, p[:messageHeaderLength])
if err != nil {
return messageHeader{}, err
}
return messageHeader{
Length: binary.BigEndian.Uint32(p[:4]),
StreamID: binary.BigEndian.Uint32(p[4:8]),
Type: messageType(p[8]),
Flags: p[9],
}, nil
}
func writeMessageHeader(w io.Writer, p []byte, mh messageHeader) error {
binary.BigEndian.PutUint32(p[:4], mh.Length)
binary.BigEndian.PutUint32(p[4:8], mh.StreamID)
p[8] = byte(mh.Type)
p[9] = mh.Flags
_, err := w.Write(p[:])
return err
}
var buffers sync.Pool
type channel struct {
conn net.Conn
bw *bufio.Writer
br *bufio.Reader
hrbuf [messageHeaderLength]byte // avoid alloc when reading header
hwbuf [messageHeaderLength]byte
}
func newChannel(conn net.Conn) *channel {
return &channel{
conn: conn,
bw: bufio.NewWriter(conn),
br: bufio.NewReader(conn),
}
}
// recv a message from the channel. The returned buffer contains the message.
//
// If a valid grpc status is returned, the message header
// returned will be valid and caller should send that along to
// the correct consumer. The bytes on the underlying channel
// will be discarded.
func (ch *channel) recv() (messageHeader, []byte, error) {
mh, err := readMessageHeader(ch.hrbuf[:], ch.br)
if err != nil {
return messageHeader{}, nil, err
}
if mh.Length > uint32(messageLengthMax) {
if _, err := ch.br.Discard(int(mh.Length)); err != nil {
return mh, nil, fmt.Errorf("failed to discard after receiving oversized message: %w", err)
}
return mh, nil, status.Errorf(codes.ResourceExhausted, "message length %v exceed maximum message size of %v", mh.Length, messageLengthMax)
}
var p []byte
if mh.Length > 0 {
p = ch.getmbuf(int(mh.Length))
if _, err := io.ReadFull(ch.br, p); err != nil {
return messageHeader{}, nil, fmt.Errorf("failed reading message: %w", err)
}
}
return mh, p, nil
}
func (ch *channel) send(streamID uint32, t messageType, flags uint8, p []byte) error {
// TODO: Error on send rather than on recv
//if len(p) > messageLengthMax {
// return status.Errorf(codes.InvalidArgument, "refusing to send, message length %v exceed maximum message size of %v", len(p), messageLengthMax)
//}
if err := writeMessageHeader(ch.bw, ch.hwbuf[:], messageHeader{Length: uint32(len(p)), StreamID: streamID, Type: t, Flags: flags}); err != nil {
return err
}
if len(p) > 0 {
_, err := ch.bw.Write(p)
if err != nil {
return err
}
}
return ch.bw.Flush()
}
func (ch *channel) getmbuf(size int) []byte {
// we can't use the standard New method on pool because we want to allocate
// based on size.
b, ok := buffers.Get().(*[]byte)
if !ok || cap(*b) < size {
// TODO(stevvooe): It may be better to allocate these in fixed length
// buckets to reduce fragmentation but its not clear that would help
// with performance. An ilogb approach or similar would work well.
bb := make([]byte, size)
b = &bb
} else {
*b = (*b)[:size]
}
return *b
}
func (ch *channel) putmbuf(p []byte) {
buffers.Put(&p)
}

@ -0,0 +1,512 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import (
"context"
"errors"
"fmt"
"io"
"net"
"strings"
"sync"
"syscall"
"time"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Client for a ttrpc server
type Client struct {
codec codec
conn net.Conn
channel *channel
streamLock sync.RWMutex
streams map[streamID]*stream
nextStreamID streamID
sendLock sync.Mutex
ctx context.Context
closed func()
closeOnce sync.Once
userCloseFunc func()
userCloseWaitCh chan struct{}
interceptor UnaryClientInterceptor
}
// ClientOpts configures a client
type ClientOpts func(c *Client)
// WithOnClose sets the close func whenever the client's Close() method is called
func WithOnClose(onClose func()) ClientOpts {
return func(c *Client) {
c.userCloseFunc = onClose
}
}
// WithUnaryClientInterceptor sets the provided client interceptor
func WithUnaryClientInterceptor(i UnaryClientInterceptor) ClientOpts {
return func(c *Client) {
c.interceptor = i
}
}
// NewClient creates a new ttrpc client using the given connection
func NewClient(conn net.Conn, opts ...ClientOpts) *Client {
ctx, cancel := context.WithCancel(context.Background())
channel := newChannel(conn)
c := &Client{
codec: codec{},
conn: conn,
channel: channel,
streams: make(map[streamID]*stream),
nextStreamID: 1,
closed: cancel,
ctx: ctx,
userCloseFunc: func() {},
userCloseWaitCh: make(chan struct{}),
interceptor: defaultClientInterceptor,
}
for _, o := range opts {
o(c)
}
go c.run()
return c
}
func (c *Client) send(sid uint32, mt messageType, flags uint8, b []byte) error {
c.sendLock.Lock()
defer c.sendLock.Unlock()
return c.channel.send(sid, mt, flags, b)
}
// Call makes a unary request and returns with response
func (c *Client) Call(ctx context.Context, service, method string, req, resp interface{}) error {
payload, err := c.codec.Marshal(req)
if err != nil {
return err
}
var (
creq = &Request{
Service: service,
Method: method,
Payload: payload,
// TODO: metadata from context
}
cresp = &Response{}
)
if metadata, ok := GetMetadata(ctx); ok {
metadata.setRequest(creq)
}
if dl, ok := ctx.Deadline(); ok {
creq.TimeoutNano = time.Until(dl).Nanoseconds()
}
info := &UnaryClientInfo{
FullMethod: fullPath(service, method),
}
if err := c.interceptor(ctx, creq, cresp, info, c.dispatch); err != nil {
return err
}
if err := c.codec.Unmarshal(cresp.Payload, resp); err != nil {
return err
}
if cresp.Status != nil && cresp.Status.Code != int32(codes.OK) {
return status.ErrorProto(cresp.Status)
}
return nil
}
// StreamDesc describes the stream properties, whether the stream has
// a streaming client, a streaming server, or both
type StreamDesc struct {
StreamingClient bool
StreamingServer bool
}
// ClientStream is used to send or recv messages on the underlying stream
type ClientStream interface {
CloseSend() error
SendMsg(m interface{}) error
RecvMsg(m interface{}) error
}
type clientStream struct {
ctx context.Context
s *stream
c *Client
desc *StreamDesc
localClosed bool
remoteClosed bool
}
func (cs *clientStream) CloseSend() error {
if !cs.desc.StreamingClient {
return fmt.Errorf("%w: cannot close non-streaming client", ErrProtocol)
}
if cs.localClosed {
return ErrStreamClosed
}
err := cs.s.send(messageTypeData, flagRemoteClosed|flagNoData, nil)
if err != nil {
return filterCloseErr(err)
}
cs.localClosed = true
return nil
}
func (cs *clientStream) SendMsg(m interface{}) error {
if !cs.desc.StreamingClient {
return fmt.Errorf("%w: cannot send data from non-streaming client", ErrProtocol)
}
if cs.localClosed {
return ErrStreamClosed
}
var (
payload []byte
err error
)
if m != nil {
payload, err = cs.c.codec.Marshal(m)
if err != nil {
return err
}
}
err = cs.s.send(messageTypeData, 0, payload)
if err != nil {
return filterCloseErr(err)
}
return nil
}
func (cs *clientStream) RecvMsg(m interface{}) error {
if cs.remoteClosed {
return io.EOF
}
var msg *streamMessage
select {
case <-cs.ctx.Done():
return cs.ctx.Err()
case <-cs.s.recvClose:
// If recv has a pending message, process that first
select {
case msg = <-cs.s.recv:
default:
return cs.s.recvErr
}
case msg = <-cs.s.recv:
}
if msg.header.Type == messageTypeResponse {
resp := &Response{}
err := proto.Unmarshal(msg.payload[:msg.header.Length], resp)
// return the payload buffer for reuse
cs.c.channel.putmbuf(msg.payload)
if err != nil {
return err
}
if err := cs.c.codec.Unmarshal(resp.Payload, m); err != nil {
return err
}
if resp.Status != nil && resp.Status.Code != int32(codes.OK) {
return status.ErrorProto(resp.Status)
}
cs.c.deleteStream(cs.s)
cs.remoteClosed = true
return nil
} else if msg.header.Type == messageTypeData {
if !cs.desc.StreamingServer {
cs.c.deleteStream(cs.s)
cs.remoteClosed = true
return fmt.Errorf("received data from non-streaming server: %w", ErrProtocol)
}
if msg.header.Flags&flagRemoteClosed == flagRemoteClosed {
cs.c.deleteStream(cs.s)
cs.remoteClosed = true
if msg.header.Flags&flagNoData == flagNoData {
return io.EOF
}
}
err := cs.c.codec.Unmarshal(msg.payload[:msg.header.Length], m)
cs.c.channel.putmbuf(msg.payload)
if err != nil {
return err
}
return nil
}
return fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol)
}
// Close closes the ttrpc connection and underlying connection
func (c *Client) Close() error {
c.closeOnce.Do(func() {
c.closed()
c.conn.Close()
})
return nil
}
// UserOnCloseWait is used to blocks untils the user's on-close callback
// finishes.
func (c *Client) UserOnCloseWait(ctx context.Context) error {
select {
case <-c.userCloseWaitCh:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
func (c *Client) run() {
err := c.receiveLoop()
c.Close()
c.cleanupStreams(err)
c.userCloseFunc()
close(c.userCloseWaitCh)
}
func (c *Client) receiveLoop() error {
for {
select {
case <-c.ctx.Done():
return ErrClosed
default:
var (
msg = &streamMessage{}
err error
)
msg.header, msg.payload, err = c.channel.recv()
if err != nil {
_, ok := status.FromError(err)
if !ok {
// treat all errors that are not an rpc status as terminal.
// all others poison the connection.
return filterCloseErr(err)
}
}
sid := streamID(msg.header.StreamID)
s := c.getStream(sid)
if s == nil {
logrus.WithField("stream", sid).Errorf("ttrpc: received message on inactive stream")
continue
}
if err != nil {
s.closeWithError(err)
} else {
if err := s.receive(c.ctx, msg); err != nil {
logrus.WithError(err).WithField("stream", sid).Errorf("ttrpc: failed to handle message")
}
}
}
}
}
// createStream creates a new stream and registers it with the client
// Introduce stream types for multiple or single response
func (c *Client) createStream(flags uint8, b []byte) (*stream, error) {
c.streamLock.Lock()
// Check if closed since lock acquired to prevent adding
// anything after cleanup completes
select {
case <-c.ctx.Done():
c.streamLock.Unlock()
return nil, ErrClosed
default:
}
// Stream ID should be allocated at same time
s := newStream(c.nextStreamID, c)
c.streams[s.id] = s
c.nextStreamID = c.nextStreamID + 2
c.sendLock.Lock()
defer c.sendLock.Unlock()
c.streamLock.Unlock()
if err := c.channel.send(uint32(s.id), messageTypeRequest, flags, b); err != nil {
return s, filterCloseErr(err)
}
return s, nil
}
func (c *Client) deleteStream(s *stream) {
c.streamLock.Lock()
delete(c.streams, s.id)
c.streamLock.Unlock()
s.closeWithError(nil)
}
func (c *Client) getStream(sid streamID) *stream {
c.streamLock.RLock()
s := c.streams[sid]
c.streamLock.RUnlock()
return s
}
func (c *Client) cleanupStreams(err error) {
c.streamLock.Lock()
defer c.streamLock.Unlock()
for sid, s := range c.streams {
s.closeWithError(err)
delete(c.streams, sid)
}
}
// filterCloseErr rewrites EOF and EPIPE errors to ErrClosed. Use when
// returning from call or handling errors from main read loop.
//
// This purposely ignores errors with a wrapped cause.
func filterCloseErr(err error) error {
switch {
case err == nil:
return nil
case err == io.EOF:
return ErrClosed
case errors.Is(err, io.ErrClosedPipe):
return ErrClosed
case errors.Is(err, io.EOF):
return ErrClosed
case strings.Contains(err.Error(), "use of closed network connection"):
return ErrClosed
default:
// if we have an epipe on a write or econnreset on a read , we cast to errclosed
var oerr *net.OpError
if errors.As(err, &oerr) {
if (oerr.Op == "write" && errors.Is(err, syscall.EPIPE)) ||
(oerr.Op == "read" && errors.Is(err, syscall.ECONNRESET)) {
return ErrClosed
}
}
}
return err
}
// NewStream creates a new stream with the given stream descriptor to the
// specified service and method. If not a streaming client, the request object
// may be provided.
func (c *Client) NewStream(ctx context.Context, desc *StreamDesc, service, method string, req interface{}) (ClientStream, error) {
var payload []byte
if req != nil {
var err error
payload, err = c.codec.Marshal(req)
if err != nil {
return nil, err
}
}
request := &Request{
Service: service,
Method: method,
Payload: payload,
// TODO: metadata from context
}
p, err := c.codec.Marshal(request)
if err != nil {
return nil, err
}
var flags uint8
if desc.StreamingClient {
flags = flagRemoteOpen
} else {
flags = flagRemoteClosed
}
s, err := c.createStream(flags, p)
if err != nil {
return nil, err
}
return &clientStream{
ctx: ctx,
s: s,
c: c,
desc: desc,
}, nil
}
func (c *Client) dispatch(ctx context.Context, req *Request, resp *Response) error {
p, err := c.codec.Marshal(req)
if err != nil {
return err
}
s, err := c.createStream(0, p)
if err != nil {
return err
}
defer c.deleteStream(s)
var msg *streamMessage
select {
case <-ctx.Done():
return ctx.Err()
case <-c.ctx.Done():
return ErrClosed
case <-s.recvClose:
// If recv has a pending message, process that first
select {
case msg = <-s.recv:
default:
return s.recvErr
}
case msg = <-s.recv:
}
if msg.header.Type == messageTypeResponse {
err = proto.Unmarshal(msg.payload[:msg.header.Length], resp)
} else {
err = fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol)
}
// return the payload buffer for reuse
c.channel.putmbuf(msg.payload)
return err
}

@ -0,0 +1,43 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import (
"fmt"
"google.golang.org/protobuf/proto"
)
type codec struct{}
func (c codec) Marshal(msg interface{}) ([]byte, error) {
switch v := msg.(type) {
case proto.Message:
return proto.Marshal(v)
default:
return nil, fmt.Errorf("ttrpc: cannot marshal unknown type: %T", msg)
}
}
func (c codec) Unmarshal(p []byte, msg interface{}) error {
switch v := msg.(type) {
case proto.Message:
return proto.Unmarshal(p, v)
default:
return fmt.Errorf("ttrpc: cannot unmarshal into unknown type: %T", msg)
}
}

@ -0,0 +1,52 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import "errors"
type serverConfig struct {
handshaker Handshaker
interceptor UnaryServerInterceptor
}
// ServerOpt for configuring a ttrpc server
type ServerOpt func(*serverConfig) error
// WithServerHandshaker can be passed to NewServer to ensure that the
// handshaker is called before every connection attempt.
//
// Only one handshaker is allowed per server.
func WithServerHandshaker(handshaker Handshaker) ServerOpt {
return func(c *serverConfig) error {
if c.handshaker != nil {
return errors.New("only one handshaker allowed per server")
}
c.handshaker = handshaker
return nil
}
}
// WithUnaryServerInterceptor sets the provided interceptor on the server
func WithUnaryServerInterceptor(i UnaryServerInterceptor) ServerOpt {
return func(c *serverConfig) error {
if c.interceptor != nil {
return errors.New("only one interceptor allowed per server")
}
c.interceptor = i
return nil
}
}

@ -0,0 +1,23 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
package ttrpc defines and implements a low level simple transfer protocol
optimized for low latency and reliable connections between processes on the same
host. The protocol uses simple framing for sending requests, responses, and data
using multiple streams.
*/
package ttrpc

@ -0,0 +1,34 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import "errors"
var (
// ErrProtocol is a general error in the handling the protocol.
ErrProtocol = errors.New("protocol error")
// ErrClosed is returned by client methods when the underlying connection is
// closed.
ErrClosed = errors.New("ttrpc: closed")
// ErrServerClosed is returned when the Server has closed its connection.
ErrServerClosed = errors.New("ttrpc: server closed")
// ErrStreamClosed is when the streaming connection is closed.
ErrStreamClosed = errors.New("ttrpc: stream closed")
)

@ -0,0 +1,50 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import (
"context"
"net"
)
// Handshaker defines the interface for connection handshakes performed on the
// server or client when first connecting.
type Handshaker interface {
// Handshake should confirm or decorate a connection that may be incoming
// to a server or outgoing from a client.
//
// If this returns without an error, the caller should use the connection
// in place of the original connection.
//
// The second return value can contain credential specific data, such as
// unix socket credentials or TLS information.
//
// While we currently only have implementations on the server-side, this
// interface should be sufficient to implement similar handshakes on the
// client-side.
Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error)
}
type handshakerFunc func(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error)
func (fn handshakerFunc) Handshake(ctx context.Context, conn net.Conn) (net.Conn, interface{}, error) {
return fn(ctx, conn)
}
func noopHandshake(_ context.Context, conn net.Conn) (net.Conn, interface{}, error) {
return conn, nil, nil
}

@ -0,0 +1,65 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import "context"
// UnaryServerInfo provides information about the server request
type UnaryServerInfo struct {
FullMethod string
}
// UnaryClientInfo provides information about the client request
type UnaryClientInfo struct {
FullMethod string
}
// StreamServerInfo provides information about the server request
type StreamServerInfo struct {
FullMethod string
StreamingClient bool
StreamingServer bool
}
// Unmarshaler contains the server request data and allows it to be unmarshaled
// into a concrete type
type Unmarshaler func(interface{}) error
// Invoker invokes the client's request and response from the ttrpc server
type Invoker func(context.Context, *Request, *Response) error
// UnaryServerInterceptor specifies the interceptor function for server request/response
type UnaryServerInterceptor func(context.Context, Unmarshaler, *UnaryServerInfo, Method) (interface{}, error)
// UnaryClientInterceptor specifies the interceptor function for client request/response
type UnaryClientInterceptor func(context.Context, *Request, *Response, *UnaryClientInfo, Invoker) error
func defaultServerInterceptor(ctx context.Context, unmarshal Unmarshaler, _ *UnaryServerInfo, method Method) (interface{}, error) {
return method(ctx, unmarshal)
}
func defaultClientInterceptor(ctx context.Context, req *Request, resp *Response, _ *UnaryClientInfo, invoker Invoker) error {
return invoker(ctx, req, resp)
}
type StreamServerInterceptor func(context.Context, StreamServer, *StreamServerInfo, StreamHandler) (interface{}, error)
func defaultStreamServerInterceptor(ctx context.Context, ss StreamServer, _ *StreamServerInfo, stream StreamHandler) (interface{}, error) {
return stream(ctx, ss)
}
type StreamClientInterceptor func(context.Context)

@ -0,0 +1,107 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import (
"context"
"strings"
)
// MD is the user type for ttrpc metadata
type MD map[string][]string
// Get returns the metadata for a given key when they exist.
// If there is no metadata, a nil slice and false are returned.
func (m MD) Get(key string) ([]string, bool) {
key = strings.ToLower(key)
list, ok := m[key]
if !ok || len(list) == 0 {
return nil, false
}
return list, true
}
// Set sets the provided values for a given key.
// The values will overwrite any existing values.
// If no values provided, a key will be deleted.
func (m MD) Set(key string, values ...string) {
key = strings.ToLower(key)
if len(values) == 0 {
delete(m, key)
return
}
m[key] = values
}
// Append appends additional values to the given key.
func (m MD) Append(key string, values ...string) {
key = strings.ToLower(key)
if len(values) == 0 {
return
}
current, ok := m[key]
if ok {
m.Set(key, append(current, values...)...)
} else {
m.Set(key, values...)
}
}
func (m MD) setRequest(r *Request) {
for k, values := range m {
for _, v := range values {
r.Metadata = append(r.Metadata, &KeyValue{
Key: k,
Value: v,
})
}
}
}
func (m MD) fromRequest(r *Request) {
for _, kv := range r.Metadata {
m[kv.Key] = append(m[kv.Key], kv.Value)
}
}
type metadataKey struct{}
// GetMetadata retrieves metadata from context.Context (previously attached with WithMetadata)
func GetMetadata(ctx context.Context) (MD, bool) {
metadata, ok := ctx.Value(metadataKey{}).(MD)
return metadata, ok
}
// GetMetadataValue gets a specific metadata value by name from context.Context
func GetMetadataValue(ctx context.Context, name string) (string, bool) {
metadata, ok := GetMetadata(ctx)
if !ok {
return "", false
}
if list, ok := metadata.Get(name); ok {
return list[0], true
}
return "", false
}
// WithMetadata attaches metadata map to a context.Context
func WithMetadata(ctx context.Context, md MD) context.Context {
return context.WithValue(ctx, metadataKey{}, md)
}

@ -0,0 +1,396 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.20.1
// source: github.com/containerd/ttrpc/request.proto
package ttrpc
import (
status "google.golang.org/genproto/googleapis/rpc/status"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Request struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Service string `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"`
Method string `protobuf:"bytes,2,opt,name=method,proto3" json:"method,omitempty"`
Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"`
TimeoutNano int64 `protobuf:"varint,4,opt,name=timeout_nano,json=timeoutNano,proto3" json:"timeout_nano,omitempty"`
Metadata []*KeyValue `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty"`
}
func (x *Request) Reset() {
*x = Request{}
if protoimpl.UnsafeEnabled {
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Request) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Request) ProtoMessage() {}
func (x *Request) ProtoReflect() protoreflect.Message {
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Request.ProtoReflect.Descriptor instead.
func (*Request) Descriptor() ([]byte, []int) {
return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{0}
}
func (x *Request) GetService() string {
if x != nil {
return x.Service
}
return ""
}
func (x *Request) GetMethod() string {
if x != nil {
return x.Method
}
return ""
}
func (x *Request) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
func (x *Request) GetTimeoutNano() int64 {
if x != nil {
return x.TimeoutNano
}
return 0
}
func (x *Request) GetMetadata() []*KeyValue {
if x != nil {
return x.Metadata
}
return nil
}
type Response struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Status *status.Status `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"`
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
}
func (x *Response) Reset() {
*x = Response{}
if protoimpl.UnsafeEnabled {
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Response) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Response) ProtoMessage() {}
func (x *Response) ProtoReflect() protoreflect.Message {
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Response.ProtoReflect.Descriptor instead.
func (*Response) Descriptor() ([]byte, []int) {
return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{1}
}
func (x *Response) GetStatus() *status.Status {
if x != nil {
return x.Status
}
return nil
}
func (x *Response) GetPayload() []byte {
if x != nil {
return x.Payload
}
return nil
}
type StringList struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
List []string `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"`
}
func (x *StringList) Reset() {
*x = StringList{}
if protoimpl.UnsafeEnabled {
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *StringList) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*StringList) ProtoMessage() {}
func (x *StringList) ProtoReflect() protoreflect.Message {
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use StringList.ProtoReflect.Descriptor instead.
func (*StringList) Descriptor() ([]byte, []int) {
return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{2}
}
func (x *StringList) GetList() []string {
if x != nil {
return x.List
}
return nil
}
type KeyValue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"`
}
func (x *KeyValue) Reset() {
*x = KeyValue{}
if protoimpl.UnsafeEnabled {
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *KeyValue) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*KeyValue) ProtoMessage() {}
func (x *KeyValue) ProtoReflect() protoreflect.Message {
mi := &file_github_com_containerd_ttrpc_request_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use KeyValue.ProtoReflect.Descriptor instead.
func (*KeyValue) Descriptor() ([]byte, []int) {
return file_github_com_containerd_ttrpc_request_proto_rawDescGZIP(), []int{3}
}
func (x *KeyValue) GetKey() string {
if x != nil {
return x.Key
}
return ""
}
func (x *KeyValue) GetValue() string {
if x != nil {
return x.Value
}
return ""
}
var File_github_com_containerd_ttrpc_request_proto protoreflect.FileDescriptor
var file_github_com_containerd_ttrpc_request_proto_rawDesc = []byte{
0x0a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e,
0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2f, 0x72, 0x65,
0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x74, 0x74, 0x72,
0x70, 0x63, 0x1a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa5, 0x01, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06,
0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65,
0x74, 0x68, 0x6f, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18,
0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x21,
0x0a, 0x0c, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x18, 0x04,
0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x4e, 0x61, 0x6e,
0x6f, 0x12, 0x2b, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20,
0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4b, 0x65, 0x79, 0x56,
0x61, 0x6c, 0x75, 0x65, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x45,
0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1f, 0x0a, 0x06, 0x73, 0x74,
0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x07, 0x2e, 0x53, 0x74, 0x61,
0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x70,
0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, 0x61,
0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x20, 0x0a, 0x0a, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x4c,
0x69, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28,
0x09, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x32, 0x0a, 0x08, 0x4b, 0x65, 0x79, 0x56, 0x61,
0x6c, 0x75, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x1d, 0x5a, 0x1b, 0x67,
0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69,
0x6e, 0x65, 0x72, 0x64, 0x2f, 0x74, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
0x6f, 0x33,
}
var (
file_github_com_containerd_ttrpc_request_proto_rawDescOnce sync.Once
file_github_com_containerd_ttrpc_request_proto_rawDescData = file_github_com_containerd_ttrpc_request_proto_rawDesc
)
func file_github_com_containerd_ttrpc_request_proto_rawDescGZIP() []byte {
file_github_com_containerd_ttrpc_request_proto_rawDescOnce.Do(func() {
file_github_com_containerd_ttrpc_request_proto_rawDescData = protoimpl.X.CompressGZIP(file_github_com_containerd_ttrpc_request_proto_rawDescData)
})
return file_github_com_containerd_ttrpc_request_proto_rawDescData
}
var file_github_com_containerd_ttrpc_request_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_github_com_containerd_ttrpc_request_proto_goTypes = []interface{}{
(*Request)(nil), // 0: ttrpc.Request
(*Response)(nil), // 1: ttrpc.Response
(*StringList)(nil), // 2: ttrpc.StringList
(*KeyValue)(nil), // 3: ttrpc.KeyValue
(*status.Status)(nil), // 4: Status
}
var file_github_com_containerd_ttrpc_request_proto_depIdxs = []int32{
3, // 0: ttrpc.Request.metadata:type_name -> ttrpc.KeyValue
4, // 1: ttrpc.Response.status:type_name -> Status
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_github_com_containerd_ttrpc_request_proto_init() }
func file_github_com_containerd_ttrpc_request_proto_init() {
if File_github_com_containerd_ttrpc_request_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_github_com_containerd_ttrpc_request_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Request); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_github_com_containerd_ttrpc_request_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Response); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_github_com_containerd_ttrpc_request_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*StringList); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_github_com_containerd_ttrpc_request_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*KeyValue); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_github_com_containerd_ttrpc_request_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_github_com_containerd_ttrpc_request_proto_goTypes,
DependencyIndexes: file_github_com_containerd_ttrpc_request_proto_depIdxs,
MessageInfos: file_github_com_containerd_ttrpc_request_proto_msgTypes,
}.Build()
File_github_com_containerd_ttrpc_request_proto = out.File
file_github_com_containerd_ttrpc_request_proto_rawDesc = nil
file_github_com_containerd_ttrpc_request_proto_goTypes = nil
file_github_com_containerd_ttrpc_request_proto_depIdxs = nil
}

@ -0,0 +1,29 @@
syntax = "proto3";
package ttrpc;
import "proto/status.proto";
option go_package = "github.com/containerd/ttrpc";
message Request {
string service = 1;
string method = 2;
bytes payload = 3;
int64 timeout_nano = 4;
repeated KeyValue metadata = 5;
}
message Response {
Status status = 1;
bytes payload = 2;
}
message StringList {
repeated string list = 1;
}
message KeyValue {
string key = 1;
string value = 2;
}

@ -0,0 +1,579 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import (
"context"
"errors"
"io"
"math/rand"
"net"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type Server struct {
config *serverConfig
services *serviceSet
codec codec
mu sync.Mutex
listeners map[net.Listener]struct{}
connections map[*serverConn]struct{} // all connections to current state
done chan struct{} // marks point at which we stop serving requests
}
func NewServer(opts ...ServerOpt) (*Server, error) {
config := &serverConfig{}
for _, opt := range opts {
if err := opt(config); err != nil {
return nil, err
}
}
if config.interceptor == nil {
config.interceptor = defaultServerInterceptor
}
return &Server{
config: config,
services: newServiceSet(config.interceptor),
done: make(chan struct{}),
listeners: make(map[net.Listener]struct{}),
connections: make(map[*serverConn]struct{}),
}, nil
}
// Register registers a map of methods to method handlers
// TODO: Remove in 2.0, does not support streams
func (s *Server) Register(name string, methods map[string]Method) {
s.services.register(name, &ServiceDesc{Methods: methods})
}
func (s *Server) RegisterService(name string, desc *ServiceDesc) {
s.services.register(name, desc)
}
func (s *Server) Serve(ctx context.Context, l net.Listener) error {
s.addListener(l)
defer s.closeListener(l)
var (
backoff time.Duration
handshaker = s.config.handshaker
)
if handshaker == nil {
handshaker = handshakerFunc(noopHandshake)
}
for {
conn, err := l.Accept()
if err != nil {
select {
case <-s.done:
return ErrServerClosed
default:
}
if terr, ok := err.(interface {
Temporary() bool
}); ok && terr.Temporary() {
if backoff == 0 {
backoff = time.Millisecond
} else {
backoff *= 2
}
if max := time.Second; backoff > max {
backoff = max
}
sleep := time.Duration(rand.Int63n(int64(backoff)))
logrus.WithError(err).Errorf("ttrpc: failed accept; backoff %v", sleep)
time.Sleep(sleep)
continue
}
return err
}
backoff = 0
approved, handshake, err := handshaker.Handshake(ctx, conn)
if err != nil {
logrus.WithError(err).Error("ttrpc: refusing connection after handshake")
conn.Close()
continue
}
sc, err := s.newConn(approved, handshake)
if err != nil {
logrus.WithError(err).Error("ttrpc: create connection failed")
conn.Close()
continue
}
go sc.run(ctx)
}
}
func (s *Server) Shutdown(ctx context.Context) error {
s.mu.Lock()
select {
case <-s.done:
default:
// protected by mutex
close(s.done)
}
lnerr := s.closeListeners()
s.mu.Unlock()
ticker := time.NewTicker(200 * time.Millisecond)
defer ticker.Stop()
for {
s.closeIdleConns()
if s.countConnection() == 0 {
break
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
return lnerr
}
// Close the server without waiting for active connections.
func (s *Server) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
select {
case <-s.done:
default:
// protected by mutex
close(s.done)
}
err := s.closeListeners()
for c := range s.connections {
c.close()
delete(s.connections, c)
}
return err
}
func (s *Server) addListener(l net.Listener) {
s.mu.Lock()
defer s.mu.Unlock()
s.listeners[l] = struct{}{}
}
func (s *Server) closeListener(l net.Listener) error {
s.mu.Lock()
defer s.mu.Unlock()
return s.closeListenerLocked(l)
}
func (s *Server) closeListenerLocked(l net.Listener) error {
defer delete(s.listeners, l)
return l.Close()
}
func (s *Server) closeListeners() error {
var err error
for l := range s.listeners {
if cerr := s.closeListenerLocked(l); cerr != nil && err == nil {
err = cerr
}
}
return err
}
func (s *Server) addConnection(c *serverConn) error {
s.mu.Lock()
defer s.mu.Unlock()
select {
case <-s.done:
return ErrServerClosed
default:
}
s.connections[c] = struct{}{}
return nil
}
func (s *Server) delConnection(c *serverConn) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.connections, c)
}
func (s *Server) countConnection() int {
s.mu.Lock()
defer s.mu.Unlock()
return len(s.connections)
}
func (s *Server) closeIdleConns() {
s.mu.Lock()
defer s.mu.Unlock()
for c := range s.connections {
if st, ok := c.getState(); !ok || st == connStateActive {
continue
}
c.close()
delete(s.connections, c)
}
}
type connState int
const (
connStateActive = iota + 1 // outstanding requests
connStateIdle // no requests
connStateClosed // closed connection
)
func (cs connState) String() string {
switch cs {
case connStateActive:
return "active"
case connStateIdle:
return "idle"
case connStateClosed:
return "closed"
default:
return "unknown"
}
}
func (s *Server) newConn(conn net.Conn, handshake interface{}) (*serverConn, error) {
c := &serverConn{
server: s,
conn: conn,
handshake: handshake,
shutdown: make(chan struct{}),
}
c.setState(connStateIdle)
if err := s.addConnection(c); err != nil {
c.close()
return nil, err
}
return c, nil
}
type serverConn struct {
server *Server
conn net.Conn
handshake interface{} // data from handshake, not used for now
state atomic.Value
shutdownOnce sync.Once
shutdown chan struct{} // forced shutdown, used by close
}
func (c *serverConn) getState() (connState, bool) {
cs, ok := c.state.Load().(connState)
return cs, ok
}
func (c *serverConn) setState(newstate connState) {
c.state.Store(newstate)
}
func (c *serverConn) close() error {
c.shutdownOnce.Do(func() {
close(c.shutdown)
})
return nil
}
func (c *serverConn) run(sctx context.Context) {
type (
response struct {
id uint32
status *status.Status
data []byte
closeStream bool
streaming bool
}
)
var (
ch = newChannel(c.conn)
ctx, cancel = context.WithCancel(sctx)
state connState = connStateIdle
responses = make(chan response)
recvErr = make(chan error, 1)
done = make(chan struct{})
streams = sync.Map{}
active int32
lastStreamID uint32
)
defer c.conn.Close()
defer cancel()
defer close(done)
defer c.server.delConnection(c)
sendStatus := func(id uint32, st *status.Status) bool {
select {
case responses <- response{
// even though we've had an invalid stream id, we send it
// back on the same stream id so the client knows which
// stream id was bad.
id: id,
status: st,
closeStream: true,
}:
return true
case <-c.shutdown:
return false
case <-done:
return false
}
}
go func(recvErr chan error) {
defer close(recvErr)
for {
select {
case <-c.shutdown:
return
case <-done:
return
default: // proceed
}
mh, p, err := ch.recv()
if err != nil {
status, ok := status.FromError(err)
if !ok {
recvErr <- err
return
}
// in this case, we send an error for that particular message
// when the status is defined.
if !sendStatus(mh.StreamID, status) {
return
}
continue
}
if mh.StreamID%2 != 1 {
// enforce odd client initiated identifiers.
if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID must be odd for client initiated streams")) {
return
}
continue
}
if mh.Type == messageTypeData {
i, ok := streams.Load(mh.StreamID)
if !ok {
if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID is no longer active")) {
return
}
}
sh := i.(*streamHandler)
if mh.Flags&flagNoData != flagNoData {
unmarshal := func(obj interface{}) error {
err := protoUnmarshal(p, obj)
ch.putmbuf(p)
return err
}
if err := sh.data(unmarshal); err != nil {
if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data handling error: %v", err)) {
return
}
}
}
if mh.Flags&flagRemoteClosed == flagRemoteClosed {
sh.closeSend()
if len(p) > 0 {
if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data close message cannot include data")) {
return
}
}
}
} else if mh.Type == messageTypeRequest {
if mh.StreamID <= lastStreamID {
// enforce odd client initiated identifiers.
if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID cannot be re-used and must increment")) {
return
}
continue
}
lastStreamID = mh.StreamID
// TODO: Make request type configurable
// Unmarshaller which takes in a byte array and returns an interface?
var req Request
if err := c.server.codec.Unmarshal(p, &req); err != nil {
ch.putmbuf(p)
if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "unmarshal request error: %v", err)) {
return
}
continue
}
ch.putmbuf(p)
id := mh.StreamID
respond := func(status *status.Status, data []byte, streaming, closeStream bool) error {
select {
case responses <- response{
id: id,
status: status,
data: data,
closeStream: closeStream,
streaming: streaming,
}:
case <-done:
return ErrClosed
}
return nil
}
sh, err := c.server.services.handle(ctx, &req, respond)
if err != nil {
status, _ := status.FromError(err)
if !sendStatus(mh.StreamID, status) {
return
}
continue
}
streams.Store(id, sh)
atomic.AddInt32(&active, 1)
}
// TODO: else we must ignore this for future compat. log this?
}
}(recvErr)
for {
var (
newstate connState
shutdown chan struct{}
)
activeN := atomic.LoadInt32(&active)
if activeN > 0 {
newstate = connStateActive
shutdown = nil
} else {
newstate = connStateIdle
shutdown = c.shutdown // only enable this branch in idle mode
}
if newstate != state {
c.setState(newstate)
state = newstate
}
select {
case response := <-responses:
if !response.streaming || response.status.Code() != codes.OK {
p, err := c.server.codec.Marshal(&Response{
Status: response.status.Proto(),
Payload: response.data,
})
if err != nil {
logrus.WithError(err).Error("failed marshaling response")
return
}
if err := ch.send(response.id, messageTypeResponse, 0, p); err != nil {
logrus.WithError(err).Error("failed sending message on channel")
return
}
} else {
var flags uint8
if response.closeStream {
flags = flagRemoteClosed
}
if response.data == nil {
flags = flags | flagNoData
}
if err := ch.send(response.id, messageTypeData, flags, response.data); err != nil {
logrus.WithError(err).Error("failed sending message on channel")
return
}
}
if response.closeStream {
// The ttrpc protocol currently does not support the case where
// the server is localClosed but not remoteClosed. Once the server
// is closing, the whole stream may be considered finished
streams.Delete(response.id)
atomic.AddInt32(&active, -1)
}
case err := <-recvErr:
// TODO(stevvooe): Not wildly clear what we should do in this
// branch. Basically, it means that we are no longer receiving
// requests due to a terminal error.
recvErr = nil // connection is now "closing"
if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.ECONNRESET) {
// The client went away and we should stop processing
// requests, so that the client connection is closed
return
}
logrus.WithError(err).Error("error receiving message")
// else, initiate shutdown
case <-shutdown:
return
}
}
}
var noopFunc = func() {}
func getRequestContext(ctx context.Context, req *Request) (retCtx context.Context, cancel func()) {
if len(req.Metadata) > 0 {
md := MD{}
md.fromRequest(req)
ctx = WithMetadata(ctx, md)
}
cancel = noopFunc
if req.TimeoutNano == 0 {
return ctx, cancel
}
ctx, cancel = context.WithTimeout(ctx, time.Duration(req.TimeoutNano))
return ctx, cancel
}

@ -0,0 +1,275 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import (
"context"
"errors"
"fmt"
"io"
"os"
"path"
"unsafe"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
type Method func(ctx context.Context, unmarshal func(interface{}) error) (interface{}, error)
type StreamHandler func(context.Context, StreamServer) (interface{}, error)
type Stream struct {
Handler StreamHandler
StreamingClient bool
StreamingServer bool
}
type ServiceDesc struct {
Methods map[string]Method
Streams map[string]Stream
}
type serviceSet struct {
services map[string]*ServiceDesc
unaryInterceptor UnaryServerInterceptor
streamInterceptor StreamServerInterceptor
}
func newServiceSet(interceptor UnaryServerInterceptor) *serviceSet {
return &serviceSet{
services: make(map[string]*ServiceDesc),
unaryInterceptor: interceptor,
streamInterceptor: defaultStreamServerInterceptor,
}
}
func (s *serviceSet) register(name string, desc *ServiceDesc) {
if _, ok := s.services[name]; ok {
panic(fmt.Errorf("duplicate service %v registered", name))
}
s.services[name] = desc
}
func (s *serviceSet) unaryCall(ctx context.Context, method Method, info *UnaryServerInfo, data []byte) (p []byte, st *status.Status) {
unmarshal := func(obj interface{}) error {
return protoUnmarshal(data, obj)
}
resp, err := s.unaryInterceptor(ctx, unmarshal, info, method)
if err == nil {
if isNil(resp) {
err = errors.New("ttrpc: marshal called with nil")
} else {
p, err = protoMarshal(resp)
}
}
st, ok := status.FromError(err)
if !ok {
st = status.New(convertCode(err), err.Error())
}
return p, st
}
func (s *serviceSet) streamCall(ctx context.Context, stream StreamHandler, info *StreamServerInfo, ss StreamServer) (p []byte, st *status.Status) {
resp, err := s.streamInterceptor(ctx, ss, info, stream)
if err == nil {
p, err = protoMarshal(resp)
}
st, ok := status.FromError(err)
if !ok {
st = status.New(convertCode(err), err.Error())
}
return
}
func (s *serviceSet) handle(ctx context.Context, req *Request, respond func(*status.Status, []byte, bool, bool) error) (*streamHandler, error) {
srv, ok := s.services[req.Service]
if !ok {
return nil, status.Errorf(codes.Unimplemented, "service %v", req.Service)
}
if method, ok := srv.Methods[req.Method]; ok {
go func() {
ctx, cancel := getRequestContext(ctx, req)
defer cancel()
info := &UnaryServerInfo{
FullMethod: fullPath(req.Service, req.Method),
}
p, st := s.unaryCall(ctx, method, info, req.Payload)
respond(st, p, false, true)
}()
return nil, nil
}
if stream, ok := srv.Streams[req.Method]; ok {
ctx, cancel := getRequestContext(ctx, req)
info := &StreamServerInfo{
FullMethod: fullPath(req.Service, req.Method),
StreamingClient: stream.StreamingClient,
StreamingServer: stream.StreamingServer,
}
sh := &streamHandler{
ctx: ctx,
respond: respond,
recv: make(chan Unmarshaler, 5),
info: info,
}
go func() {
defer cancel()
p, st := s.streamCall(ctx, stream.Handler, info, sh)
respond(st, p, stream.StreamingServer, true)
}()
if req.Payload != nil {
unmarshal := func(obj interface{}) error {
return protoUnmarshal(req.Payload, obj)
}
if err := sh.data(unmarshal); err != nil {
return nil, err
}
}
return sh, nil
}
return nil, status.Errorf(codes.Unimplemented, "method %v", req.Method)
}
type streamHandler struct {
ctx context.Context
respond func(*status.Status, []byte, bool, bool) error
recv chan Unmarshaler
info *StreamServerInfo
remoteClosed bool
localClosed bool
}
func (s *streamHandler) closeSend() {
if !s.remoteClosed {
s.remoteClosed = true
close(s.recv)
}
}
func (s *streamHandler) data(unmarshal Unmarshaler) error {
if s.remoteClosed {
return ErrStreamClosed
}
select {
case s.recv <- unmarshal:
return nil
case <-s.ctx.Done():
return s.ctx.Err()
}
}
func (s *streamHandler) SendMsg(m interface{}) error {
if s.localClosed {
return ErrStreamClosed
}
p, err := protoMarshal(m)
if err != nil {
return err
}
return s.respond(nil, p, true, false)
}
func (s *streamHandler) RecvMsg(m interface{}) error {
select {
case unmarshal, ok := <-s.recv:
if !ok {
return io.EOF
}
return unmarshal(m)
case <-s.ctx.Done():
return s.ctx.Err()
}
}
func protoUnmarshal(p []byte, obj interface{}) error {
switch v := obj.(type) {
case proto.Message:
if err := proto.Unmarshal(p, v); err != nil {
return status.Errorf(codes.Internal, "ttrpc: error unmarshalling payload: %v", err.Error())
}
default:
return status.Errorf(codes.Internal, "ttrpc: error unsupported request type: %T", v)
}
return nil
}
func protoMarshal(obj interface{}) ([]byte, error) {
if obj == nil {
return nil, nil
}
switch v := obj.(type) {
case proto.Message:
r, err := proto.Marshal(v)
if err != nil {
return nil, status.Errorf(codes.Internal, "ttrpc: error marshaling payload: %v", err.Error())
}
return r, nil
default:
return nil, status.Errorf(codes.Internal, "ttrpc: error unsupported response type: %T", v)
}
}
// convertCode maps stdlib go errors into grpc space.
//
// This is ripped from the grpc-go code base.
func convertCode(err error) codes.Code {
switch err {
case nil:
return codes.OK
case io.EOF:
return codes.OutOfRange
case io.ErrClosedPipe, io.ErrNoProgress, io.ErrShortBuffer, io.ErrShortWrite, io.ErrUnexpectedEOF:
return codes.FailedPrecondition
case os.ErrInvalid:
return codes.InvalidArgument
case context.Canceled:
return codes.Canceled
case context.DeadlineExceeded:
return codes.DeadlineExceeded
}
switch {
case os.IsExist(err):
return codes.AlreadyExists
case os.IsNotExist(err):
return codes.NotFound
case os.IsPermission(err):
return codes.PermissionDenied
}
return codes.Unknown
}
func fullPath(service, method string) string {
return "/" + path.Join(service, method)
}
func isNil(resp interface{}) bool {
return (*[2]uintptr)(unsafe.Pointer(&resp))[1] == 0
}

@ -0,0 +1,84 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import (
"context"
"sync"
)
type streamID uint32
type streamMessage struct {
header messageHeader
payload []byte
}
type stream struct {
id streamID
sender sender
recv chan *streamMessage
closeOnce sync.Once
recvErr error
recvClose chan struct{}
}
func newStream(id streamID, send sender) *stream {
return &stream{
id: id,
sender: send,
recv: make(chan *streamMessage, 1),
recvClose: make(chan struct{}),
}
}
func (s *stream) closeWithError(err error) error {
s.closeOnce.Do(func() {
if err != nil {
s.recvErr = err
} else {
s.recvErr = ErrClosed
}
close(s.recvClose)
})
return nil
}
func (s *stream) send(mt messageType, flags uint8, b []byte) error {
return s.sender.send(uint32(s.id), mt, flags, b)
}
func (s *stream) receive(ctx context.Context, msg *streamMessage) error {
select {
case <-s.recvClose:
return s.recvErr
default:
}
select {
case <-s.recvClose:
return s.recvErr
case s.recv <- msg:
return nil
case <-ctx.Done():
return ctx.Err()
}
}
type sender interface {
send(uint32, messageType, uint8, []byte) error
}

@ -0,0 +1,22 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
type StreamServer interface {
SendMsg(m interface{}) error
RecvMsg(m interface{}) error
}

@ -0,0 +1,16 @@
syntax = "proto3";
package ttrpc;
option go_package = "github.com/containerd/ttrpc/internal";
message TestPayload {
string foo = 1;
int64 deadline = 2;
string metadata = 3;
}
message EchoPayload {
int64 seq = 1;
string msg = 2;
}

@ -0,0 +1,105 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package ttrpc
import (
"context"
"errors"
"fmt"
"net"
"os"
"syscall"
"golang.org/x/sys/unix"
)
type UnixCredentialsFunc func(*unix.Ucred) error
func (fn UnixCredentialsFunc) Handshake(_ context.Context, conn net.Conn) (net.Conn, interface{}, error) {
uc, err := requireUnixSocket(conn)
if err != nil {
return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: require unix socket: %w", err)
}
rs, err := uc.SyscallConn()
if err != nil {
return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: (net.UnixConn).SyscallConn failed: %w", err)
}
var (
ucred *unix.Ucred
ucredErr error
)
if err := rs.Control(func(fd uintptr) {
ucred, ucredErr = unix.GetsockoptUcred(int(fd), unix.SOL_SOCKET, unix.SO_PEERCRED)
}); err != nil {
return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: (*syscall.RawConn).Control failed: %w", err)
}
if ucredErr != nil {
return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: failed to retrieve socket peer credentials: %w", ucredErr)
}
if err := fn(ucred); err != nil {
return nil, nil, fmt.Errorf("ttrpc.UnixCredentialsFunc: credential check failed: %w", err)
}
return uc, ucred, nil
}
// UnixSocketRequireUidGid requires specific *effective* UID/GID, rather than the real UID/GID.
//
// For example, if a daemon binary is owned by the root (UID 0) with SUID bit but running as an
// unprivileged user (UID 1001), the effective UID becomes 0, and the real UID becomes 1001.
// So calling this function with uid=0 allows a connection from effective UID 0 but rejects
// a connection from effective UID 1001.
//
// See socket(7), SO_PEERCRED: "The returned credentials are those that were in effect at the time of the call to connect(2) or socketpair(2)."
func UnixSocketRequireUidGid(uid, gid int) UnixCredentialsFunc {
return func(ucred *unix.Ucred) error {
return requireUidGid(ucred, uid, gid)
}
}
func UnixSocketRequireRoot() UnixCredentialsFunc {
return UnixSocketRequireUidGid(0, 0)
}
// UnixSocketRequireSameUser resolves the current effective unix user and returns a
// UnixCredentialsFunc that will validate incoming unix connections against the
// current credentials.
//
// This is useful when using abstract sockets that are accessible by all users.
func UnixSocketRequireSameUser() UnixCredentialsFunc {
euid, egid := os.Geteuid(), os.Getegid()
return UnixSocketRequireUidGid(euid, egid)
}
func requireUidGid(ucred *unix.Ucred, uid, gid int) error {
if (uid != -1 && uint32(uid) != ucred.Uid) || (gid != -1 && uint32(gid) != ucred.Gid) {
return fmt.Errorf("ttrpc: invalid credentials: %v", syscall.EPERM)
}
return nil
}
func requireUnixSocket(conn net.Conn) (*net.UnixConn, error) {
uc, ok := conn.(*net.UnixConn)
if !ok {
return nil, errors.New("a unix socket connection is required")
}
return uc, nil
}

@ -25,6 +25,7 @@ import (
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
)
@ -184,6 +185,19 @@ func (c *Client) Dialer() session.Dialer {
return grpchijack.Dialer(c.ControlClient())
}
func (c *Client) Wait(ctx context.Context) error {
opts := []grpc.CallOption{grpc.WaitForReady(true)}
_, err := c.ControlClient().Info(ctx, &controlapi.InfoRequest{}, opts...)
if err != nil {
if code := grpcerrors.Code(err); code == codes.Unimplemented {
// only buildkit v0.11+ supports the info api, but an unimplemented
// response error is still a response so we can ignore it
return nil
}
}
return err
}
func (c *Client) Close() error {
return c.conn.Close()
}

@ -15,7 +15,7 @@ type asyncState struct {
target State
set bool
err error
g flightcontrol.Group
g flightcontrol.Group[State]
}
func (as *asyncState) Output() Output {
@ -53,7 +53,7 @@ func (as *asyncState) ToInput(ctx context.Context, c *Constraints) (*pb.Input, e
}
func (as *asyncState) Do(ctx context.Context, c *Constraints) error {
_, err := as.g.Do(ctx, "", func(ctx context.Context) (interface{}, error) {
_, err := as.g.Do(ctx, "", func(ctx context.Context) (State, error) {
if as.set {
return as.target, as.err
}

@ -145,6 +145,7 @@ func Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction {
for _, o := range opt {
o.SetMkdirOption(&mi)
}
return &FileAction{
action: &fileActionMkdir{
file: p,
@ -447,7 +448,6 @@ func Copy(input CopyInput, src, dest string, opts ...CopyOption) *FileAction {
for _, o := range opts {
o.SetCopyOption(&mi)
}
return &FileAction{
action: &fileActionCopy{
state: state,
@ -523,22 +523,19 @@ func (a *fileActionCopy) toProtoAction(ctx context.Context, parent string, base
func (a *fileActionCopy) sourcePath(ctx context.Context) (string, error) {
p := path.Clean(a.src)
dir := "/"
var err error
if !path.IsAbs(p) {
if a.state != nil {
dir, err := a.state.GetDir(ctx)
if err != nil {
return "", err
}
p = path.Join("/", dir, p)
dir, err = a.state.GetDir(ctx)
} else if a.fas != nil {
dir, err := a.fas.state.GetDir(ctx)
if err != nil {
return "", err
}
p = path.Join("/", dir, p)
dir, err = a.fas.state.GetDir(ctx)
}
if err != nil {
return "", err
}
}
return p, nil
return path.Join(dir, p), nil
}
func (a *fileActionCopy) addCaps(f *FileOp) {

@ -10,6 +10,7 @@ import (
"github.com/google/shlex"
"github.com/moby/buildkit/solver/pb"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
type contextKeyT string
@ -78,7 +79,7 @@ func dirf(value string, replace bool, v ...interface{}) StateOption {
if !path.IsAbs(value) {
prev, err := getDir(s)(ctx, c)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "getting dir from state")
}
if prev == "" {
prev = "/"

@ -3,6 +3,7 @@ package llb
import (
"context"
spb "github.com/moby/buildkit/sourcepolicy/pb"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
)
@ -31,7 +32,7 @@ func WithLayerLimit(l int) ImageOption {
// ImageMetaResolver can resolve image config metadata from a reference
type ImageMetaResolver interface {
ResolveImageConfig(ctx context.Context, ref string, opt ResolveImageConfigOpt) (digest.Digest, []byte, error)
ResolveImageConfig(ctx context.Context, ref string, opt ResolveImageConfigOpt) (string, digest.Digest, []byte, error)
}
type ResolverType int
@ -49,6 +50,8 @@ type ResolveImageConfigOpt struct {
LogName string
Store ResolveImageConfigOptStore
SourcePolicies []*spb.Policy
}
type ResolveImageConfigOptStore struct {

@ -135,7 +135,7 @@ func Image(ref string, opts ...ImageOption) State {
if p == nil {
p = c.Platform
}
_, dt, err := info.metaResolver.ResolveImageConfig(ctx, ref, ResolveImageConfigOpt{
_, _, dt, err := info.metaResolver.ResolveImageConfig(ctx, ref, ResolveImageConfigOpt{
Platform: p,
ResolveMode: info.resolveMode.String(),
ResolverType: ResolverTypeRegistry,
@ -151,7 +151,7 @@ func Image(ref string, opts ...ImageOption) State {
if p == nil {
p = c.Platform
}
dgst, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, ResolveImageConfigOpt{
ref, dgst, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, ResolveImageConfigOpt{
Platform: p,
ResolveMode: info.resolveMode.String(),
ResolverType: ResolverTypeRegistry,
@ -159,6 +159,10 @@ func Image(ref string, opts ...ImageOption) State {
if err != nil {
return State{}, err
}
r, err := reference.ParseNormalizedNamed(ref)
if err != nil {
return State{}, err
}
if dgst != "" {
r, err = reference.WithDigest(r, dgst)
if err != nil {

@ -19,9 +19,10 @@ type HealthConfig struct {
Test []string `json:",omitempty"`
// Zero means to inherit. Durations are expressed as integer nanoseconds.
Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
StartPeriod time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.
Interval time.Duration `json:",omitempty"` // Interval is the time to wait between checks.
Timeout time.Duration `json:",omitempty"` // Timeout is the time to wait before considering the check to have hung.
StartPeriod time.Duration `json:",omitempty"` // The start period for the container to initialize before the retries starts to count down.
StartInterval time.Duration `json:",omitempty"` // StartInterval is the time to wait between checks during the start period.
// Retries is the number of consecutive failures needed to consider a container as unhealthy.
// Zero means inherit.

@ -19,6 +19,7 @@ import (
"github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/flightcontrol"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
@ -76,7 +77,7 @@ type Client struct {
client client.Client
ignoreCache []string
bctx *buildContext
g flightcontrol.Group
g flightcontrol.Group[*buildContext]
bopts client.BuildOpts
dockerignore []byte
@ -96,6 +97,7 @@ type ContextOpt struct {
LocalOpts []llb.LocalOption
Platform *ocispecs.Platform
ResolveMode string
CaptureDigest *digest.Digest
}
func validateMinCaps(c client.Client) error {
@ -278,7 +280,7 @@ func (bc *Client) init() error {
}
func (bc *Client) buildContext(ctx context.Context) (*buildContext, error) {
bctx, err := bc.g.Do(ctx, "initcontext", func(ctx context.Context) (interface{}, error) {
return bc.g.Do(ctx, "initcontext", func(ctx context.Context) (*buildContext, error) {
if bc.bctx != nil {
return bc.bctx, nil
}
@ -288,10 +290,6 @@ func (bc *Client) buildContext(ctx context.Context) (*buildContext, error) {
}
return bctx, err
})
if err != nil {
return nil, err
}
return bctx.(*buildContext), nil
}
func (bc *Client) ReadEntrypoint(ctx context.Context, lang string, opts ...llb.LocalOption) (*Source, error) {

@ -13,25 +13,36 @@ import (
"github.com/moby/buildkit/exporter/containerimage/image"
"github.com/moby/buildkit/frontend/dockerfile/dockerignore"
"github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/util/imageutil"
"github.com/pkg/errors"
)
const (
contextPrefix = "context:"
inputMetadataPrefix = "input-metadata:"
maxContextRecursion = 10
)
func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatform string, opt ContextOpt) (*llb.State, *image.Image, error) {
return bc.namedContextRecursive(ctx, name, nameWithPlatform, opt, 0)
}
func (bc *Client) namedContextRecursive(ctx context.Context, name string, nameWithPlatform string, opt ContextOpt, count int) (*llb.State, *image.Image, error) {
opts := bc.bopts.Opts
v, ok := opts[contextPrefix+nameWithPlatform]
if !ok {
return nil, nil, nil
}
if count > maxContextRecursion {
return nil, nil, errors.New("context recursion limit exceeded; this may indicate a cycle in the provided source policies: " + v)
}
vv := strings.SplitN(v, ":", 2)
if len(vv) != 2 {
return nil, nil, errors.Errorf("invalid context specifier %s for %s", v, nameWithPlatform)
}
// allow git@ without protocol for SSH URLs for backwards compatibility
if strings.HasPrefix(vv[0], "git@") {
vv[0] = "git"
@ -58,13 +69,17 @@ func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatfor
named = reference.TagNameOnly(named)
_, data, err := bc.client.ResolveImageConfig(ctx, named.String(), llb.ResolveImageConfigOpt{
ref, dgst, data, err := bc.client.ResolveImageConfig(ctx, named.String(), llb.ResolveImageConfigOpt{
Platform: opt.Platform,
ResolveMode: opt.ResolveMode,
LogName: fmt.Sprintf("[context %s] load metadata for %s", nameWithPlatform, ref),
ResolverType: llb.ResolverTypeRegistry,
})
if err != nil {
e := &imageutil.ResolveToNonImageError{}
if errors.As(err, &e) {
return bc.namedContextRecursive(ctx, e.Updated, name, opt, count+1)
}
return nil, nil, err
}
@ -79,6 +94,9 @@ func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatfor
if err != nil {
return nil, nil, err
}
if opt.CaptureDigest != nil {
*opt.CaptureDigest = dgst
}
return &st, &img, nil
case "git":
st, ok := DetectGitContext(v, true)
@ -119,7 +137,8 @@ func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatfor
return nil, nil, errors.Wrapf(err, "could not wrap %q with digest", name)
}
_, data, err := bc.client.ResolveImageConfig(ctx, dummyRef.String(), llb.ResolveImageConfigOpt{
// TODO: How should source policy be handled here with a dummy ref?
_, dgst, data, err := bc.client.ResolveImageConfig(ctx, dummyRef.String(), llb.ResolveImageConfigOpt{
Platform: opt.Platform,
ResolveMode: opt.ResolveMode,
LogName: fmt.Sprintf("[context %s] load metadata for %s", nameWithPlatform, dummyRef.String()),
@ -153,6 +172,9 @@ func (bc *Client) namedContext(ctx context.Context, name string, nameWithPlatfor
if err != nil {
return nil, nil, err
}
if opt.CaptureDigest != nil {
*opt.CaptureDigest = dgst
}
return &st, &img, nil
case "local":
st := llb.Local(vv[1],

@ -27,7 +27,7 @@ func NewResult() *Result {
type Client interface {
Solve(ctx context.Context, req SolveRequest) (*Result, error)
ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (digest.Digest, []byte, error)
ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (string, digest.Digest, []byte, error)
BuildOpts() BuildOpts
Inputs(ctx context.Context) (map[string]llb.State, error)
NewContainer(ctx context.Context, req NewContainerRequest) (Container, error)
@ -71,6 +71,7 @@ type Container interface {
type StartRequest struct {
Args []string
Env []string
SecretEnv []*pb.SecretEnv
User string
Cwd string
Tty bool

@ -478,7 +478,7 @@ func (c *grpcClient) Solve(ctx context.Context, creq client.SolveRequest) (res *
return res, nil
}
func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (digest.Digest, []byte, error) {
func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, opt llb.ResolveImageConfigOpt) (string, digest.Digest, []byte, error) {
var p *opspb.Platform
if platform := opt.Platform; platform != nil {
p = &opspb.Platform{
@ -489,19 +489,27 @@ func (c *grpcClient) ResolveImageConfig(ctx context.Context, ref string, opt llb
OSFeatures: platform.OSFeatures,
}
}
resp, err := c.client.ResolveImageConfig(ctx, &pb.ResolveImageConfigRequest{
ResolverType: int32(opt.ResolverType),
Ref: ref,
Platform: p,
ResolveMode: opt.ResolveMode,
LogName: opt.LogName,
SessionID: opt.Store.SessionID,
StoreID: opt.Store.StoreID,
ResolverType: int32(opt.ResolverType),
Ref: ref,
Platform: p,
ResolveMode: opt.ResolveMode,
LogName: opt.LogName,
SessionID: opt.Store.SessionID,
StoreID: opt.Store.StoreID,
SourcePolicies: opt.SourcePolicies,
})
if err != nil {
return "", nil, err
return "", "", nil, err
}
newRef := resp.Ref
if newRef == "" {
// No ref returned, use the original one.
// This could occur if the version of buildkitd is too old.
newRef = ref
}
return resp.Digest, resp.Config, nil
return newRef, resp.Digest, resp.Config, nil
}
func (c *grpcClient) BuildOpts() client.BuildOpts {
@ -806,6 +814,7 @@ func (c *grpcClient) NewContainer(ctx context.Context, req client.NewContainerRe
return &container{
client: c.client,
caps: c.caps,
id: id,
execMsgs: c.execMsgs,
}, nil
@ -813,6 +822,7 @@ func (c *grpcClient) NewContainer(ctx context.Context, req client.NewContainerRe
type container struct {
client pb.LLBBridgeClient
caps apicaps.CapSet
id string
execMsgs *messageForwarder
}
@ -821,6 +831,12 @@ func (ctr *container) Start(ctx context.Context, req client.StartRequest) (clien
pid := fmt.Sprintf("%s:%s", ctr.id, identity.NewID())
msgs := ctr.execMsgs.Register(pid)
if len(req.SecretEnv) > 0 {
if err := ctr.caps.Supports(pb.CapGatewayExecSecretEnv); err != nil {
return nil, err
}
}
init := &pb.InitMessage{
ContainerID: ctr.id,
Meta: &opspb.Meta{
@ -829,8 +845,9 @@ func (ctr *container) Start(ctx context.Context, req client.StartRequest) (clien
Cwd: req.Cwd,
User: req.User,
},
Tty: req.Tty,
Security: req.SecurityMode,
Tty: req.Tty,
Security: req.SecurityMode,
Secretenv: req.SecretEnv,
}
init.Meta.RemoveMountStubsRecursive = req.RemoveMountStubsRecursive
if req.Stdin != nil {

@ -44,6 +44,10 @@ const (
// /etc/hosts for containers created via gateway exec.
CapGatewayExecExtraHosts apicaps.CapID = "gateway.exec.extrahosts"
// CapGatewayExecExtraHosts is the capability to set secrets as env vars for
// containers created via gateway exec.
CapGatewayExecSecretEnv apicaps.CapID = "gateway.exec.secretenv"
// CapGatewayExecExtraHosts is the capability to send signals to a process
// created via gateway exec.
CapGatewayExecSignals apicaps.CapID = "gateway.exec.signals"
@ -179,6 +183,13 @@ func init() {
Status: apicaps.CapStatusExperimental,
})
Caps.Init(apicaps.Cap{
ID: CapGatewayExecSecretEnv,
Name: "gateway exec secret env",
Enabled: true,
Status: apicaps.CapStatusExperimental,
})
Caps.Init(apicaps.Cap{
ID: CapGatewayExecSignals,
Name: "gateway exec signals",

@ -736,16 +736,17 @@ func (m *InputsResponse) GetDefinitions() map[string]*pb.Definition {
}
type ResolveImageConfigRequest struct {
Ref string `protobuf:"bytes,1,opt,name=Ref,proto3" json:"Ref,omitempty"`
Platform *pb.Platform `protobuf:"bytes,2,opt,name=Platform,proto3" json:"Platform,omitempty"`
ResolveMode string `protobuf:"bytes,3,opt,name=ResolveMode,proto3" json:"ResolveMode,omitempty"`
LogName string `protobuf:"bytes,4,opt,name=LogName,proto3" json:"LogName,omitempty"`
ResolverType int32 `protobuf:"varint,5,opt,name=ResolverType,proto3" json:"ResolverType,omitempty"`
SessionID string `protobuf:"bytes,6,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
StoreID string `protobuf:"bytes,7,opt,name=StoreID,proto3" json:"StoreID,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
Ref string `protobuf:"bytes,1,opt,name=Ref,proto3" json:"Ref,omitempty"`
Platform *pb.Platform `protobuf:"bytes,2,opt,name=Platform,proto3" json:"Platform,omitempty"`
ResolveMode string `protobuf:"bytes,3,opt,name=ResolveMode,proto3" json:"ResolveMode,omitempty"`
LogName string `protobuf:"bytes,4,opt,name=LogName,proto3" json:"LogName,omitempty"`
ResolverType int32 `protobuf:"varint,5,opt,name=ResolverType,proto3" json:"ResolverType,omitempty"`
SessionID string `protobuf:"bytes,6,opt,name=SessionID,proto3" json:"SessionID,omitempty"`
StoreID string `protobuf:"bytes,7,opt,name=StoreID,proto3" json:"StoreID,omitempty"`
SourcePolicies []*pb1.Policy `protobuf:"bytes,8,rep,name=SourcePolicies,proto3" json:"SourcePolicies,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *ResolveImageConfigRequest) Reset() { *m = ResolveImageConfigRequest{} }
@ -830,9 +831,17 @@ func (m *ResolveImageConfigRequest) GetStoreID() string {
return ""
}
func (m *ResolveImageConfigRequest) GetSourcePolicies() []*pb1.Policy {
if m != nil {
return m.SourcePolicies
}
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"`
Ref string `protobuf:"bytes,3,opt,name=Ref,proto3" json:"Ref,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -878,6 +887,13 @@ func (m *ResolveImageConfigResponse) GetConfig() []byte {
return nil
}
func (m *ResolveImageConfigResponse) GetRef() string {
if m != nil {
return m.Ref
}
return ""
}
type SolveRequest struct {
Definition *pb.Definition `protobuf:"bytes,1,opt,name=Definition,proto3" json:"Definition,omitempty"`
Frontend string `protobuf:"bytes,2,opt,name=Frontend,proto3" json:"Frontend,omitempty"`
@ -2204,6 +2220,7 @@ type InitMessage struct {
Fds []uint32 `protobuf:"varint,3,rep,packed,name=Fds,proto3" json:"Fds,omitempty"`
Tty bool `protobuf:"varint,4,opt,name=Tty,proto3" json:"Tty,omitempty"`
Security pb.SecurityMode `protobuf:"varint,5,opt,name=Security,proto3,enum=pb.SecurityMode" json:"Security,omitempty"`
Secretenv []*pb.SecretEnv `protobuf:"bytes,6,rep,name=secretenv,proto3" json:"secretenv,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -2277,6 +2294,13 @@ func (m *InitMessage) GetSecurity() pb.SecurityMode {
return pb.SecurityMode_SANDBOX
}
func (m *InitMessage) GetSecretenv() []*pb.SecretEnv {
if m != nil {
return m.Secretenv
}
return nil
}
type ExitMessage struct {
Code uint32 `protobuf:"varint,1,opt,name=Code,proto3" json:"Code,omitempty"`
Error *rpc.Status `protobuf:"bytes,2,opt,name=Error,proto3" json:"Error,omitempty"`
@ -2635,161 +2659,164 @@ func init() {
func init() { proto.RegisterFile("gateway.proto", fileDescriptor_f1a937782ebbded5) }
var fileDescriptor_f1a937782ebbded5 = []byte{
// 2464 bytes of a gzipped FileDescriptorProto
// 2497 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x59, 0xcf, 0x6f, 0x1b, 0xc7,
0xf5, 0xd7, 0x8a, 0x14, 0x25, 0x3d, 0xfe, 0x10, 0x3d, 0x71, 0xf2, 0x65, 0x16, 0x81, 0x23, 0xaf,
0x63, 0x45, 0x56, 0x9c, 0xa5, 0xbf, 0xb2, 0x0d, 0xb9, 0x76, 0xeb, 0xc4, 0xfa, 0x05, 0x29, 0x96,
0x6c, 0x76, 0xe4, 0xc2, 0x45, 0x90, 0x02, 0x5d, 0x71, 0x87, 0xd4, 0xd6, 0xab, 0xdd, 0xed, 0xee,
0xd0, 0xb2, 0x92, 0x4b, 0x7b, 0x28, 0x50, 0xe4, 0x0f, 0xe8, 0x2d, 0x28, 0xd0, 0x02, 0x3d, 0xf5,
0xd0, 0xfe, 0x01, 0xed, 0x39, 0x40, 0x2f, 0x3d, 0xf7, 0x10, 0x14, 0xfe, 0x07, 0x7a, 0x2b, 0xd0,
0x5b, 0xf1, 0x66, 0x66, 0xc9, 0xe1, 0x0f, 0x2d, 0xc9, 0xfa, 0xc4, 0x99, 0x37, 0xef, 0xc7, 0xbc,
0xf7, 0xe6, 0xbd, 0xf9, 0xcc, 0x12, 0xca, 0x6d, 0x87, 0xb3, 0x33, 0xe7, 0xdc, 0x8e, 0xe2, 0x90,
0x87, 0xe4, 0xdd, 0xd3, 0xf0, 0xf8, 0xdc, 0x3e, 0xee, 0x78, 0xbe, 0xfb, 0xc2, 0xe3, 0xf6, 0xcb,
0xff, 0xb7, 0x5b, 0x71, 0x18, 0x70, 0x16, 0xb8, 0xe6, 0xc7, 0x6d, 0x8f, 0x9f, 0x74, 0x8e, 0xed,
0x66, 0x78, 0x5a, 0x6f, 0x87, 0xed, 0xb0, 0x2e, 0x24, 0x8e, 0x3b, 0x2d, 0x31, 0x13, 0x13, 0x31,
0x92, 0x9a, 0xcc, 0xf5, 0x41, 0xf6, 0x76, 0x18, 0xb6, 0x7d, 0xe6, 0x44, 0x5e, 0xa2, 0x86, 0xf5,
0x38, 0x6a, 0xd6, 0x13, 0xee, 0xf0, 0x4e, 0xa2, 0x64, 0x6e, 0x6a, 0x32, 0xb8, 0x91, 0x7a, 0xba,
0x91, 0x7a, 0x12, 0xfa, 0x2f, 0x59, 0x5c, 0x8f, 0x8e, 0xeb, 0x61, 0x94, 0x72, 0xd7, 0x2f, 0xe4,
0x76, 0x22, 0xaf, 0xce, 0xcf, 0x23, 0x96, 0xd4, 0xcf, 0xc2, 0xf8, 0x05, 0x8b, 0x95, 0xc0, 0xed,
0x0b, 0x05, 0x3a, 0xdc, 0xf3, 0x51, 0xaa, 0xe9, 0x44, 0x09, 0x1a, 0xc1, 0x5f, 0x25, 0xa4, 0xbb,
0xcd, 0xc3, 0xc0, 0x4b, 0xb8, 0xe7, 0xb5, 0xbd, 0x7a, 0x2b, 0x11, 0x32, 0xd2, 0x0a, 0x3a, 0xa1,
0xd8, 0xef, 0x66, 0xb8, 0xd0, 0x89, 0x9b, 0x2c, 0x0a, 0x7d, 0xaf, 0x79, 0x8e, 0x36, 0xe4, 0x48,
0x8a, 0x59, 0x7f, 0xcb, 0x43, 0x81, 0xb2, 0xa4, 0xe3, 0x73, 0xb2, 0x02, 0xe5, 0x98, 0xb5, 0xb6,
0x59, 0x14, 0xb3, 0xa6, 0xc3, 0x99, 0x5b, 0x33, 0x96, 0x8d, 0xd5, 0xc5, 0xbd, 0x19, 0xda, 0x4f,
0x26, 0x3f, 0x82, 0x4a, 0xcc, 0x5a, 0x89, 0xc6, 0x38, 0xbb, 0x6c, 0xac, 0x16, 0xd7, 0x3f, 0xb2,
0x2f, 0xcc, 0xa1, 0x4d, 0x59, 0xeb, 0xd0, 0x89, 0x7a, 0x22, 0x7b, 0x33, 0x74, 0x40, 0x09, 0x59,
0x87, 0x5c, 0xcc, 0x5a, 0xb5, 0x9c, 0xd0, 0x75, 0x25, 0x5b, 0xd7, 0xde, 0x0c, 0x45, 0x66, 0xb2,
0x01, 0x79, 0xd4, 0x52, 0xcb, 0x0b, 0xa1, 0xab, 0x63, 0x37, 0xb0, 0x37, 0x43, 0x85, 0x00, 0x79,
0x0c, 0x0b, 0xa7, 0x8c, 0x3b, 0xae, 0xc3, 0x9d, 0x1a, 0x2c, 0xe7, 0x56, 0x8b, 0xeb, 0xf5, 0x4c,
0x61, 0x0c, 0x90, 0x7d, 0xa8, 0x24, 0x76, 0x02, 0x1e, 0x9f, 0xd3, 0xae, 0x02, 0xf2, 0x1c, 0x4a,
0x0e, 0xe7, 0x0c, 0x93, 0xe1, 0x85, 0x41, 0x52, 0x2b, 0x09, 0x85, 0xb7, 0xc7, 0x2b, 0x7c, 0xa4,
0x49, 0x49, 0xa5, 0x7d, 0x8a, 0xcc, 0x07, 0x50, 0xee, 0xb3, 0x49, 0xaa, 0x90, 0x7b, 0xc1, 0xce,
0x65, 0x62, 0x28, 0x0e, 0xc9, 0x65, 0x98, 0x7b, 0xe9, 0xf8, 0x1d, 0x26, 0x72, 0x50, 0xa2, 0x72,
0x72, 0x7f, 0xf6, 0x9e, 0x61, 0x9e, 0xc0, 0xa5, 0x21, 0xfd, 0x23, 0x14, 0xfc, 0x40, 0x57, 0x50,
0x5c, 0xff, 0x30, 0x63, 0xd7, 0xba, 0x3a, 0xcd, 0xd2, 0xe6, 0x02, 0x14, 0x62, 0xe1, 0x90, 0xf5,
0x1b, 0x03, 0xaa, 0x83, 0xa9, 0x26, 0xfb, 0x2a, 0x49, 0x86, 0x08, 0xcb, 0xdd, 0x29, 0x4e, 0x09,
0x12, 0x54, 0x60, 0x84, 0x0a, 0x73, 0x03, 0x16, 0xbb, 0xa4, 0x71, 0xc1, 0x58, 0xd4, 0xb6, 0x68,
0x6d, 0x40, 0x8e, 0xb2, 0x16, 0xa9, 0xc0, 0xac, 0xa7, 0xce, 0x35, 0x9d, 0xf5, 0x5c, 0xb2, 0x0c,
0x39, 0x97, 0xb5, 0x94, 0xeb, 0x15, 0x3b, 0x3a, 0xb6, 0xb7, 0x59, 0xcb, 0x0b, 0x3c, 0x74, 0x91,
0xe2, 0x92, 0xf5, 0x3b, 0x03, 0xeb, 0x03, 0xb7, 0x45, 0x3e, 0xe9, 0xf3, 0x63, 0xfc, 0x69, 0x1f,
0xda, 0xfd, 0xf3, 0xec, 0xdd, 0xdf, 0xe9, 0xcf, 0xc4, 0x98, 0x12, 0xd0, 0xbd, 0xfb, 0x31, 0x94,
0xf4, 0xdc, 0x90, 0x3d, 0x28, 0x6a, 0xe7, 0x48, 0x6d, 0x78, 0x65, 0xb2, 0xcc, 0x52, 0x5d, 0xd4,
0xfa, 0x43, 0x0e, 0x8a, 0xda, 0x22, 0x79, 0x08, 0xf9, 0x17, 0x5e, 0x20, 0x43, 0x58, 0x59, 0x5f,
0x9b, 0x4c, 0xe5, 0x63, 0x2f, 0x70, 0xa9, 0x90, 0x23, 0x0d, 0xad, 0xee, 0x66, 0xc5, 0xb6, 0xee,
0x4c, 0xa6, 0xe3, 0xc2, 0xe2, 0xbb, 0x35, 0x45, 0xdb, 0x90, 0x4d, 0x83, 0x40, 0x3e, 0x72, 0xf8,
0x89, 0x68, 0x1a, 0x8b, 0x54, 0x8c, 0xc9, 0x2d, 0x78, 0xcb, 0x0b, 0x9e, 0x85, 0x3c, 0x6c, 0xc4,
0xcc, 0xf5, 0xf0, 0xf0, 0x3d, 0x3b, 0x8f, 0x58, 0x6d, 0x4e, 0xb0, 0x8c, 0x5a, 0x22, 0x0d, 0xa8,
0x48, 0xf2, 0x51, 0xe7, 0xf8, 0x67, 0xac, 0xc9, 0x93, 0x5a, 0x41, 0xf8, 0xb3, 0x9a, 0xb1, 0x85,
0x7d, 0x5d, 0x80, 0x0e, 0xc8, 0xbf, 0x51, 0xb5, 0x5b, 0x7f, 0x36, 0xa0, 0xdc, 0xa7, 0x9e, 0x7c,
0xda, 0x97, 0xaa, 0x9b, 0x93, 0x6e, 0x4b, 0x4b, 0xd6, 0x67, 0x50, 0x70, 0xbd, 0x36, 0x4b, 0xb8,
0x48, 0xd5, 0xe2, 0xe6, 0xfa, 0xb7, 0xdf, 0xbd, 0x3f, 0xf3, 0x8f, 0xef, 0xde, 0x5f, 0xd3, 0xae,
0x9a, 0x30, 0x62, 0x41, 0x33, 0x0c, 0xb8, 0xe3, 0x05, 0x2c, 0xc6, 0x0b, 0xf6, 0x63, 0x29, 0x62,
0x6f, 0x8b, 0x1f, 0xaa, 0x34, 0x60, 0xd0, 0x03, 0xe7, 0x94, 0x89, 0x3c, 0x2d, 0x52, 0x31, 0xb6,
0x38, 0x94, 0x29, 0xe3, 0x9d, 0x38, 0xa0, 0xec, 0xe7, 0x1d, 0x64, 0xfa, 0x5e, 0xda, 0x48, 0xc4,
0xa6, 0xc7, 0x35, 0x74, 0x64, 0xa4, 0x4a, 0x80, 0xac, 0xc2, 0x1c, 0x8b, 0xe3, 0x30, 0x56, 0xc5,
0x43, 0x6c, 0x79, 0xd5, 0xdb, 0x71, 0xd4, 0xb4, 0x8f, 0xc4, 0x55, 0x4f, 0x25, 0x83, 0x55, 0x85,
0x4a, 0x6a, 0x35, 0x89, 0xc2, 0x20, 0x61, 0xd6, 0x12, 0x86, 0x2e, 0xea, 0xf0, 0x44, 0xed, 0xc3,
0xfa, 0xab, 0x01, 0x95, 0x94, 0x22, 0x79, 0xc8, 0x17, 0x50, 0xec, 0xb5, 0x86, 0xb4, 0x07, 0xdc,
0xcf, 0x0c, 0xaa, 0x2e, 0xaf, 0xf5, 0x15, 0xd5, 0x12, 0x74, 0x75, 0xe6, 0x13, 0xa8, 0x0e, 0x32,
0x8c, 0xc8, 0xfe, 0x07, 0xfd, 0x0d, 0x62, 0xb0, 0x5f, 0x69, 0xa7, 0xe1, 0x5f, 0x06, 0xbc, 0x4b,
0x99, 0xc0, 0x2e, 0xfb, 0xa7, 0x4e, 0x9b, 0x6d, 0x85, 0x41, 0xcb, 0x6b, 0xa7, 0x61, 0xae, 0x8a,
0x66, 0x98, 0x6a, 0xc6, 0xbe, 0xb8, 0x0a, 0x0b, 0x0d, 0xdf, 0xe1, 0xad, 0x30, 0x3e, 0x55, 0xca,
0x4b, 0xa8, 0x3c, 0xa5, 0xd1, 0xee, 0x2a, 0x59, 0x86, 0xa2, 0x52, 0x7c, 0x18, 0xba, 0x69, 0x3a,
0x75, 0x12, 0xa9, 0xc1, 0xfc, 0x41, 0xd8, 0x7e, 0x82, 0xc9, 0x96, 0x15, 0x96, 0x4e, 0x89, 0x05,
0x25, 0xc5, 0x18, 0x77, 0xab, 0x6b, 0x8e, 0xf6, 0xd1, 0xc8, 0x7b, 0xb0, 0x78, 0xc4, 0x92, 0xc4,
0x0b, 0x83, 0xfd, 0xed, 0x5a, 0x41, 0xc8, 0xf7, 0x08, 0xa8, 0xfb, 0x88, 0x87, 0x31, 0xdb, 0xdf,
0xae, 0xcd, 0x4b, 0xdd, 0x6a, 0x6a, 0xfd, 0xc2, 0x00, 0x73, 0x94, 0xc7, 0x2a, 0x7d, 0x9f, 0x41,
0x41, 0x1e, 0x48, 0xe9, 0xf5, 0xff, 0x76, 0x94, 0xe5, 0x2f, 0x79, 0x07, 0x0a, 0x52, 0xbb, 0xaa,
0x42, 0x35, 0xb3, 0x7e, 0x55, 0x80, 0xd2, 0x11, 0x6e, 0x20, 0x8d, 0xb3, 0x0d, 0xd0, 0x4b, 0x8f,
0x3a, 0xd2, 0x83, 0x49, 0xd3, 0x38, 0x88, 0x09, 0x0b, 0xbb, 0xea, 0xf8, 0xa8, 0x1b, 0xac, 0x3b,
0x27, 0x9f, 0x43, 0x31, 0x1d, 0x3f, 0x8d, 0x78, 0x2d, 0x27, 0xce, 0xdf, 0xbd, 0x8c, 0xf3, 0xa7,
0xef, 0xc4, 0xd6, 0x44, 0xd5, 0xe9, 0xd3, 0x28, 0xe4, 0x26, 0x5c, 0x72, 0x7c, 0x3f, 0x3c, 0x53,
0x25, 0x25, 0x8a, 0x43, 0x24, 0x67, 0x81, 0x0e, 0x2f, 0x60, 0xab, 0xd4, 0x88, 0x8f, 0xe2, 0xd8,
0x39, 0xc7, 0xd3, 0x54, 0x10, 0xfc, 0xa3, 0x96, 0xb0, 0x6b, 0xed, 0x7a, 0x81, 0xe3, 0xd7, 0x40,
0xf0, 0xc8, 0x09, 0x9e, 0x86, 0x9d, 0x57, 0x51, 0x18, 0x73, 0x16, 0x3f, 0xe2, 0x3c, 0xae, 0x15,
0x45, 0x30, 0xfb, 0x68, 0xa4, 0x01, 0xa5, 0x2d, 0xa7, 0x79, 0xc2, 0xf6, 0x4f, 0x91, 0x98, 0x22,
0xab, 0xac, 0x5e, 0x26, 0xd8, 0x9f, 0x46, 0x3a, 0xa4, 0xd2, 0x35, 0x90, 0x26, 0x54, 0x52, 0xd7,
0x65, 0x85, 0xd6, 0xca, 0x42, 0xe7, 0x83, 0x69, 0x43, 0x29, 0xa5, 0xa5, 0x89, 0x01, 0x95, 0x98,
0xc8, 0x1d, 0x2c, 0x46, 0x87, 0xb3, 0x5a, 0x45, 0xf8, 0xdc, 0x9d, 0x93, 0x43, 0xa8, 0x1c, 0x09,
0x40, 0xde, 0x40, 0x18, 0xee, 0xb1, 0xa4, 0xb6, 0x24, 0x36, 0x70, 0x7d, 0x78, 0x03, 0x3a, 0x70,
0xb7, 0x05, 0xfb, 0x39, 0x1d, 0x10, 0x36, 0x1f, 0x42, 0x75, 0x30, 0xb9, 0xd3, 0x00, 0x23, 0xf3,
0x87, 0xf0, 0xd6, 0x08, 0x8f, 0xde, 0xa8, 0xf9, 0xfc, 0xc9, 0x80, 0x4b, 0x43, 0x69, 0xc0, 0x0b,
0x40, 0x14, 0xbd, 0x54, 0x29, 0xc6, 0xe4, 0x10, 0xe6, 0x30, 0xcd, 0x89, 0x82, 0x02, 0x1b, 0xd3,
0xe4, 0xd5, 0x16, 0x92, 0x32, 0xfe, 0x52, 0x8b, 0x79, 0x0f, 0xa0, 0x47, 0x9c, 0x0a, 0x1e, 0x7e,
0x01, 0x65, 0x95, 0x64, 0xd5, 0x2f, 0xaa, 0x12, 0x55, 0x28, 0x61, 0x44, 0x0d, 0xbd, 0xbb, 0x29,
0x37, 0xe5, 0xdd, 0x64, 0x7d, 0x05, 0x4b, 0x94, 0x39, 0xee, 0xae, 0xe7, 0xb3, 0x8b, 0x5b, 0x30,
0x16, 0xbf, 0xe7, 0xb3, 0x06, 0x22, 0x93, 0xb4, 0xf8, 0xd5, 0x9c, 0xdc, 0x87, 0x39, 0xea, 0x04,
0x6d, 0xa6, 0x4c, 0x7f, 0x90, 0x61, 0x5a, 0x18, 0x41, 0x5e, 0x2a, 0x45, 0xac, 0x07, 0xb0, 0xd8,
0xa5, 0x61, 0xeb, 0x7a, 0xda, 0x6a, 0x25, 0x4c, 0xb6, 0xc1, 0x1c, 0x55, 0x33, 0xa4, 0x1f, 0xb0,
0xa0, 0xad, 0x4c, 0xe7, 0xa8, 0x9a, 0x59, 0x2b, 0x08, 0xe7, 0xd3, 0x9d, 0xab, 0xd0, 0x10, 0xc8,
0x6f, 0x23, 0x7c, 0x33, 0x44, 0xbd, 0x8a, 0xb1, 0xe5, 0xe2, 0x9d, 0xea, 0xb8, 0xdb, 0x5e, 0x7c,
0xb1, 0x83, 0x35, 0x98, 0xdf, 0xf6, 0x62, 0xcd, 0xbf, 0x74, 0x4a, 0x56, 0xf0, 0xb6, 0x6d, 0xfa,
0x1d, 0x17, 0xbd, 0xe5, 0x2c, 0x0e, 0xd4, 0xb5, 0x32, 0x40, 0xb5, 0x3e, 0x91, 0x71, 0x14, 0x56,
0xd4, 0x66, 0x6e, 0xc2, 0x3c, 0x0b, 0x78, 0x8c, 0x65, 0x24, 0xaf, 0x64, 0x62, 0xcb, 0x07, 0xb2,
0x2d, 0x1e, 0xc8, 0xe2, 0xea, 0xa7, 0x29, 0x8b, 0xb5, 0x01, 0x4b, 0x48, 0xc8, 0x4e, 0x04, 0x81,
0xbc, 0xb6, 0x49, 0x31, 0xb6, 0xee, 0x43, 0xb5, 0x27, 0xa8, 0x4c, 0xaf, 0x40, 0x1e, 0xb1, 0xa9,
0xea, 0xeb, 0xa3, 0xec, 0x8a, 0x75, 0xeb, 0x1a, 0x2c, 0xa5, 0xc5, 0x7f, 0xa1, 0x51, 0x8b, 0x40,
0xb5, 0xc7, 0xa4, 0x60, 0x49, 0x19, 0x8a, 0x0d, 0x2f, 0x48, 0x6f, 0x6d, 0xeb, 0xb5, 0x01, 0xa5,
0x46, 0x18, 0xf4, 0xee, 0xb4, 0x06, 0x2c, 0xa5, 0xa5, 0xfb, 0xa8, 0xb1, 0xbf, 0xe5, 0x44, 0x69,
0x0c, 0x96, 0x87, 0xcf, 0x87, 0xfa, 0xc4, 0x60, 0x4b, 0xc6, 0xcd, 0x3c, 0x5e, 0x7f, 0x74, 0x50,
0x9c, 0x7c, 0x0a, 0xf3, 0x07, 0x07, 0x9b, 0x42, 0xd3, 0xec, 0x54, 0x9a, 0x52, 0x31, 0xf2, 0x10,
0xe6, 0x9f, 0x8b, 0x2f, 0x1f, 0x89, 0xba, 0xa2, 0x46, 0x9c, 0x55, 0x19, 0x21, 0xc9, 0x46, 0x59,
0x33, 0x8c, 0x5d, 0x9a, 0x0a, 0x59, 0xff, 0x36, 0xa0, 0xf8, 0xdc, 0xe9, 0x21, 0xc2, 0x1e, 0x04,
0x7d, 0x83, 0x7b, 0x5b, 0x41, 0xd0, 0xcb, 0x30, 0xe7, 0xb3, 0x97, 0xcc, 0x57, 0x67, 0x5c, 0x4e,
0x90, 0x9a, 0x9c, 0x84, 0xb1, 0x2c, 0xeb, 0x12, 0x95, 0x13, 0x2c, 0x08, 0x97, 0x71, 0xc7, 0xf3,
0x6b, 0xf9, 0xe5, 0x1c, 0xde, 0xf1, 0x72, 0x86, 0x99, 0xeb, 0xc4, 0xbe, 0x7a, 0x17, 0xe0, 0x90,
0x58, 0x90, 0xf7, 0x82, 0x56, 0x28, 0xee, 0x3f, 0xd5, 0x16, 0x65, 0x8b, 0xde, 0x0f, 0x5a, 0x21,
0x15, 0x6b, 0xe4, 0x2a, 0x14, 0x62, 0xac, 0xbf, 0xa4, 0x36, 0x2f, 0x82, 0xb2, 0x88, 0x5c, 0xb2,
0x4a, 0xd5, 0x82, 0x55, 0x81, 0x92, 0xf4, 0x5b, 0x25, 0xff, 0x8f, 0xb3, 0xf0, 0xd6, 0x13, 0x76,
0xb6, 0x95, 0xfa, 0x95, 0x06, 0x64, 0x19, 0x8a, 0x5d, 0xda, 0xfe, 0xb6, 0x3a, 0x42, 0x3a, 0x09,
0x8d, 0x1d, 0x86, 0x9d, 0x80, 0xa7, 0x39, 0x14, 0xc6, 0x04, 0x85, 0xaa, 0x05, 0x72, 0x1d, 0xe6,
0x9f, 0x30, 0x7e, 0x16, 0xc6, 0x2f, 0x84, 0xd7, 0x95, 0xf5, 0x22, 0xf2, 0x3c, 0x61, 0x1c, 0x01,
0x1c, 0x4d, 0xd7, 0x10, 0x15, 0x46, 0x29, 0x2a, 0xcc, 0x8f, 0x42, 0x85, 0xe9, 0x2a, 0xd9, 0x80,
0x62, 0x33, 0x0c, 0x12, 0x1e, 0x3b, 0x1e, 0x1a, 0x9e, 0x13, 0xcc, 0x6f, 0x23, 0xb3, 0x4c, 0xec,
0x56, 0x6f, 0x91, 0xea, 0x9c, 0x64, 0x0d, 0x80, 0xbd, 0xe2, 0xb1, 0xb3, 0x17, 0x26, 0xdd, 0x17,
0x14, 0xa0, 0x1c, 0x12, 0xf6, 0x1b, 0x54, 0x5b, 0xc5, 0x0e, 0x79, 0x12, 0x26, 0x5c, 0x3c, 0x23,
0x24, 0xfa, 0xeb, 0xce, 0xad, 0x77, 0xe0, 0x72, 0x7f, 0xb4, 0x54, 0x18, 0x1f, 0xc0, 0xff, 0x51,
0xe6, 0x33, 0x27, 0x61, 0xd3, 0x47, 0xd2, 0x32, 0xa1, 0x36, 0x2c, 0xac, 0x14, 0xff, 0x27, 0x07,
0xc5, 0x9d, 0x57, 0xac, 0x79, 0xc8, 0x92, 0xc4, 0x69, 0x0b, 0xdc, 0xda, 0x88, 0xc3, 0x26, 0x4b,
0x92, 0xae, 0xae, 0x1e, 0x81, 0x7c, 0x1f, 0xf2, 0xfb, 0x81, 0xc7, 0xd5, 0xdd, 0xb9, 0x92, 0xf9,
0x6c, 0xf0, 0xb8, 0xd2, 0xb9, 0x37, 0x43, 0x85, 0x14, 0xb9, 0x0f, 0x79, 0xec, 0x3c, 0x93, 0x74,
0x7f, 0x57, 0x93, 0x45, 0x19, 0xb2, 0x29, 0x3e, 0xef, 0x79, 0x5f, 0x32, 0x95, 0xc1, 0xd5, 0xec,
0x6b, 0xcb, 0xfb, 0x92, 0xf5, 0x34, 0x28, 0x49, 0xb2, 0x83, 0xa8, 0xdb, 0x89, 0x39, 0x73, 0x55,
0x66, 0x6f, 0x64, 0x81, 0x25, 0xc9, 0xd9, 0xd3, 0x92, 0xca, 0x62, 0x10, 0x76, 0x5e, 0x79, 0x5c,
0x55, 0x4a, 0x56, 0x10, 0x90, 0x4d, 0x73, 0x04, 0xa7, 0x28, 0xbd, 0x1d, 0x06, 0x32, 0xf3, 0xd9,
0xd2, 0xc8, 0xa6, 0x49, 0xe3, 0x14, 0xc3, 0x70, 0xe4, 0xb5, 0x11, 0x83, 0x2e, 0x8c, 0x0d, 0x83,
0x64, 0xd4, 0xc2, 0x20, 0x09, 0x9b, 0xf3, 0x30, 0x27, 0x20, 0x92, 0xf5, 0x5b, 0x03, 0x8a, 0x5a,
0x9e, 0x26, 0xa8, 0xc9, 0xf7, 0x20, 0x8f, 0x4f, 0x7b, 0x95, 0xff, 0x05, 0x51, 0x91, 0x8c, 0x3b,
0x54, 0x50, 0xb1, 0xa9, 0xec, 0xba, 0xb2, 0x61, 0x96, 0x29, 0x0e, 0x91, 0xf2, 0x8c, 0x9f, 0x8b,
0x94, 0x2d, 0x50, 0x1c, 0x92, 0x9b, 0xb0, 0x70, 0xc4, 0x9a, 0x9d, 0xd8, 0xe3, 0xe7, 0x22, 0x09,
0x95, 0xf5, 0xaa, 0x68, 0x35, 0x8a, 0x26, 0x0a, 0xb7, 0xcb, 0x61, 0x3d, 0xc6, 0xc3, 0xd9, 0xdb,
0x20, 0x81, 0xfc, 0x16, 0xbe, 0xd6, 0x70, 0x67, 0x65, 0x2a, 0xc6, 0xf8, 0x60, 0xde, 0x19, 0xf7,
0x60, 0xde, 0x49, 0x1f, 0xcc, 0xfd, 0x49, 0xc5, 0x9b, 0x49, 0x0b, 0xb2, 0xf5, 0x08, 0x16, 0xbb,
0x07, 0x8f, 0x54, 0x60, 0x76, 0xd7, 0x55, 0x96, 0x66, 0x77, 0x5d, 0x74, 0x65, 0xe7, 0xe9, 0xae,
0xb0, 0xb2, 0x40, 0x71, 0xd8, 0x05, 0x10, 0x39, 0x0d, 0x40, 0x6c, 0x40, 0xb9, 0xef, 0xf4, 0x21,
0x13, 0x0d, 0xcf, 0x92, 0x74, 0xcb, 0x38, 0x96, 0x6e, 0xf8, 0x89, 0xd0, 0x25, 0xdc, 0xf0, 0x13,
0xeb, 0x1a, 0x94, 0xfb, 0xf2, 0x85, 0x4c, 0xe2, 0xed, 0xa9, 0x70, 0x26, 0x8e, 0xd7, 0x18, 0x2c,
0x0d, 0x7c, 0x8e, 0x22, 0xd7, 0xa1, 0x20, 0x3f, 0x7b, 0x54, 0x67, 0xcc, 0x77, 0xbf, 0xfe, 0x66,
0xf9, 0xed, 0x01, 0x06, 0xb9, 0x88, 0x6c, 0x9b, 0x9d, 0xc0, 0xf5, 0x59, 0xd5, 0x18, 0xc9, 0x26,
0x17, 0xcd, 0xfc, 0xaf, 0x7f, 0x7f, 0x65, 0x66, 0xcd, 0x81, 0x4b, 0x43, 0x9f, 0x52, 0xc8, 0x35,
0xc8, 0x1f, 0x31, 0xbf, 0x95, 0x9a, 0x19, 0x62, 0xc0, 0x45, 0x72, 0x15, 0x72, 0xd4, 0x39, 0xab,
0x1a, 0x66, 0xed, 0xeb, 0x6f, 0x96, 0x2f, 0x0f, 0x7f, 0x8f, 0x71, 0xce, 0xa4, 0x89, 0xf5, 0xbf,
0x00, 0x2c, 0x1e, 0x1c, 0x6c, 0x6e, 0xc6, 0x9e, 0xdb, 0x66, 0xe4, 0x97, 0x06, 0x90, 0xe1, 0x47,
0x2f, 0xb9, 0x93, 0x5d, 0xe3, 0xa3, 0xbf, 0x0a, 0x98, 0x77, 0xa7, 0x94, 0x52, 0x28, 0xe4, 0x73,
0x98, 0x13, 0xd0, 0x99, 0x7c, 0x38, 0xe1, 0x0b, 0xca, 0x5c, 0x1d, 0xcf, 0xa8, 0x74, 0x37, 0x61,
0x21, 0x85, 0x9f, 0x64, 0x2d, 0x73, 0x7b, 0x7d, 0xe8, 0xda, 0xfc, 0x68, 0x22, 0x5e, 0x65, 0xe4,
0xa7, 0x30, 0xaf, 0x50, 0x25, 0xb9, 0x31, 0x46, 0xae, 0x87, 0x6f, 0xcd, 0xb5, 0x49, 0x58, 0x7b,
0x6e, 0xa4, 0xe8, 0x31, 0xd3, 0x8d, 0x01, 0x6c, 0x9a, 0xe9, 0xc6, 0x10, 0x1c, 0x6d, 0xf6, 0xde,
0x9c, 0x99, 0x46, 0x06, 0xb0, 0x68, 0xa6, 0x91, 0x41, 0x48, 0x4a, 0x9e, 0x43, 0x1e, 0x21, 0x29,
0xc9, 0x6a, 0xbf, 0x1a, 0x66, 0x35, 0xb3, 0xce, 0x44, 0x1f, 0x96, 0xfd, 0x09, 0x5e, 0x53, 0xe2,
0xf3, 0x42, 0xf6, 0x05, 0xa5, 0x7d, 0x2d, 0x34, 0x6f, 0x4c, 0xc0, 0xd9, 0x53, 0xaf, 0x9e, 0xe6,
0xab, 0x13, 0x7c, 0xb2, 0x1b, 0xaf, 0x7e, 0xe0, 0xe3, 0x60, 0x08, 0x25, 0x1d, 0x7d, 0x10, 0x3b,
0x43, 0x74, 0x04, 0xa8, 0x33, 0xeb, 0x13, 0xf3, 0x2b, 0x83, 0x5f, 0xe1, 0xbb, 0xac, 0x1f, 0x99,
0x90, 0xf5, 0xcc, 0x70, 0x8c, 0xc4, 0x40, 0xe6, 0xed, 0xa9, 0x64, 0x94, 0x71, 0x47, 0x22, 0x1f,
0x85, 0x6e, 0x48, 0xf6, 0x45, 0xde, 0x45, 0x48, 0xe6, 0x84, 0x7c, 0xab, 0xc6, 0x2d, 0x03, 0xcf,
0x19, 0xa2, 0xe1, 0x4c, 0xdd, 0xda, 0x33, 0x21, 0xf3, 0x9c, 0xe9, 0xb0, 0x7a, 0xb3, 0xf4, 0xed,
0xeb, 0x2b, 0xc6, 0xdf, 0x5f, 0x5f, 0x31, 0xfe, 0xf9, 0xfa, 0x8a, 0x71, 0x5c, 0x10, 0xff, 0x81,
0xde, 0xfe, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xec, 0xb9, 0xed, 0xf9, 0x8c, 0x1e, 0x00, 0x00,
0xf5, 0xd7, 0x8a, 0x14, 0x45, 0x3e, 0xfe, 0x10, 0x3d, 0x71, 0xf2, 0xa5, 0x17, 0x81, 0x23, 0xaf,
0x63, 0x45, 0x96, 0x1d, 0xd2, 0x5f, 0xd9, 0x86, 0x5c, 0xbb, 0x75, 0x62, 0xfd, 0x82, 0x14, 0x4b,
0x36, 0x3b, 0x72, 0xe1, 0x22, 0x48, 0x81, 0xae, 0xb8, 0x43, 0x6a, 0xeb, 0xd5, 0xee, 0x76, 0x77,
0x28, 0x59, 0xc9, 0xa9, 0x87, 0x02, 0x45, 0x8e, 0x3d, 0xf4, 0x96, 0x4b, 0x0b, 0xf4, 0xd4, 0x43,
0xfb, 0x07, 0x34, 0xe7, 0x00, 0xed, 0xa1, 0xe7, 0x1e, 0x82, 0xc2, 0x7f, 0x44, 0x81, 0xde, 0x8a,
0x37, 0x33, 0x4b, 0x0e, 0x7f, 0x68, 0x45, 0xd6, 0x27, 0xce, 0xbc, 0x79, 0x3f, 0xe6, 0xbd, 0x37,
0xef, 0xcd, 0x67, 0x96, 0x50, 0xee, 0xd8, 0x9c, 0x9d, 0xda, 0x67, 0xf5, 0x30, 0x0a, 0x78, 0x40,
0xae, 0x1c, 0x07, 0x87, 0x67, 0xf5, 0xc3, 0xae, 0xeb, 0x39, 0xaf, 0x5c, 0x5e, 0x3f, 0xf9, 0xff,
0x7a, 0x3b, 0x0a, 0x7c, 0xce, 0x7c, 0xc7, 0xfc, 0xb8, 0xe3, 0xf2, 0xa3, 0xee, 0x61, 0xbd, 0x15,
0x1c, 0x37, 0x3a, 0x41, 0x27, 0x68, 0x08, 0x89, 0xc3, 0x6e, 0x5b, 0xcc, 0xc4, 0x44, 0x8c, 0xa4,
0x26, 0x73, 0x75, 0x98, 0xbd, 0x13, 0x04, 0x1d, 0x8f, 0xd9, 0xa1, 0x1b, 0xab, 0x61, 0x23, 0x0a,
0x5b, 0x8d, 0x98, 0xdb, 0xbc, 0x1b, 0x2b, 0x99, 0xdb, 0x9a, 0x0c, 0x6e, 0xa4, 0x91, 0x6c, 0xa4,
0x11, 0x07, 0xde, 0x09, 0x8b, 0x1a, 0xe1, 0x61, 0x23, 0x08, 0x13, 0xee, 0xc6, 0xb9, 0xdc, 0x76,
0xe8, 0x36, 0xf8, 0x59, 0xc8, 0xe2, 0xc6, 0x69, 0x10, 0xbd, 0x62, 0x91, 0x12, 0xb8, 0x7b, 0xae,
0x40, 0x97, 0xbb, 0x1e, 0x4a, 0xb5, 0xec, 0x30, 0x46, 0x23, 0xf8, 0xab, 0x84, 0x74, 0xb7, 0x79,
0xe0, 0xbb, 0x31, 0x77, 0xdd, 0x8e, 0xdb, 0x68, 0xc7, 0x42, 0x46, 0x5a, 0x41, 0x27, 0x14, 0xfb,
0xfd, 0x14, 0x17, 0xba, 0x51, 0x8b, 0x85, 0x81, 0xe7, 0xb6, 0xce, 0xd0, 0x86, 0x1c, 0x49, 0x31,
0xeb, 0x6f, 0x59, 0xc8, 0x51, 0x16, 0x77, 0x3d, 0x4e, 0x96, 0xa0, 0x1c, 0xb1, 0xf6, 0x26, 0x0b,
0x23, 0xd6, 0xb2, 0x39, 0x73, 0x6a, 0xc6, 0xa2, 0xb1, 0x5c, 0xd8, 0x99, 0xa1, 0x83, 0x64, 0xf2,
0x13, 0xa8, 0x44, 0xac, 0x1d, 0x6b, 0x8c, 0xb3, 0x8b, 0xc6, 0x72, 0x71, 0xf5, 0x56, 0xfd, 0xdc,
0x1c, 0xd6, 0x29, 0x6b, 0xef, 0xdb, 0x61, 0x5f, 0x64, 0x67, 0x86, 0x0e, 0x29, 0x21, 0xab, 0x90,
0x89, 0x58, 0xbb, 0x96, 0x11, 0xba, 0xae, 0xa6, 0xeb, 0xda, 0x99, 0xa1, 0xc8, 0x4c, 0xd6, 0x20,
0x8b, 0x5a, 0x6a, 0x59, 0x21, 0x74, 0xed, 0xc2, 0x0d, 0xec, 0xcc, 0x50, 0x21, 0x40, 0x9e, 0x42,
0xfe, 0x98, 0x71, 0xdb, 0xb1, 0xb9, 0x5d, 0x83, 0xc5, 0xcc, 0x72, 0x71, 0xb5, 0x91, 0x2a, 0x8c,
0x01, 0xaa, 0xef, 0x2b, 0x89, 0x2d, 0x9f, 0x47, 0x67, 0xb4, 0xa7, 0x80, 0xbc, 0x84, 0x92, 0xcd,
0x39, 0xc3, 0x64, 0xb8, 0x81, 0x1f, 0xd7, 0x4a, 0x42, 0xe1, 0xdd, 0x8b, 0x15, 0x3e, 0xd1, 0xa4,
0xa4, 0xd2, 0x01, 0x45, 0xe6, 0x23, 0x28, 0x0f, 0xd8, 0x24, 0x55, 0xc8, 0xbc, 0x62, 0x67, 0x32,
0x31, 0x14, 0x87, 0xe4, 0x32, 0xcc, 0x9d, 0xd8, 0x5e, 0x97, 0x89, 0x1c, 0x94, 0xa8, 0x9c, 0x3c,
0x9c, 0x7d, 0x60, 0x98, 0x47, 0x70, 0x69, 0x44, 0xff, 0x18, 0x05, 0x3f, 0xd2, 0x15, 0x14, 0x57,
0x3f, 0x4a, 0xd9, 0xb5, 0xae, 0x4e, 0xb3, 0xb4, 0x9e, 0x87, 0x5c, 0x24, 0x1c, 0xb2, 0x7e, 0x67,
0x40, 0x75, 0x38, 0xd5, 0x64, 0x57, 0x25, 0xc9, 0x10, 0x61, 0xb9, 0x3f, 0xc5, 0x29, 0x41, 0x82,
0x0a, 0x8c, 0x50, 0x61, 0xae, 0x41, 0xa1, 0x47, 0xba, 0x28, 0x18, 0x05, 0x6d, 0x8b, 0xd6, 0x1a,
0x64, 0x28, 0x6b, 0x93, 0x0a, 0xcc, 0xba, 0xea, 0x5c, 0xd3, 0x59, 0xd7, 0x21, 0x8b, 0x90, 0x71,
0x58, 0x5b, 0xb9, 0x5e, 0xa9, 0x87, 0x87, 0xf5, 0x4d, 0xd6, 0x76, 0x7d, 0x17, 0x5d, 0xa4, 0xb8,
0x64, 0xfd, 0xde, 0xc0, 0xfa, 0xc0, 0x6d, 0x91, 0x4f, 0x06, 0xfc, 0xb8, 0xf8, 0xb4, 0x8f, 0xec,
0xfe, 0x65, 0xfa, 0xee, 0xef, 0x0d, 0x66, 0xe2, 0x82, 0x12, 0xd0, 0xbd, 0xfb, 0x29, 0x94, 0xf4,
0xdc, 0x90, 0x1d, 0x28, 0x6a, 0xe7, 0x48, 0x6d, 0x78, 0x69, 0xb2, 0xcc, 0x52, 0x5d, 0xd4, 0xfa,
0x63, 0x06, 0x8a, 0xda, 0x22, 0x79, 0x0c, 0xd9, 0x57, 0xae, 0x2f, 0x43, 0x58, 0x59, 0x5d, 0x99,
0x4c, 0xe5, 0x53, 0xd7, 0x77, 0xa8, 0x90, 0x23, 0x4d, 0xad, 0xee, 0x66, 0xc5, 0xb6, 0xee, 0x4d,
0xa6, 0xe3, 0xdc, 0xe2, 0xbb, 0x33, 0x45, 0xdb, 0x90, 0x4d, 0x83, 0x40, 0x36, 0xb4, 0xf9, 0x91,
0x68, 0x1a, 0x05, 0x2a, 0xc6, 0xe4, 0x0e, 0xbc, 0xe3, 0xfa, 0x2f, 0x02, 0x1e, 0x34, 0x23, 0xe6,
0xb8, 0x78, 0xf8, 0x5e, 0x9c, 0x85, 0xac, 0x36, 0x27, 0x58, 0xc6, 0x2d, 0x91, 0x26, 0x54, 0x24,
0xf9, 0xa0, 0x7b, 0xf8, 0x0b, 0xd6, 0xe2, 0x71, 0x2d, 0x27, 0xfc, 0x59, 0x4e, 0xd9, 0xc2, 0xae,
0x2e, 0x40, 0x87, 0xe4, 0xdf, 0xaa, 0xda, 0xad, 0xbf, 0x18, 0x50, 0x1e, 0x50, 0x4f, 0x3e, 0x1d,
0x48, 0xd5, 0xed, 0x49, 0xb7, 0xa5, 0x25, 0xeb, 0x33, 0xc8, 0x39, 0x6e, 0x87, 0xc5, 0x5c, 0xa4,
0xaa, 0xb0, 0xbe, 0xfa, 0xdd, 0xf7, 0x1f, 0xcc, 0xfc, 0xf3, 0xfb, 0x0f, 0x56, 0xb4, 0xab, 0x26,
0x08, 0x99, 0xdf, 0x0a, 0x7c, 0x6e, 0xbb, 0x3e, 0x8b, 0xf0, 0x82, 0xfd, 0x58, 0x8a, 0xd4, 0x37,
0xc5, 0x0f, 0x55, 0x1a, 0x30, 0xe8, 0xbe, 0x7d, 0xcc, 0x44, 0x9e, 0x0a, 0x54, 0x8c, 0x2d, 0x0e,
0x65, 0xca, 0x78, 0x37, 0xf2, 0x29, 0xfb, 0x65, 0x17, 0x99, 0x7e, 0x90, 0x34, 0x12, 0xb1, 0xe9,
0x8b, 0x1a, 0x3a, 0x32, 0x52, 0x25, 0x40, 0x96, 0x61, 0x8e, 0x45, 0x51, 0x10, 0xa9, 0xe2, 0x21,
0x75, 0x79, 0xd5, 0xd7, 0xa3, 0xb0, 0x55, 0x3f, 0x10, 0x57, 0x3d, 0x95, 0x0c, 0x56, 0x15, 0x2a,
0x89, 0xd5, 0x38, 0x0c, 0xfc, 0x98, 0x59, 0x0b, 0x18, 0xba, 0xb0, 0xcb, 0x63, 0xb5, 0x0f, 0xeb,
0x5b, 0x03, 0x2a, 0x09, 0x45, 0xf2, 0x90, 0x2f, 0xa0, 0xd8, 0x6f, 0x0d, 0x49, 0x0f, 0x78, 0x98,
0x1a, 0x54, 0x5d, 0x5e, 0xeb, 0x2b, 0xaa, 0x25, 0xe8, 0xea, 0xcc, 0x67, 0x50, 0x1d, 0x66, 0x18,
0x93, 0xfd, 0x0f, 0x07, 0x1b, 0xc4, 0x70, 0xbf, 0xd2, 0x4e, 0xc3, 0xb7, 0xb3, 0x70, 0x85, 0x32,
0x81, 0x5d, 0x76, 0x8f, 0xed, 0x0e, 0xdb, 0x08, 0xfc, 0xb6, 0xdb, 0x49, 0xc2, 0x5c, 0x15, 0xcd,
0x30, 0xd1, 0x8c, 0x7d, 0x71, 0x19, 0xf2, 0x4d, 0xcf, 0xe6, 0xed, 0x20, 0x3a, 0x56, 0xca, 0x4b,
0xa8, 0x3c, 0xa1, 0xd1, 0xde, 0x2a, 0x59, 0x84, 0xa2, 0x52, 0xbc, 0x1f, 0x38, 0x49, 0x3a, 0x75,
0x12, 0xa9, 0xc1, 0xfc, 0x5e, 0xd0, 0x79, 0x86, 0xc9, 0x96, 0x15, 0x96, 0x4c, 0x89, 0x05, 0x25,
0xc5, 0x18, 0xf5, 0xaa, 0x6b, 0x8e, 0x0e, 0xd0, 0xc8, 0xfb, 0x50, 0x38, 0x60, 0x71, 0xec, 0x06,
0xfe, 0xee, 0x66, 0x2d, 0x27, 0xe4, 0xfb, 0x04, 0xd4, 0x7d, 0xc0, 0x83, 0x88, 0xed, 0x6e, 0xd6,
0xe6, 0xa5, 0x6e, 0x35, 0x25, 0xfb, 0x50, 0x39, 0x10, 0x38, 0xa7, 0x89, 0xe8, 0xc6, 0x65, 0x71,
0x2d, 0x2f, 0x52, 0x74, 0x63, 0x34, 0x45, 0x3a, 0x1e, 0xaa, 0x0b, 0xf6, 0x33, 0x3a, 0x24, 0x6c,
0xfd, 0xd6, 0x00, 0x73, 0x5c, 0x00, 0xd5, 0x69, 0xf8, 0x0c, 0x72, 0xf2, 0x7c, 0xcb, 0x20, 0xfe,
0x6f, 0x95, 0x21, 0x7f, 0xc9, 0x7b, 0x90, 0x93, 0xda, 0x55, 0x51, 0xab, 0x59, 0x92, 0xa5, 0x4c,
0x2f, 0x4b, 0xd6, 0xaf, 0x73, 0x50, 0x3a, 0xc0, 0x2d, 0x25, 0x89, 0xac, 0x03, 0xf4, 0xf3, 0xaf,
0x6a, 0x66, 0xf8, 0x54, 0x68, 0x1c, 0xc4, 0x84, 0xfc, 0xb6, 0x3a, 0x9f, 0xea, 0x8a, 0xec, 0xcd,
0xc9, 0xe7, 0x50, 0x4c, 0xc6, 0xcf, 0x43, 0x5e, 0xcb, 0x88, 0xe8, 0x3d, 0x48, 0x39, 0xe0, 0xfa,
0x4e, 0xea, 0x9a, 0xa8, 0x3a, 0xde, 0x1a, 0x85, 0xdc, 0x86, 0x4b, 0xb6, 0xe7, 0x05, 0xa7, 0xaa,
0x66, 0x45, 0xf5, 0x89, 0xec, 0xe7, 0xe9, 0xe8, 0x02, 0xf6, 0x62, 0x8d, 0xf8, 0x24, 0x8a, 0xec,
0x33, 0x0c, 0x44, 0x4e, 0xf0, 0x8f, 0x5b, 0xc2, 0xb6, 0xb8, 0xed, 0xfa, 0xb6, 0x57, 0x03, 0xc1,
0x23, 0x27, 0x78, 0xdc, 0xb6, 0x5e, 0x87, 0x41, 0xc4, 0x59, 0xf4, 0x84, 0xf3, 0xa8, 0x56, 0x14,
0xe1, 0x1d, 0xa0, 0x91, 0x26, 0x94, 0x36, 0xec, 0xd6, 0x11, 0xdb, 0x3d, 0x46, 0x62, 0x02, 0xdd,
0xd2, 0x9a, 0xa5, 0x60, 0x7f, 0x1e, 0xea, 0x98, 0x4d, 0xd7, 0x40, 0x5a, 0x50, 0x49, 0x5c, 0x97,
0x2d, 0xa0, 0x56, 0x16, 0x3a, 0x1f, 0x4d, 0x1b, 0x4a, 0x29, 0x2d, 0x4d, 0x0c, 0xa9, 0xc4, 0x44,
0x6e, 0x61, 0xb5, 0xdb, 0x9c, 0xd5, 0x2a, 0xc2, 0xe7, 0xde, 0x7c, 0x4c, 0x25, 0x2c, 0xbc, 0x45,
0x25, 0x98, 0x8f, 0xa1, 0x3a, 0x9c, 0xdc, 0x69, 0x90, 0x97, 0xf9, 0x63, 0x78, 0x67, 0x8c, 0x47,
0x6f, 0xd5, 0xdd, 0xfe, 0x6c, 0xc0, 0xa5, 0x91, 0x34, 0xe0, 0x0d, 0x23, 0xba, 0x8a, 0x54, 0x29,
0xc6, 0x64, 0x1f, 0xe6, 0x30, 0xcd, 0xb1, 0xc2, 0x1a, 0x6b, 0xd3, 0xe4, 0xb5, 0x2e, 0x24, 0x65,
0xfc, 0xa5, 0x16, 0xf3, 0x01, 0x40, 0x9f, 0x38, 0x15, 0xfe, 0xfc, 0x02, 0xca, 0x2a, 0xc9, 0xaa,
0x83, 0x54, 0x25, 0x6c, 0x51, 0xc2, 0x08, 0x4b, 0xfa, 0x97, 0x5f, 0x66, 0xca, 0xcb, 0xcf, 0xfa,
0x0a, 0x16, 0x28, 0xb3, 0x9d, 0x6d, 0xd7, 0x63, 0xe7, 0xf7, 0x78, 0x2c, 0x7e, 0xd7, 0x63, 0x4d,
0x84, 0x3e, 0x49, 0xf1, 0xab, 0x39, 0x79, 0x08, 0x73, 0xd4, 0xf6, 0x3b, 0x4c, 0x99, 0xfe, 0x30,
0xc5, 0xb4, 0x30, 0x82, 0xbc, 0x54, 0x8a, 0x58, 0x8f, 0xa0, 0xd0, 0xa3, 0x61, 0x33, 0x7b, 0xde,
0x6e, 0xc7, 0x4c, 0x36, 0xc6, 0x0c, 0x55, 0x33, 0xa4, 0xef, 0x31, 0xbf, 0xa3, 0x4c, 0x67, 0xa8,
0x9a, 0x59, 0x4b, 0xf8, 0x5e, 0x48, 0x76, 0xae, 0x42, 0x43, 0x20, 0xbb, 0x89, 0xf8, 0xd0, 0x10,
0xf5, 0x2a, 0xc6, 0x96, 0x83, 0x97, 0xb6, 0xed, 0x6c, 0xba, 0xd1, 0xf9, 0x0e, 0xd6, 0x60, 0x7e,
0xd3, 0x8d, 0x34, 0xff, 0x92, 0x29, 0x59, 0xc2, 0xeb, 0xbc, 0xe5, 0x75, 0x1d, 0xf4, 0x96, 0xb3,
0xc8, 0x57, 0x5d, 0x75, 0x88, 0x6a, 0x7d, 0x22, 0xe3, 0x28, 0xac, 0xa8, 0xcd, 0xdc, 0x86, 0x79,
0xe6, 0xf3, 0x08, 0xcb, 0x48, 0xde, 0xf9, 0xa4, 0x2e, 0x5f, 0xe0, 0x75, 0xf1, 0x02, 0x17, 0xd8,
0x82, 0x26, 0x2c, 0xd6, 0x1a, 0x2c, 0x20, 0x21, 0x3d, 0x11, 0x04, 0xb2, 0xda, 0x26, 0xc5, 0xd8,
0x7a, 0x08, 0xd5, 0xbe, 0xa0, 0x32, 0xbd, 0x04, 0x59, 0x04, 0xbf, 0xaa, 0xaf, 0x8f, 0xb3, 0x2b,
0xd6, 0xad, 0xeb, 0xb0, 0x90, 0x14, 0xff, 0xb9, 0x46, 0x2d, 0x02, 0xd5, 0x3e, 0x93, 0xc2, 0x3d,
0x65, 0x28, 0x36, 0x5d, 0x3f, 0x81, 0x05, 0xd6, 0x1b, 0x03, 0x4a, 0xcd, 0xc0, 0xef, 0xdf, 0x72,
0x4d, 0x58, 0x48, 0x4a, 0xf7, 0x49, 0x73, 0x77, 0xc3, 0x0e, 0x93, 0x18, 0x2c, 0x8e, 0x9e, 0x0f,
0xf5, 0x0d, 0xa3, 0x2e, 0x19, 0xd7, 0xb3, 0x78, 0x21, 0xd2, 0x61, 0x71, 0xf2, 0x29, 0xcc, 0xef,
0xed, 0xad, 0x0b, 0x4d, 0xb3, 0x53, 0x69, 0x4a, 0xc4, 0xc8, 0x63, 0x98, 0x7f, 0x29, 0x3e, 0xad,
0xc4, 0xea, 0x8a, 0x1a, 0x73, 0x56, 0x65, 0x84, 0x24, 0x1b, 0x65, 0xad, 0x20, 0x72, 0x68, 0x22,
0x64, 0xfd, 0xdb, 0x80, 0xe2, 0x4b, 0xbb, 0x0f, 0x39, 0xfb, 0x18, 0xf7, 0x2d, 0x6e, 0x72, 0x85,
0x71, 0x2f, 0xc3, 0x9c, 0xc7, 0x4e, 0x98, 0xa7, 0xce, 0xb8, 0x9c, 0x20, 0x35, 0x3e, 0x0a, 0x22,
0x59, 0xd6, 0x25, 0x2a, 0x27, 0x58, 0x10, 0x0e, 0xe3, 0xb6, 0xeb, 0xd5, 0xb2, 0x8b, 0x19, 0xbc,
0xf5, 0xe5, 0x0c, 0x33, 0xd7, 0x8d, 0x3c, 0xf5, 0xf0, 0xc0, 0x21, 0xb1, 0x20, 0xeb, 0xfa, 0xed,
0x40, 0xdc, 0x7f, 0xaa, 0x2d, 0xca, 0x16, 0xbd, 0xeb, 0xb7, 0x03, 0x2a, 0xd6, 0xc8, 0x35, 0xc8,
0x45, 0x58, 0x7f, 0x71, 0x6d, 0x5e, 0x04, 0xa5, 0x80, 0x5c, 0xb2, 0x4a, 0xd5, 0x82, 0x55, 0x81,
0x92, 0xf4, 0x5b, 0x25, 0xff, 0x4f, 0xb3, 0xf0, 0xce, 0x33, 0x76, 0xba, 0x91, 0xf8, 0x95, 0x04,
0x64, 0x11, 0x8a, 0x3d, 0xda, 0xee, 0xa6, 0x3a, 0x42, 0x3a, 0x09, 0x8d, 0xed, 0x07, 0x5d, 0x9f,
0x27, 0x39, 0x14, 0xc6, 0x04, 0x85, 0xaa, 0x05, 0x72, 0x03, 0xe6, 0x9f, 0x31, 0x7e, 0x1a, 0x44,
0xaf, 0x84, 0xd7, 0x95, 0xd5, 0x22, 0xf2, 0x3c, 0x63, 0x1c, 0x11, 0x22, 0x4d, 0xd6, 0x10, 0x76,
0x86, 0x09, 0xec, 0xcc, 0x8e, 0x83, 0x9d, 0xc9, 0x2a, 0x59, 0x83, 0x62, 0x2b, 0xf0, 0x63, 0x1e,
0xd9, 0x2e, 0x1a, 0x9e, 0x13, 0xcc, 0xef, 0x22, 0xb3, 0x4c, 0xec, 0x46, 0x7f, 0x91, 0xea, 0x9c,
0x64, 0x05, 0x80, 0xbd, 0xe6, 0x91, 0xbd, 0x13, 0xc4, 0xbd, 0x27, 0x1a, 0xa0, 0x1c, 0x12, 0x76,
0x9b, 0x54, 0x5b, 0xc5, 0x0e, 0x79, 0x14, 0xc4, 0x5c, 0xbc, 0x53, 0x24, 0xbc, 0xec, 0xcd, 0xad,
0xf7, 0xe0, 0xf2, 0x60, 0xb4, 0x54, 0x18, 0x1f, 0xc1, 0xff, 0x51, 0xe6, 0x31, 0x3b, 0x66, 0xd3,
0x47, 0xd2, 0x32, 0xa1, 0x36, 0x2a, 0xac, 0x14, 0xff, 0x27, 0x03, 0xc5, 0xad, 0xd7, 0xac, 0xb5,
0xcf, 0xe2, 0xd8, 0xee, 0x08, 0x60, 0xdc, 0x8c, 0x82, 0x16, 0x8b, 0xe3, 0x9e, 0xae, 0x3e, 0x81,
0xfc, 0x10, 0xb2, 0xbb, 0xbe, 0xcb, 0xd5, 0xdd, 0xb9, 0x94, 0xfa, 0x2e, 0x71, 0xb9, 0xd2, 0xb9,
0x33, 0x43, 0x85, 0x14, 0x79, 0x08, 0x59, 0xec, 0x3c, 0x93, 0x74, 0x7f, 0x47, 0x93, 0x45, 0x19,
0xb2, 0x2e, 0xbe, 0x1f, 0xba, 0x5f, 0x32, 0x95, 0xc1, 0xe5, 0xf4, 0x6b, 0xcb, 0xfd, 0x92, 0xf5,
0x35, 0x28, 0x49, 0xb2, 0x85, 0xb0, 0xde, 0x8e, 0x38, 0x73, 0x54, 0x66, 0x6f, 0xa6, 0x81, 0x25,
0xc9, 0xd9, 0xd7, 0x92, 0xc8, 0x62, 0x10, 0xb6, 0x5e, 0xbb, 0x5c, 0x55, 0x4a, 0x5a, 0x10, 0x90,
0x4d, 0x73, 0x04, 0xa7, 0x28, 0xbd, 0x19, 0xf8, 0x32, 0xf3, 0xe9, 0xd2, 0xc8, 0xa6, 0x49, 0xe3,
0x14, 0xc3, 0x70, 0xe0, 0x76, 0x10, 0x83, 0xe6, 0x2f, 0x0c, 0x83, 0x64, 0xd4, 0xc2, 0x20, 0x09,
0xeb, 0xf3, 0x30, 0x27, 0x20, 0x92, 0xf5, 0x77, 0x03, 0x8a, 0x5a, 0x9e, 0x26, 0xa8, 0xc9, 0xf7,
0x21, 0xbb, 0xcf, 0xc4, 0x37, 0x15, 0x34, 0x9e, 0x17, 0x15, 0xc9, 0xb8, 0x4d, 0x05, 0x15, 0x9b,
0xca, 0xb6, 0x23, 0x1b, 0x66, 0x99, 0xe2, 0x10, 0x29, 0x2f, 0xf8, 0x99, 0x48, 0x59, 0x9e, 0xe2,
0x90, 0xdc, 0x86, 0xfc, 0x01, 0x6b, 0x75, 0x23, 0x97, 0x9f, 0x89, 0x24, 0x54, 0x56, 0xab, 0xa2,
0xd5, 0x28, 0x9a, 0x28, 0xdc, 0x1e, 0x07, 0xb9, 0x05, 0x85, 0x98, 0xb5, 0x22, 0xc6, 0x99, 0x7f,
0xa2, 0xaa, 0xaa, 0xac, 0xd8, 0x23, 0xc6, 0xb7, 0xfc, 0x13, 0xda, 0x5f, 0xb7, 0x9e, 0xe2, 0x49,
0xee, 0x7b, 0x43, 0x20, 0xbb, 0x81, 0x6f, 0x47, 0x74, 0xa3, 0x4c, 0xc5, 0x18, 0x9f, 0xef, 0x5b,
0x17, 0x3d, 0xdf, 0xb7, 0x92, 0xe7, 0xfb, 0xe0, 0x09, 0xc0, 0x6b, 0x4c, 0xcb, 0x88, 0xf5, 0x04,
0x0a, 0xbd, 0x53, 0x4a, 0x2a, 0x30, 0xbb, 0xed, 0x28, 0x4b, 0xb3, 0xdb, 0x0e, 0xfa, 0xbd, 0xf5,
0x7c, 0x5b, 0x58, 0xc9, 0x53, 0x1c, 0xf6, 0xd0, 0x46, 0x46, 0x43, 0x1b, 0x6b, 0x50, 0x1e, 0x38,
0xaa, 0xc8, 0x44, 0x83, 0xd3, 0x38, 0xd9, 0x32, 0x8e, 0xa5, 0x1b, 0x5e, 0x2c, 0x74, 0x09, 0x37,
0xbc, 0xd8, 0xba, 0x0e, 0xe5, 0x81, 0xe4, 0x22, 0x93, 0x78, 0x09, 0x2b, 0x50, 0x8a, 0xe3, 0x15,
0x06, 0x0b, 0x43, 0x1f, 0xc7, 0xc8, 0x0d, 0xc8, 0xc9, 0x8f, 0x30, 0xd5, 0x19, 0xf3, 0xca, 0xd7,
0xdf, 0x2c, 0xbe, 0x3b, 0xc4, 0x20, 0x17, 0x91, 0x6d, 0xbd, 0xeb, 0x3b, 0x1e, 0xab, 0x1a, 0x63,
0xd9, 0xe4, 0xa2, 0x99, 0xfd, 0xcd, 0x1f, 0xae, 0xce, 0xac, 0xd8, 0x70, 0x69, 0xe4, 0xc3, 0x0e,
0xb9, 0x0e, 0xd9, 0x03, 0xe6, 0xb5, 0x13, 0x33, 0x23, 0x0c, 0xb8, 0x48, 0xae, 0x41, 0x86, 0xda,
0xa7, 0x55, 0xc3, 0xac, 0x7d, 0xfd, 0xcd, 0xe2, 0xe5, 0xd1, 0xaf, 0x43, 0xf6, 0xa9, 0x34, 0xb1,
0xfa, 0x57, 0x80, 0xc2, 0xde, 0xde, 0xfa, 0x7a, 0xe4, 0x3a, 0x1d, 0x46, 0x7e, 0x65, 0x00, 0x19,
0x7d, 0x33, 0x93, 0x7b, 0xe9, 0x0d, 0x61, 0xfc, 0x37, 0x0a, 0xf3, 0xfe, 0x94, 0x52, 0x0a, 0xb2,
0x7c, 0x0e, 0x73, 0x02, 0x67, 0x93, 0x8f, 0x26, 0x7c, 0x6e, 0x99, 0xcb, 0x17, 0x33, 0x2a, 0xdd,
0x2d, 0xc8, 0x27, 0x58, 0x95, 0xac, 0xa4, 0x6e, 0x6f, 0x00, 0x8a, 0x9b, 0xb7, 0x26, 0xe2, 0x55,
0x46, 0x7e, 0x0e, 0xf3, 0x0a, 0x82, 0x92, 0x9b, 0x17, 0xc8, 0xf5, 0xc1, 0xb0, 0xb9, 0x32, 0x09,
0x6b, 0xdf, 0x8d, 0x04, 0x6a, 0xa6, 0xba, 0x31, 0x04, 0x64, 0x53, 0xdd, 0x18, 0xc1, 0xae, 0xad,
0xfe, 0x03, 0x35, 0xd5, 0xc8, 0x10, 0x70, 0x4d, 0x35, 0x32, 0x8c, 0x5f, 0xc9, 0x4b, 0xc8, 0x22,
0x7e, 0x25, 0x69, 0xbd, 0x5a, 0x03, 0xb8, 0x66, 0xda, 0x99, 0x18, 0x00, 0xbe, 0x3f, 0xc3, 0x3b,
0x4d, 0x7c, 0x8b, 0x48, 0xbf, 0xcd, 0xb4, 0x6f, 0x97, 0xe6, 0xcd, 0x09, 0x38, 0xfb, 0xea, 0xd5,
0x3b, 0x7e, 0x79, 0x82, 0x0f, 0x88, 0x17, 0xab, 0x1f, 0xfa, 0x54, 0x19, 0x40, 0x49, 0x87, 0x2a,
0xa4, 0x9e, 0x22, 0x3a, 0x06, 0x01, 0x9a, 0x8d, 0x89, 0xf9, 0x95, 0xc1, 0xaf, 0xf0, 0x11, 0x37,
0x08, 0x63, 0xc8, 0x6a, 0x6a, 0x38, 0xc6, 0x02, 0x26, 0xf3, 0xee, 0x54, 0x32, 0xca, 0xb8, 0x2d,
0x61, 0x92, 0x82, 0x42, 0x24, 0xfd, 0xd6, 0xef, 0xc1, 0x29, 0x73, 0x42, 0xbe, 0x65, 0xe3, 0x8e,
0x81, 0xe7, 0x0c, 0xa1, 0x73, 0xaa, 0x6e, 0xed, 0x4d, 0x91, 0x7a, 0xce, 0x74, 0x0c, 0xbe, 0x5e,
0xfa, 0xee, 0xcd, 0x55, 0xe3, 0x1f, 0x6f, 0xae, 0x1a, 0xff, 0x7a, 0x73, 0xd5, 0x38, 0xcc, 0x89,
0x7f, 0x64, 0xef, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0x20, 0x47, 0x7d, 0x27, 0x1a, 0x1f, 0x00,
0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -4022,6 +4049,20 @@ func (m *ResolveImageConfigRequest) MarshalToSizedBuffer(dAtA []byte) (int, erro
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.SourcePolicies) > 0 {
for iNdEx := len(m.SourcePolicies) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.SourcePolicies[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGateway(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x42
}
}
if len(m.StoreID) > 0 {
i -= len(m.StoreID)
copy(dAtA[i:], m.StoreID)
@ -4101,6 +4142,13 @@ func (m *ResolveImageConfigResponse) MarshalToSizedBuffer(dAtA []byte) (int, err
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.Ref) > 0 {
i -= len(m.Ref)
copy(dAtA[i:], m.Ref)
i = encodeVarintGateway(dAtA, i, uint64(len(m.Ref)))
i--
dAtA[i] = 0x1a
}
if len(m.Config) > 0 {
i -= len(m.Config)
copy(dAtA[i:], m.Config)
@ -5348,6 +5396,20 @@ func (m *InitMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if len(m.Secretenv) > 0 {
for iNdEx := len(m.Secretenv) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.Secretenv[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintGateway(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0x32
}
}
if m.Security != 0 {
i = encodeVarintGateway(dAtA, i, uint64(m.Security))
i--
@ -5973,6 +6035,12 @@ func (m *ResolveImageConfigRequest) Size() (n int) {
if l > 0 {
n += 1 + l + sovGateway(uint64(l))
}
if len(m.SourcePolicies) > 0 {
for _, e := range m.SourcePolicies {
l = e.Size()
n += 1 + l + sovGateway(uint64(l))
}
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@ -5993,6 +6061,10 @@ func (m *ResolveImageConfigResponse) Size() (n int) {
if l > 0 {
n += 1 + l + sovGateway(uint64(l))
}
l = len(m.Ref)
if l > 0 {
n += 1 + l + sovGateway(uint64(l))
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@ -6586,6 +6658,12 @@ func (m *InitMessage) Size() (n int) {
if m.Security != 0 {
n += 1 + sovGateway(uint64(m.Security))
}
if len(m.Secretenv) > 0 {
for _, e := range m.Secretenv {
l = e.Size()
n += 1 + l + sovGateway(uint64(l))
}
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@ -8819,6 +8897,40 @@ func (m *ResolveImageConfigRequest) Unmarshal(dAtA []byte) error {
}
m.StoreID = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field SourcePolicies", 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 < 0 {
return ErrInvalidLengthGateway
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.SourcePolicies = append(m.SourcePolicies, &pb1.Policy{})
if err := m.SourcePolicies[len(m.SourcePolicies)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGateway(dAtA[iNdEx:])
@ -8936,6 +9048,38 @@ func (m *ResolveImageConfigResponse) Unmarshal(dAtA []byte) error {
m.Config = []byte{}
}
iNdEx = postIndex
case 3:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Ref", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGateway
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGateway
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGateway
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Ref = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGateway(dAtA[iNdEx:])
@ -12261,6 +12405,40 @@ func (m *InitMessage) Unmarshal(dAtA []byte) error {
break
}
}
case 6:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Secretenv", 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 < 0 {
return ErrInvalidLengthGateway
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Secretenv = append(m.Secretenv, &pb.SecretEnv{})
if err := m.Secretenv[len(m.Secretenv)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGateway(dAtA[iNdEx:])

@ -11,6 +11,7 @@ import "github.com/tonistiigi/fsutil/types/stat.proto";
import "github.com/moby/buildkit/sourcepolicy/pb/policy.proto";
option (gogoproto.sizer_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.unmarshaler_all) = true;
@ -38,7 +39,7 @@ service LLBBridge {
rpc ExecProcess(stream ExecMessage) returns (stream ExecMessage);
// apicaps:CapGatewayWarnings
rpc Warn(WarnRequest) returns (WarnResponse);
rpc Warn(WarnRequest) returns (WarnResponse);
}
message Result {
@ -124,11 +125,13 @@ message ResolveImageConfigRequest {
int32 ResolverType = 5;
string SessionID = 6;
string StoreID = 7;
repeated moby.buildkit.v1.sourcepolicy.Policy SourcePolicies = 8;
}
message ResolveImageConfigResponse {
string Digest = 1 [(gogoproto.customtype) = "github.com/opencontainers/go-digest.Digest", (gogoproto.nullable) = false];
bytes Config = 2;
string Ref = 3;
}
message SolveRequest {
@ -138,7 +141,7 @@ message SolveRequest {
// 4 was removed in BuildKit v0.11.0.
bool allowResultReturn = 5;
bool allowResultArrayRef = 6;
// apicaps.CapSolveInlineReturn deprecated
bool Final = 10;
bytes ExporterAttr = 11;
@ -165,7 +168,7 @@ message SolveResponse {
string ref = 1; // can be used by readfile request
// deprecated
/* bytes ExporterAttr = 2;*/
// these fields are returned when allowMapReturn was set
Result result = 3;
}
@ -256,7 +259,7 @@ message ExecMessage {
// InitMessage sent from client to server will start a new process in a
// container
InitMessage Init = 2;
// FdMessage used from client to server for input (stdin) and
// FdMessage used from client to server for input (stdin) and
// from server to client for output (stdout, stderr)
FdMessage File = 3;
// ResizeMessage used from client to server for terminal resize events
@ -281,6 +284,7 @@ message InitMessage{
repeated uint32 Fds = 3;
bool Tty = 4;
pb.SecurityMode Security = 5;
repeated pb.SecretEnv secretenv = 6;
}
message ExitMessage {

@ -4,8 +4,11 @@ import (
"context"
"fmt"
io "io"
"net/url"
"os"
"strconv"
"strings"
"unicode"
"github.com/moby/buildkit/session"
"github.com/pkg/errors"
@ -82,6 +85,7 @@ func (sp *fsSyncProvider) handle(method string, stream grpc.ServerStream) (retEr
}
opts, _ := metadata.FromIncomingContext(stream.Context()) // if no metadata continue with empty object
opts = decodeOpts(opts)
dirName := ""
name, ok := opts[keyDirName]
@ -209,6 +213,8 @@ func FSSync(ctx context.Context, c session.Caller, opt FSSendRequestOpt) error {
var stream grpc.ClientStream
opts = encodeOpts(opts)
ctx = metadata.NewOutgoingContext(ctx, opts)
switch pr.name {
@ -337,3 +343,60 @@ func (e InvalidSessionError) Error() string {
func (e InvalidSessionError) Unwrap() error {
return e.err
}
func encodeOpts(opts map[string][]string) map[string][]string {
md := make(map[string][]string, len(opts))
for k, v := range opts {
out, encoded := encodeStringForHeader(v)
md[k] = out
if encoded {
md[k+"-encoded"] = []string{"1"}
}
}
return md
}
func decodeOpts(opts map[string][]string) map[string][]string {
md := make(map[string][]string, len(opts))
for k, v := range opts {
out := make([]string, len(v))
var isDecoded bool
if v, ok := opts[k+"-encoded"]; ok && len(v) > 0 {
if b, _ := strconv.ParseBool(v[0]); b {
isDecoded = true
}
}
if isDecoded {
for i, s := range v {
out[i], _ = url.QueryUnescape(s)
}
} else {
copy(out, v)
}
md[k] = out
}
return md
}
// encodeStringForHeader encodes a string value so it can be used in grpc header. This encoding
// is backwards compatible and avoids encoding ASCII characters.
func encodeStringForHeader(inputs []string) ([]string, bool) {
var encode bool
for _, input := range inputs {
for _, runeVal := range input {
// Only encode non-ASCII characters, and characters that have special
// meaning during decoding.
if runeVal > unicode.MaxASCII {
encode = true
break
}
}
}
if !encode {
return inputs, false
}
for i, input := range inputs {
inputs[i] = url.QueryEscape(input)
}
return inputs, true
}

@ -0,0 +1,10 @@
package srctypes
const (
DockerImageScheme = "docker-image"
GitScheme = "git"
LocalScheme = "local"
HTTPScheme = "http"
HTTPSScheme = "https"
OCIScheme = "oci-layout"
)

@ -0,0 +1,161 @@
package sourcepolicy
import (
"context"
"github.com/moby/buildkit/solver/pb"
spb "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/bklog"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var (
// ErrSourceDenied is returned by the policy engine when a source is denied by the policy.
ErrSourceDenied = errors.New("source denied by policy")
// ErrTooManyOps is returned by the policy engine when there are too many converts for a single source op.
ErrTooManyOps = errors.New("too many operations")
)
// Engine is the source policy engine.
// It is responsible for evaluating a source policy against a source operation.
// Create one with `NewEngine`
//
// Rule matching is delegated to the `Matcher` interface.
// Mutations are delegated to the `Mutater` interface.
type Engine struct {
pol []*spb.Policy
sources map[string]*selectorCache
}
// NewEngine creates a new source policy engine.
func NewEngine(pol []*spb.Policy) *Engine {
return &Engine{
pol: pol,
}
}
// TODO: The key here can't be used to cache attr constraint regexes.
func (e *Engine) selectorCache(src *spb.Selector) *selectorCache {
if e.sources == nil {
e.sources = map[string]*selectorCache{}
}
key := src.MatchType.String() + " " + src.Identifier
if s, ok := e.sources[key]; ok {
return s
}
s := &selectorCache{Selector: src}
e.sources[key] = s
return s
}
// Evaluate evaluates a source operation against the policy.
//
// Policies are re-evaluated for each convert rule.
// Evaluate will error if the there are too many converts for a single source op to prevent infinite loops.
// This function may error out even if the op was mutated, in which case `true` will be returned along with the error.
//
// An error is returned when the source is denied by the policy.
func (e *Engine) Evaluate(ctx context.Context, op *pb.Op) (bool, error) {
if len(e.pol) == 0 {
return false, nil
}
var mutated bool
const maxIterr = 20
for i := 0; ; i++ {
if i > maxIterr {
return mutated, errors.Wrapf(ErrTooManyOps, "too many mutations on a single source")
}
srcOp := op.GetSource()
if srcOp == nil {
return false, nil
}
if i == 0 {
ctx = bklog.WithLogger(ctx, bklog.G(ctx).WithField("orig", *srcOp).WithField("updated", op.GetSource()))
}
mut, err := e.evaluatePolicies(ctx, srcOp)
if mut {
mutated = true
}
if err != nil {
return mutated, err
}
if !mut {
break
}
}
return mutated, nil
}
func (e *Engine) evaluatePolicies(ctx context.Context, srcOp *pb.SourceOp) (bool, error) {
for _, pol := range e.pol {
mut, err := e.evaluatePolicy(ctx, pol, srcOp)
if mut || err != nil {
return mut, err
}
}
return false, nil
}
// evaluatePolicy evaluates a single policy against a source operation.
// If the source is mutated the policy is short-circuited and `true` is returned.
// If the source is denied, an error will be returned.
//
// For Allow/Deny rules, the last matching rule wins.
// E.g. `ALLOW foo; DENY foo` will deny `foo`, `DENY foo; ALLOW foo` will allow `foo`.
func (e *Engine) evaluatePolicy(ctx context.Context, pol *spb.Policy, srcOp *pb.SourceOp) (retMut bool, retErr error) {
ident := srcOp.GetIdentifier()
ctx = bklog.WithLogger(ctx, bklog.G(ctx).WithField("ref", ident))
defer func() {
if retMut || retErr != nil {
bklog.G(ctx).WithFields(
logrus.Fields{
"mutated": retMut,
"updated": srcOp.GetIdentifier(),
logrus.ErrorKey: retErr,
}).Debug("Evaluated source policy")
}
}()
var deny bool
for _, rule := range pol.Rules {
selector := e.selectorCache(rule.Selector)
matched, err := match(ctx, selector, ident, srcOp.Attrs)
if err != nil {
return false, errors.Wrap(err, "error matching source policy")
}
if !matched {
continue
}
switch rule.Action {
case spb.PolicyAction_ALLOW:
deny = false
case spb.PolicyAction_DENY:
deny = true
case spb.PolicyAction_CONVERT:
mut, err := mutate(ctx, srcOp, rule, selector, ident)
if err != nil || mut {
return mut, errors.Wrap(err, "error mutating source policy")
}
default:
return false, errors.Errorf("source policy: rule %s %s: unknown type %q", rule.Action, rule.Selector.Identifier, ident)
}
}
if deny {
return false, errors.Wrapf(ErrSourceDenied, "source %q denied by policy", ident)
}
return false, nil
}

@ -0,0 +1,92 @@
package sourcepolicy
import (
"regexp"
spb "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/wildcard"
"github.com/pkg/errors"
)
// Source wraps a a protobuf source in order to store cached state such as the compiled regexes.
type selectorCache struct {
*spb.Selector
re *regexp.Regexp
w *wildcardCache
}
// Format formats the provided ref according to the match/type of the source.
//
// For example, if the source is a wildcard, the ref will be formatted with the wildcard in the source replacing the parameters in the destination.
//
// matcher: wildcard source: "docker.io/library/golang:*" match: "docker.io/library/golang:1.19" format: "docker.io/library/golang:${1}-alpine" result: "docker.io/library/golang:1.19-alpine"
func (s *selectorCache) Format(match, format string) (string, error) {
switch s.MatchType {
case spb.MatchType_EXACT:
return s.Identifier, nil
case spb.MatchType_REGEX:
re, err := s.regex()
if err != nil {
return "", err
}
return re.ReplaceAllString(match, format), nil
case spb.MatchType_WILDCARD:
w, err := s.wildcard()
if err != nil {
return "", err
}
m := w.Match(match)
if m == nil {
return match, nil
}
return m.Format(format)
}
return "", errors.Errorf("unknown match type: %s", s.MatchType)
}
// wildcardCache wraps a wildcard.Wildcard to cache returned matches by ref.
// This way a match only needs to be computed once per ref.
type wildcardCache struct {
w *wildcard.Wildcard
m map[string]*wildcard.Match
}
func (w *wildcardCache) Match(ref string) *wildcard.Match {
if w.m == nil {
w.m = make(map[string]*wildcard.Match)
}
if m, ok := w.m[ref]; ok {
return m
}
m := w.w.Match(ref)
w.m[ref] = m
return m
}
func (s *selectorCache) wildcard() (*wildcardCache, error) {
if s.w != nil {
return s.w, nil
}
w, err := wildcard.New(s.Identifier)
if err != nil {
return nil, err
}
s.w = &wildcardCache{w: w}
return s.w, nil
}
func (s *selectorCache) regex() (*regexp.Regexp, error) {
if s.re != nil {
return s.re, nil
}
re, err := regexp.Compile(s.Identifier)
if err != nil {
return nil, err
}
s.re = re
return re, nil
}

@ -0,0 +1,58 @@
package sourcepolicy
import (
"context"
"regexp"
spb "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/pkg/errors"
)
func match(ctx context.Context, src *selectorCache, ref string, attrs map[string]string) (bool, error) {
for _, c := range src.Constraints {
switch c.Condition {
case spb.AttrMatch_EQUAL:
if attrs[c.Key] != c.Value {
return false, nil
}
case spb.AttrMatch_NOTEQUAL:
if attrs[c.Key] == c.Value {
return false, nil
}
case spb.AttrMatch_MATCHES:
// TODO: Cache the compiled regex
matches, err := regexp.MatchString(c.Value, attrs[c.Key])
if err != nil {
return false, errors.Errorf("invalid regex %q: %v", c.Value, err)
}
if !matches {
return false, nil
}
default:
return false, errors.Errorf("unknown attr condition: %s", c.Condition)
}
}
if src.Identifier == ref {
return true, nil
}
switch src.MatchType {
case spb.MatchType_EXACT:
return false, nil
case spb.MatchType_REGEX:
re, err := src.regex()
if err != nil {
return false, err
}
return re.MatchString(ref), nil
case spb.MatchType_WILDCARD:
w, err := src.wildcard()
if err != nil {
return false, err
}
return w.Match(ref) != nil, nil
default:
return false, errors.Errorf("unknown match type: %s", src.MatchType)
}
}

@ -0,0 +1,50 @@
package sourcepolicy
import (
"context"
"github.com/moby/buildkit/solver/pb"
spb "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/bklog"
"github.com/pkg/errors"
)
// mutate is a MutateFn which converts the source operation to the identifier and attributes provided by the policy.
// If there is no change, then the return value should be false and is not considered an error.
func mutate(ctx context.Context, op *pb.SourceOp, rule *spb.Rule, selector *selectorCache, ref string) (bool, error) {
if rule.Updates == nil {
return false, errors.Errorf("missing destination for convert rule")
}
dest := rule.Updates.Identifier
if dest == "" {
dest = rule.Selector.Identifier
}
dest, err := selector.Format(ref, dest)
if err != nil {
return false, errors.Wrap(err, "error formatting destination")
}
bklog.G(ctx).Debugf("sourcepolicy: converting %s to %s, pattern: %s", ref, dest, rule.Updates.Identifier)
var mutated bool
if op.Identifier != dest && dest != "" {
mutated = true
op.Identifier = dest
}
if rule.Updates.Attrs != nil {
if op.Attrs == nil {
op.Attrs = make(map[string]string, len(rule.Updates.Attrs))
}
for k, v := range rule.Updates.Attrs {
if op.Attrs[k] != v {
bklog.G(ctx).Debugf("setting attr %s=%s", k, v)
op.Attrs[k] = v
mutated = true
}
}
}
return mutated, nil
}

@ -20,7 +20,6 @@ func ProviderFromRef(ref string) (ocispecs.Descriptor, content.Provider, error)
headers := http.Header{}
headers.Set("User-Agent", version.UserAgent())
remote := docker.NewResolver(docker.ResolverOptions{
Client: http.DefaultClient,
Headers: headers,
})
@ -40,7 +39,6 @@ func IngesterFromRef(ref string) (content.Ingester, error) {
headers := http.Header{}
headers.Set("User-Agent", version.UserAgent())
remote := docker.NewResolver(docker.ResolverOptions{
Client: http.DefaultClient,
Headers: headers,
})

@ -25,13 +25,13 @@ type contextKeyT string
var contextKey = contextKeyT("buildkit/util/flightcontrol.progress")
// Group is a flightcontrol synchronization group
type Group struct {
mu sync.Mutex // protects m
m map[string]*call // lazily initialized
type Group[T any] struct {
mu sync.Mutex // protects m
m map[string]*call[T] // lazily initialized
}
// Do executes a context function syncronized by the key
func (g *Group) Do(ctx context.Context, key string, fn func(ctx context.Context) (interface{}, error)) (v interface{}, err error) {
func (g *Group[T]) Do(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) (v T, err error) {
var backoff time.Duration
for {
v, err = g.do(ctx, key, fn)
@ -53,10 +53,10 @@ func (g *Group) Do(ctx context.Context, key string, fn func(ctx context.Context)
}
}
func (g *Group) do(ctx context.Context, key string, fn func(ctx context.Context) (interface{}, error)) (interface{}, error) {
func (g *Group[T]) do(ctx context.Context, key string, fn func(ctx context.Context) (T, error)) (T, error) {
g.mu.Lock()
if g.m == nil {
g.m = make(map[string]*call)
g.m = make(map[string]*call[T])
}
if c, ok := g.m[key]; ok { // register 2nd waiter
@ -78,16 +78,16 @@ func (g *Group) do(ctx context.Context, key string, fn func(ctx context.Context)
return c.wait(ctx)
}
type call struct {
type call[T any] struct {
mu sync.Mutex
result interface{}
result T
err error
ready chan struct{}
cleaned chan struct{}
ctx *sharedContext
ctx *sharedContext[T]
ctxs []context.Context
fn func(ctx context.Context) (interface{}, error)
fn func(ctx context.Context) (T, error)
once sync.Once
closeProgressWriter func()
@ -95,8 +95,8 @@ type call struct {
progressCtx context.Context
}
func newCall(fn func(ctx context.Context) (interface{}, error)) *call {
c := &call{
func newCall[T any](fn func(ctx context.Context) (T, error)) *call[T] {
c := &call[T]{
fn: fn,
ready: make(chan struct{}),
cleaned: make(chan struct{}),
@ -114,7 +114,7 @@ func newCall(fn func(ctx context.Context) (interface{}, error)) *call {
return c
}
func (c *call) run() {
func (c *call[T]) run() {
defer c.closeProgressWriter()
ctx, cancel := context.WithCancel(c.ctx)
defer cancel()
@ -126,7 +126,8 @@ func (c *call) run() {
close(c.ready)
}
func (c *call) wait(ctx context.Context) (v interface{}, err error) {
func (c *call[T]) wait(ctx context.Context) (v T, err error) {
var empty T
c.mu.Lock()
// detect case where caller has just returned, let it clean up before
select {
@ -134,7 +135,7 @@ func (c *call) wait(ctx context.Context) (v interface{}, err error) {
c.mu.Unlock()
if c.err != nil { // on error retry
<-c.cleaned
return nil, errRetry
return empty, errRetry
}
pw, ok, _ := progress.NewFromContext(ctx)
if ok {
@ -145,7 +146,7 @@ func (c *call) wait(ctx context.Context) (v interface{}, err error) {
case <-c.ctx.done: // could return if no error
c.mu.Unlock()
<-c.cleaned
return nil, errRetry
return empty, errRetry
default:
}
@ -174,13 +175,13 @@ func (c *call) wait(ctx context.Context) (v interface{}, err error) {
if ok {
c.progressState.close(pw)
}
return nil, ctx.Err()
return empty, ctx.Err()
case <-c.ready:
return c.result, c.err // shared not implemented yet
}
}
func (c *call) Deadline() (deadline time.Time, ok bool) {
func (c *call[T]) Deadline() (deadline time.Time, ok bool) {
c.mu.Lock()
defer c.mu.Unlock()
for _, ctx := range c.ctxs {
@ -196,11 +197,11 @@ func (c *call) Deadline() (deadline time.Time, ok bool) {
return time.Time{}, false
}
func (c *call) Done() <-chan struct{} {
func (c *call[T]) Done() <-chan struct{} {
return c.ctx.done
}
func (c *call) Err() error {
func (c *call[T]) Err() error {
select {
case <-c.ctx.Done():
return c.ctx.err
@ -209,7 +210,7 @@ func (c *call) Err() error {
}
}
func (c *call) Value(key interface{}) interface{} {
func (c *call[T]) Value(key interface{}) interface{} {
if key == contextKey {
return c.progressState
}
@ -239,17 +240,17 @@ func (c *call) Value(key interface{}) interface{} {
return nil
}
type sharedContext struct {
*call
type sharedContext[T any] struct {
*call[T]
done chan struct{}
err error
}
func newContext(c *call) *sharedContext {
return &sharedContext{call: c, done: make(chan struct{})}
func newContext[T any](c *call[T]) *sharedContext[T] {
return &sharedContext[T]{call: c, done: make(chan struct{})}
}
func (sc *sharedContext) checkDone() bool {
func (sc *sharedContext[T]) checkDone() bool {
sc.mu.Lock()
select {
case <-sc.done:

@ -0,0 +1,285 @@
package imageutil
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"
"time"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/platforms"
"github.com/containerd/containerd/reference"
"github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker"
intoto "github.com/in-toto/in-toto-golang/in_toto"
"github.com/moby/buildkit/solver/pb"
srctypes "github.com/moby/buildkit/source/types"
"github.com/moby/buildkit/sourcepolicy"
spb "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/contentutil"
"github.com/moby/buildkit/util/leaseutil"
"github.com/moby/buildkit/util/resolver/limited"
"github.com/moby/buildkit/util/resolver/retryhandler"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
type ContentCache interface {
content.Ingester
content.Provider
content.Manager
}
var leasesMu sync.Mutex
var leasesF []func(context.Context) error
func CancelCacheLeases() {
leasesMu.Lock()
for _, f := range leasesF {
f(context.TODO())
}
leasesF = nil
leasesMu.Unlock()
}
func AddLease(f func(context.Context) error) {
leasesMu.Lock()
leasesF = append(leasesF, f)
leasesMu.Unlock()
}
// ResolveToNonImageError is returned by the resolver when the ref is mutated by policy to a non-image ref
type ResolveToNonImageError struct {
Ref string
Updated string
}
func (e ResolveToNonImageError) Error() string {
return fmt.Sprintf("ref mutated by policy to non-image: %s://%s -> %s", srctypes.DockerImageScheme, e.Ref, e.Updated)
}
func Config(ctx context.Context, str string, resolver remotes.Resolver, cache ContentCache, leaseManager leases.Manager, p *ocispecs.Platform, spls []*spb.Policy) (string, digest.Digest, []byte, error) {
// TODO: fix buildkit to take interface instead of struct
var platform platforms.MatchComparer
if p != nil {
platform = platforms.Only(*p)
} else {
platform = platforms.Default()
}
ref, err := reference.Parse(str)
if err != nil {
return "", "", nil, errors.WithStack(err)
}
op := &pb.Op{
Op: &pb.Op_Source{
Source: &pb.SourceOp{
Identifier: srctypes.DockerImageScheme + "://" + ref.String(),
},
},
}
mut, err := sourcepolicy.NewEngine(spls).Evaluate(ctx, op)
if err != nil {
return "", "", nil, errors.Wrap(err, "could not resolve image due to policy")
}
if mut {
var (
t string
ok bool
)
t, newRef, ok := strings.Cut(op.GetSource().GetIdentifier(), "://")
if !ok {
return "", "", nil, errors.Errorf("could not parse ref: %s", op.GetSource().GetIdentifier())
}
if ok && t != srctypes.DockerImageScheme {
return "", "", nil, &ResolveToNonImageError{Ref: str, Updated: newRef}
}
ref, err = reference.Parse(newRef)
if err != nil {
return "", "", nil, errors.WithStack(err)
}
}
if leaseManager != nil {
ctx2, done, err := leaseutil.WithLease(ctx, leaseManager, leases.WithExpiration(5*time.Minute), leaseutil.MakeTemporary)
if err != nil {
return "", "", nil, errors.WithStack(err)
}
ctx = ctx2
defer func() {
// this lease is not deleted to allow other components to access manifest/config from cache. It will be deleted after 5 min deadline or on pruning inactive builder
AddLease(done)
}()
}
desc := ocispecs.Descriptor{
Digest: ref.Digest(),
}
if desc.Digest != "" {
ra, err := cache.ReaderAt(ctx, desc)
if err == nil {
info, err := cache.Info(ctx, desc.Digest)
if err == nil {
if ok, err := contentutil.HasSource(info, ref); err == nil && ok {
desc.Size = ra.Size()
mt, err := DetectManifestMediaType(ra)
if err == nil {
desc.MediaType = mt
}
}
}
}
}
// use resolver if desc is incomplete
if desc.MediaType == "" {
_, desc, err = resolver.Resolve(ctx, ref.String())
if err != nil {
return "", "", nil, err
}
}
fetcher, err := resolver.Fetcher(ctx, ref.String())
if err != nil {
return "", "", nil, err
}
if desc.MediaType == images.MediaTypeDockerSchema1Manifest {
dgst, dt, err := readSchema1Config(ctx, ref.String(), desc, fetcher, cache)
return ref.String(), dgst, dt, err
}
children := childrenConfigHandler(cache, platform)
dslHandler, err := docker.AppendDistributionSourceLabel(cache, ref.String())
if err != nil {
return "", "", nil, err
}
handlers := []images.Handler{
retryhandler.New(limited.FetchHandler(cache, fetcher, str), func(_ []byte) {}),
dslHandler,
children,
}
if err := images.Dispatch(ctx, images.Handlers(handlers...), nil, desc); err != nil {
return "", "", nil, err
}
config, err := images.Config(ctx, cache, desc, platform)
if err != nil {
return "", "", nil, err
}
dt, err := content.ReadBlob(ctx, cache, config)
if err != nil {
return "", "", nil, err
}
return ref.String(), desc.Digest, dt, nil
}
func childrenConfigHandler(provider content.Provider, platform platforms.MatchComparer) images.HandlerFunc {
return func(ctx context.Context, desc ocispecs.Descriptor) ([]ocispecs.Descriptor, error) {
var descs []ocispecs.Descriptor
switch desc.MediaType {
case images.MediaTypeDockerSchema2Manifest, ocispecs.MediaTypeImageManifest:
p, err := content.ReadBlob(ctx, provider, desc)
if err != nil {
return nil, err
}
// TODO(stevvooe): We just assume oci manifest, for now. There may be
// subtle differences from the docker version.
var manifest ocispecs.Manifest
if err := json.Unmarshal(p, &manifest); err != nil {
return nil, err
}
descs = append(descs, manifest.Config)
case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
p, err := content.ReadBlob(ctx, provider, desc)
if err != nil {
return nil, err
}
var index ocispecs.Index
if err := json.Unmarshal(p, &index); err != nil {
return nil, err
}
if platform != nil {
for _, d := range index.Manifests {
if d.Platform == nil || platform.Match(*d.Platform) {
descs = append(descs, d)
}
}
} else {
descs = append(descs, index.Manifests...)
}
case images.MediaTypeDockerSchema2Config, ocispecs.MediaTypeImageConfig, docker.LegacyConfigMediaType,
intoto.PayloadType:
// childless data types.
return nil, nil
default:
return nil, errors.Errorf("encountered unknown type %v; children may not be fetched", desc.MediaType)
}
return descs, nil
}
}
// specs.MediaTypeImageManifest, // TODO: detect schema1/manifest-list
func DetectManifestMediaType(ra content.ReaderAt) (string, error) {
// TODO: schema1
dt := make([]byte, ra.Size())
if _, err := ra.ReadAt(dt, 0); err != nil {
return "", err
}
return DetectManifestBlobMediaType(dt)
}
func DetectManifestBlobMediaType(dt []byte) (string, error) {
var mfst struct {
MediaType *string `json:"mediaType"`
Config json.RawMessage `json:"config"`
Manifests json.RawMessage `json:"manifests"`
Layers json.RawMessage `json:"layers"`
}
if err := json.Unmarshal(dt, &mfst); err != nil {
return "", err
}
mt := images.MediaTypeDockerSchema2ManifestList
if mfst.Config != nil || mfst.Layers != nil {
mt = images.MediaTypeDockerSchema2Manifest
if mfst.Manifests != nil {
return "", errors.Errorf("invalid ambiguous manifest and manifest list")
}
}
if mfst.MediaType != nil {
switch *mfst.MediaType {
case images.MediaTypeDockerSchema2ManifestList, ocispecs.MediaTypeImageIndex:
if mt != images.MediaTypeDockerSchema2ManifestList {
return "", errors.Errorf("mediaType in manifest does not match manifest contents")
}
mt = *mfst.MediaType
case images.MediaTypeDockerSchema2Manifest, ocispecs.MediaTypeImageManifest:
if mt != images.MediaTypeDockerSchema2Manifest {
return "", errors.Errorf("mediaType in manifest does not match manifest contents")
}
mt = *mfst.MediaType
}
}
return mt, nil
}

@ -0,0 +1,88 @@
package imageutil
import (
"context"
"encoding/json"
"io"
"strings"
"time"
"github.com/containerd/containerd/remotes"
"github.com/moby/buildkit/exporter/containerimage/image"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
func readSchema1Config(ctx context.Context, ref string, desc ocispecs.Descriptor, fetcher remotes.Fetcher, cache ContentCache) (digest.Digest, []byte, error) {
rc, err := fetcher.Fetch(ctx, desc)
if err != nil {
return "", nil, err
}
defer rc.Close()
dt, err := io.ReadAll(rc)
if err != nil {
return "", nil, errors.Wrap(err, "failed to fetch schema1 manifest")
}
dt, err = convertSchema1ConfigMeta(dt)
if err != nil {
return "", nil, err
}
return desc.Digest, dt, nil
}
func convertSchema1ConfigMeta(in []byte) ([]byte, error) {
type history struct {
V1Compatibility string `json:"v1Compatibility"`
}
var m struct {
History []history `json:"history"`
}
if err := json.Unmarshal(in, &m); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal schema1 manifest")
}
if len(m.History) == 0 {
return nil, errors.Errorf("invalid schema1 manifest")
}
var img image.Image
if err := json.Unmarshal([]byte(m.History[0].V1Compatibility), &img); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal image from schema 1 history")
}
img.RootFS = ocispecs.RootFS{
Type: "layers", // filled in by exporter
}
img.History = make([]ocispecs.History, len(m.History))
for i := range m.History {
var h v1History
if err := json.Unmarshal([]byte(m.History[i].V1Compatibility), &h); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal history")
}
img.History[len(m.History)-i-1] = ocispecs.History{
Author: h.Author,
Comment: h.Comment,
Created: &h.Created,
CreatedBy: strings.Join(h.ContainerConfig.Cmd, " "),
EmptyLayer: (h.ThrowAway != nil && *h.ThrowAway) || (h.Size != nil && *h.Size == 0),
}
}
dt, err := json.MarshalIndent(img, "", " ")
if err != nil {
return nil, errors.Wrap(err, "failed to marshal schema1 config")
}
return dt, nil
}
type v1History struct {
Author string `json:"author,omitempty"`
Created time.Time `json:"created"`
Comment string `json:"comment,omitempty"`
ThrowAway *bool `json:"throwaway,omitempty"`
Size *int `json:"Size,omitempty"` // used before ThrowAway field
ContainerConfig struct {
Cmd []string `json:"Cmd,omitempty"`
} `json:"container_config,omitempty"`
}

@ -0,0 +1,83 @@
package leaseutil
import (
"context"
"time"
"github.com/containerd/containerd/leases"
"github.com/containerd/containerd/namespaces"
)
func WithLease(ctx context.Context, ls leases.Manager, opts ...leases.Opt) (context.Context, func(context.Context) error, error) {
_, ok := leases.FromContext(ctx)
if ok {
return ctx, func(context.Context) error {
return nil
}, nil
}
l, err := ls.Create(ctx, append([]leases.Opt{leases.WithRandomID(), leases.WithExpiration(time.Hour)}, opts...)...)
if err != nil {
return nil, nil, err
}
ctx = leases.WithLease(ctx, l.ID)
return ctx, func(ctx context.Context) error {
return ls.Delete(ctx, l)
}, nil
}
func MakeTemporary(l *leases.Lease) error {
if l.Labels == nil {
l.Labels = map[string]string{}
}
l.Labels["buildkit/lease.temporary"] = time.Now().UTC().Format(time.RFC3339Nano)
return nil
}
func WithNamespace(lm leases.Manager, ns string) *Manager {
return &Manager{manager: lm, ns: ns}
}
type Manager struct {
manager leases.Manager
ns string
}
func (l *Manager) Namespace() string {
return l.ns
}
func (l *Manager) WithNamespace(ns string) *Manager {
return WithNamespace(l.manager, ns)
}
func (l *Manager) Create(ctx context.Context, opts ...leases.Opt) (leases.Lease, error) {
ctx = namespaces.WithNamespace(ctx, l.ns)
return l.manager.Create(ctx, opts...)
}
func (l *Manager) Delete(ctx context.Context, lease leases.Lease, opts ...leases.DeleteOpt) error {
ctx = namespaces.WithNamespace(ctx, l.ns)
return l.manager.Delete(ctx, lease, opts...)
}
func (l *Manager) List(ctx context.Context, filters ...string) ([]leases.Lease, error) {
ctx = namespaces.WithNamespace(ctx, l.ns)
return l.manager.List(ctx, filters...)
}
func (l *Manager) AddResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error {
ctx = namespaces.WithNamespace(ctx, l.ns)
return l.manager.AddResource(ctx, lease, resource)
}
func (l *Manager) DeleteResource(ctx context.Context, lease leases.Lease, resource leases.Resource) error {
ctx = namespaces.WithNamespace(ctx, l.ns)
return l.manager.DeleteResource(ctx, lease, resource)
}
func (l *Manager) ListResources(ctx context.Context, lease leases.Lease) ([]leases.Resource, error) {
ctx = namespaces.WithNamespace(ctx, l.ns)
return l.manager.ListResources(ctx, lease)
}

@ -37,8 +37,8 @@ func NormalizePath(parent, newPath, inputOS string, keepSlash bool) (string, err
inputOS = "linux"
}
newPath = toSlash(newPath, inputOS)
parent = toSlash(parent, inputOS)
newPath = ToSlash(newPath, inputOS)
parent = ToSlash(parent, inputOS)
origPath := newPath
if parent == "" {
@ -82,18 +82,17 @@ func NormalizePath(parent, newPath, inputOS string, keepSlash bool) (string, err
}
}
return toSlash(newPath, inputOS), nil
return ToSlash(newPath, inputOS), nil
}
func toSlash(inputPath, inputOS string) string {
separator := "/"
if inputOS == "windows" {
separator = "\\"
func ToSlash(inputPath, inputOS string) string {
if inputOS != "windows" {
return inputPath
}
return strings.Replace(inputPath, separator, "/", -1)
return strings.Replace(inputPath, "\\", "/", -1)
}
func fromSlash(inputPath, inputOS string) string {
func FromSlash(inputPath, inputOS string) string {
separator := "/"
if inputOS == "windows" {
separator = "\\"
@ -119,7 +118,7 @@ func NormalizeWorkdir(current, wd string, inputOS string) (string, error) {
// Make sure we use the platform specific path separator. HCS does not like forward
// slashes in CWD.
return fromSlash(wd, inputOS), nil
return FromSlash(wd, inputOS), nil
}
// IsAbs returns a boolean value indicating whether or not the path
@ -142,7 +141,7 @@ func IsAbs(pth, inputOS string) bool {
if err != nil {
return false
}
cleanedPath = toSlash(cleanedPath, inputOS)
cleanedPath = ToSlash(cleanedPath, inputOS)
// We stripped any potential drive letter and converted any backslashes to
// forward slashes. We can safely use path.IsAbs() for both Windows and Linux.
return path.IsAbs(cleanedPath)
@ -189,14 +188,14 @@ func CheckSystemDriveAndRemoveDriveLetter(path string, inputOS string) (string,
}
// UNC paths should error out
if len(path) >= 2 && toSlash(path[:2], inputOS) == "//" {
if len(path) >= 2 && ToSlash(path[:2], inputOS) == "//" {
return "", errors.Errorf("UNC paths are not supported")
}
parts := strings.SplitN(path, ":", 2)
// Path does not have a drive letter. Just return it.
if len(parts) < 2 {
return toSlash(filepath.Clean(path), inputOS), nil
return ToSlash(filepath.Clean(path), inputOS), nil
}
// We expect all paths to be in C:
@ -221,5 +220,5 @@ func CheckSystemDriveAndRemoveDriveLetter(path string, inputOS string) (string,
//
// We must return the second element of the split path, as is, without attempting to convert
// it to an absolute path. We have no knowledge of the CWD; that is treated elsewhere.
return toSlash(filepath.Clean(parts[1]), inputOS), nil
return ToSlash(filepath.Clean(parts[1]), inputOS), nil
}

@ -137,6 +137,7 @@ func (c Moby) New(ctx context.Context, cfg *BackendConfig) (b Backend, cl func()
dockerdFlags := []string{
"--config-file", dockerdConfigFile,
"--userland-proxy=false",
"--tls=false",
"--debug",
}
if s := os.Getenv("BUILDKIT_INTEGRATION_DOCKERD_FLAGS"); s != "" {

@ -0,0 +1,87 @@
package wildcard
import (
"regexp"
"strings"
"github.com/pkg/errors"
)
// New returns a wildcard object for a string that contains "*" symbols.
func New(s string) (*Wildcard, error) {
reStr, err := Wildcard2Regexp(s)
if err != nil {
return nil, errors.Wrapf(err, "failed to translate wildcard %q to regexp", s)
}
re, err := regexp.Compile(reStr)
if err != nil {
return nil, errors.Wrapf(err, "failed to compile regexp %q (translated from wildcard %q)", reStr, s)
}
w := &Wildcard{
orig: s,
re: re,
}
return w, nil
}
// Wildcard2Regexp translates a wildcard string to a regexp string.
func Wildcard2Regexp(wildcard string) (string, error) {
s := regexp.QuoteMeta(wildcard)
if strings.Contains(s, "\\*\\*") {
return "", errors.New("invalid wildcard: \"**\"")
}
s = strings.ReplaceAll(s, "\\*", "(.*)")
s = "^" + s + "$"
return s, nil
}
// Wildcard is a wildcard matcher object.
type Wildcard struct {
orig string
re *regexp.Regexp
}
// String implements fmt.Stringer.
func (w *Wildcard) String() string {
return w.orig
}
// Match returns a non-nil Match on match.
func (w *Wildcard) Match(q string) *Match {
submatches := w.re.FindStringSubmatch(q)
if len(submatches) == 0 {
return nil
}
m := &Match{
w: w,
Submatches: submatches,
// FIXME: avoid executing regexp twice
idx: w.re.FindStringSubmatchIndex(q),
}
return m
}
// Match is a matched result.
type Match struct {
w *Wildcard
Submatches []string // 0: the entire query, 1: the first submatch, 2: the second submatch, ...
idx []int
}
// String implements fmt.Stringer.
func (m *Match) String() string {
if len(m.Submatches) == 0 {
return ""
}
return m.Submatches[0]
}
// Format formats submatch strings like "$1", "$2".
func (m *Match) Format(f string) (string, error) {
if m.w == nil || len(m.Submatches) == 0 || len(m.idx) == 0 {
return "", errors.New("invalid state")
}
var b []byte
b = m.w.re.ExpandString(b, f, m.Submatches[0], m.idx)
return string(b), nil
}

13
vendor/modules.txt vendored

@ -157,10 +157,13 @@ github.com/containerd/containerd/content/proxy
github.com/containerd/containerd/defaults
github.com/containerd/containerd/errdefs
github.com/containerd/containerd/filters
github.com/containerd/containerd/identifiers
github.com/containerd/containerd/images
github.com/containerd/containerd/images/archive
github.com/containerd/containerd/labels
github.com/containerd/containerd/leases
github.com/containerd/containerd/log
github.com/containerd/containerd/namespaces
github.com/containerd/containerd/pkg/dialer
github.com/containerd/containerd/pkg/randutil
github.com/containerd/containerd/pkg/seed
@ -188,6 +191,9 @@ github.com/containerd/continuity/fs/fstest
github.com/containerd/continuity/pathdriver
github.com/containerd/continuity/proto
github.com/containerd/continuity/sysx
# github.com/containerd/ttrpc v1.2.2
## explicit; go 1.13
github.com/containerd/ttrpc
# github.com/containerd/typeurl/v2 v2.1.1
## explicit; go 1.13
github.com/containerd/typeurl/v2
@ -495,7 +501,7 @@ github.com/mitchellh/go-wordwrap
# github.com/mitchellh/mapstructure v1.5.0
## explicit; go 1.14
github.com/mitchellh/mapstructure
# github.com/moby/buildkit v0.11.0-rc3.0.20230620112432-2d91ddcceedc
# github.com/moby/buildkit v0.12.1-0.20230717122532-faa0cc7da353
## explicit; go 1.20
github.com/moby/buildkit/api/services/control
github.com/moby/buildkit/api/types
@ -536,6 +542,8 @@ github.com/moby/buildkit/session/upload/uploadprovider
github.com/moby/buildkit/solver/errdefs
github.com/moby/buildkit/solver/pb
github.com/moby/buildkit/solver/result
github.com/moby/buildkit/source/types
github.com/moby/buildkit/sourcepolicy
github.com/moby/buildkit/sourcepolicy/pb
github.com/moby/buildkit/util/apicaps
github.com/moby/buildkit/util/apicaps/pb
@ -547,6 +555,8 @@ github.com/moby/buildkit/util/entitlements
github.com/moby/buildkit/util/flightcontrol
github.com/moby/buildkit/util/gitutil
github.com/moby/buildkit/util/grpcerrors
github.com/moby/buildkit/util/imageutil
github.com/moby/buildkit/util/leaseutil
github.com/moby/buildkit/util/progress
github.com/moby/buildkit/util/progress/progressui
github.com/moby/buildkit/util/progress/progresswriter
@ -564,6 +574,7 @@ github.com/moby/buildkit/util/tracing/detect
github.com/moby/buildkit/util/tracing/detect/delegated
github.com/moby/buildkit/util/tracing/env
github.com/moby/buildkit/util/tracing/otlptracegrpc
github.com/moby/buildkit/util/wildcard
github.com/moby/buildkit/version
# github.com/moby/locker v1.0.1
## explicit; go 1.13

Loading…
Cancel
Save