vendor: add buildkit

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
Tonis Tiigi
2019-03-23 22:44:59 -07:00
parent 62faee5f07
commit 8b7c38e61a
364 changed files with 78556 additions and 1007 deletions

105
vendor/github.com/moby/buildkit/client/build.go generated vendored Normal file
View File

@ -0,0 +1,105 @@
package client
import (
"context"
"github.com/moby/buildkit/client/buildid"
gateway "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/frontend/gateway/grpcclient"
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/util/apicaps"
"github.com/pkg/errors"
"google.golang.org/grpc"
)
func (c *Client) Build(ctx context.Context, opt SolveOpt, product string, buildFunc gateway.BuildFunc, statusChan chan *SolveStatus) (*SolveResponse, error) {
defer func() {
if statusChan != nil {
close(statusChan)
}
}()
if opt.Frontend != "" {
return nil, errors.New("invalid SolveOpt, Build interface cannot use Frontend")
}
if product == "" {
product = apicaps.ExportedProduct
}
feOpts := opt.FrontendAttrs
opt.FrontendAttrs = nil
workers, err := c.ListWorkers(ctx)
if err != nil {
return nil, errors.Wrap(err, "listing workers for Build")
}
var gworkers []gateway.WorkerInfo
for _, w := range workers {
gworkers = append(gworkers, gateway.WorkerInfo{
ID: w.ID,
Labels: w.Labels,
Platforms: w.Platforms,
})
}
cb := func(ref string, s *session.Session) error {
g, err := grpcclient.New(ctx, feOpts, s.ID(), product, c.gatewayClientForBuild(ref), gworkers)
if err != nil {
return err
}
if err := g.Run(ctx, buildFunc); err != nil {
return errors.Wrap(err, "failed to run Build function")
}
return nil
}
return c.solve(ctx, nil, cb, opt, statusChan)
}
func (c *Client) gatewayClientForBuild(buildid string) gatewayapi.LLBBridgeClient {
g := gatewayapi.NewLLBBridgeClient(c.conn)
return &gatewayClientForBuild{g, buildid}
}
type gatewayClientForBuild struct {
gateway gatewayapi.LLBBridgeClient
buildID string
}
func (g *gatewayClientForBuild) ResolveImageConfig(ctx context.Context, in *gatewayapi.ResolveImageConfigRequest, opts ...grpc.CallOption) (*gatewayapi.ResolveImageConfigResponse, error) {
ctx = buildid.AppendToOutgoingContext(ctx, g.buildID)
return g.gateway.ResolveImageConfig(ctx, in, opts...)
}
func (g *gatewayClientForBuild) Solve(ctx context.Context, in *gatewayapi.SolveRequest, opts ...grpc.CallOption) (*gatewayapi.SolveResponse, error) {
ctx = buildid.AppendToOutgoingContext(ctx, g.buildID)
return g.gateway.Solve(ctx, in, opts...)
}
func (g *gatewayClientForBuild) ReadFile(ctx context.Context, in *gatewayapi.ReadFileRequest, opts ...grpc.CallOption) (*gatewayapi.ReadFileResponse, error) {
ctx = buildid.AppendToOutgoingContext(ctx, g.buildID)
return g.gateway.ReadFile(ctx, in, opts...)
}
func (g *gatewayClientForBuild) ReadDir(ctx context.Context, in *gatewayapi.ReadDirRequest, opts ...grpc.CallOption) (*gatewayapi.ReadDirResponse, error) {
ctx = buildid.AppendToOutgoingContext(ctx, g.buildID)
return g.gateway.ReadDir(ctx, in, opts...)
}
func (g *gatewayClientForBuild) StatFile(ctx context.Context, in *gatewayapi.StatFileRequest, opts ...grpc.CallOption) (*gatewayapi.StatFileResponse, error) {
ctx = buildid.AppendToOutgoingContext(ctx, g.buildID)
return g.gateway.StatFile(ctx, in, opts...)
}
func (g *gatewayClientForBuild) Ping(ctx context.Context, in *gatewayapi.PingRequest, opts ...grpc.CallOption) (*gatewayapi.PongResponse, error) {
ctx = buildid.AppendToOutgoingContext(ctx, g.buildID)
return g.gateway.Ping(ctx, in, opts...)
}
func (g *gatewayClientForBuild) Return(ctx context.Context, in *gatewayapi.ReturnRequest, opts ...grpc.CallOption) (*gatewayapi.ReturnResponse, error) {
ctx = buildid.AppendToOutgoingContext(ctx, g.buildID)
return g.gateway.Return(ctx, in, opts...)
}

View File

@ -0,0 +1,29 @@
package buildid
import (
"context"
"google.golang.org/grpc/metadata"
)
var metadataKey = "buildkit-controlapi-buildid"
func AppendToOutgoingContext(ctx context.Context, id string) context.Context {
if id != "" {
return metadata.AppendToOutgoingContext(ctx, metadataKey, id)
}
return ctx
}
func FromIncomingContext(ctx context.Context) string {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return ""
}
if ids := md.Get(metadataKey); len(ids) == 1 {
return ids[0]
}
return ""
}

130
vendor/github.com/moby/buildkit/client/client.go generated vendored Normal file
View File

@ -0,0 +1,130 @@
package client
import (
"context"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/util/appdefaults"
opentracing "github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
type Client struct {
conn *grpc.ClientConn
}
type ClientOpt interface{}
// New returns a new buildkit client. Address can be empty for the system-default address.
func New(ctx context.Context, address string, opts ...ClientOpt) (*Client, error) {
gopts := []grpc.DialOption{
grpc.WithDialer(dialer),
}
needWithInsecure := true
for _, o := range opts {
if _, ok := o.(*withFailFast); ok {
gopts = append(gopts, grpc.FailOnNonTempDialError(true))
}
if credInfo, ok := o.(*withCredentials); ok {
opt, err := loadCredentials(credInfo)
if err != nil {
return nil, err
}
gopts = append(gopts, opt)
needWithInsecure = false
}
if wt, ok := o.(*withTracer); ok {
gopts = append(gopts,
grpc.WithUnaryInterceptor(otgrpc.OpenTracingClientInterceptor(wt.tracer, otgrpc.LogPayloads())),
grpc.WithStreamInterceptor(otgrpc.OpenTracingStreamClientInterceptor(wt.tracer)))
}
}
if needWithInsecure {
gopts = append(gopts, grpc.WithInsecure())
}
if address == "" {
address = appdefaults.Address
}
conn, err := grpc.DialContext(ctx, address, gopts...)
if err != nil {
return nil, errors.Wrapf(err, "failed to dial %q . make sure buildkitd is running", address)
}
c := &Client{
conn: conn,
}
return c, nil
}
func (c *Client) controlClient() controlapi.ControlClient {
return controlapi.NewControlClient(c.conn)
}
func (c *Client) Close() error {
return c.conn.Close()
}
type withFailFast struct{}
func WithFailFast() ClientOpt {
return &withFailFast{}
}
type withCredentials struct {
ServerName string
CACert string
Cert string
Key string
}
// WithCredentials configures the TLS parameters of the client.
// Arguments:
// * serverName: specifies the name of the target server
// * ca: specifies the filepath of the CA certificate to use for verification
// * cert: specifies the filepath of the client certificate
// * key: specifies the filepath of the client key
func WithCredentials(serverName, ca, cert, key string) ClientOpt {
return &withCredentials{serverName, ca, cert, key}
}
func loadCredentials(opts *withCredentials) (grpc.DialOption, error) {
ca, err := ioutil.ReadFile(opts.CACert)
if err != nil {
return nil, errors.Wrap(err, "could not read ca certificate")
}
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(ca); !ok {
return nil, errors.New("failed to append ca certs")
}
cfg := &tls.Config{
ServerName: opts.ServerName,
RootCAs: certPool,
}
// we will produce an error if the user forgot about either cert or key if at least one is specified
if opts.Cert != "" || opts.Key != "" {
cert, err := tls.LoadX509KeyPair(opts.Cert, opts.Key)
if err != nil {
return nil, errors.Wrap(err, "could not read certificate/key")
}
cfg.Certificates = []tls.Certificate{cert}
cfg.BuildNameToCertificate()
}
return grpc.WithTransportCredentials(credentials.NewTLS(cfg)), nil
}
func WithTracer(t opentracing.Tracer) ClientOpt {
return &withTracer{t}
}
type withTracer struct {
tracer opentracing.Tracer
}

19
vendor/github.com/moby/buildkit/client/client_unix.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
// +build !windows
package client
import (
"net"
"strings"
"time"
"github.com/pkg/errors"
)
func dialer(address string, timeout time.Duration) (net.Conn, error) {
addrParts := strings.SplitN(address, "://", 2)
if len(addrParts) != 2 {
return nil, errors.Errorf("invalid address %s", address)
}
return net.DialTimeout(addrParts[0], addrParts[1], timeout)
}

View File

@ -0,0 +1,24 @@
package client
import (
"net"
"strings"
"time"
"github.com/Microsoft/go-winio"
"github.com/pkg/errors"
)
func dialer(address string, timeout time.Duration) (net.Conn, error) {
addrParts := strings.SplitN(address, "://", 2)
if len(addrParts) != 2 {
return nil, errors.Errorf("invalid address %s", address)
}
switch addrParts[0] {
case "npipe":
address = strings.Replace(addrParts[1], "/", "\\", -1)
return winio.DialPipe(address, &timeout)
default:
return net.DialTimeout(addrParts[0], addrParts[1], timeout)
}
}

84
vendor/github.com/moby/buildkit/client/diskusage.go generated vendored Normal file
View File

@ -0,0 +1,84 @@
package client
import (
"context"
"sort"
"time"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/pkg/errors"
)
type UsageInfo struct {
ID string
Mutable bool
InUse bool
Size int64
CreatedAt time.Time
LastUsedAt *time.Time
UsageCount int
Parent string
Description string
RecordType UsageRecordType
Shared bool
}
func (c *Client) DiskUsage(ctx context.Context, opts ...DiskUsageOption) ([]*UsageInfo, error) {
info := &DiskUsageInfo{}
for _, o := range opts {
o.SetDiskUsageOption(info)
}
req := &controlapi.DiskUsageRequest{Filter: info.Filter}
resp, err := c.controlClient().DiskUsage(ctx, req)
if err != nil {
return nil, errors.Wrap(err, "failed to call diskusage")
}
var du []*UsageInfo
for _, d := range resp.Record {
du = append(du, &UsageInfo{
ID: d.ID,
Mutable: d.Mutable,
InUse: d.InUse,
Size: d.Size_,
Parent: d.Parent,
CreatedAt: d.CreatedAt,
Description: d.Description,
UsageCount: int(d.UsageCount),
LastUsedAt: d.LastUsedAt,
RecordType: UsageRecordType(d.RecordType),
Shared: d.Shared,
})
}
sort.Slice(du, func(i, j int) bool {
if du[i].Size == du[j].Size {
return du[i].ID > du[j].ID
}
return du[i].Size > du[j].Size
})
return du, nil
}
type DiskUsageOption interface {
SetDiskUsageOption(*DiskUsageInfo)
}
type DiskUsageInfo struct {
Filter []string
}
type UsageRecordType string
const (
UsageRecordTypeInternal UsageRecordType = "internal"
UsageRecordTypeFrontend UsageRecordType = "frontend"
UsageRecordTypeLocalSource UsageRecordType = "source.local"
UsageRecordTypeGitCheckout UsageRecordType = "source.git.checkout"
UsageRecordTypeCacheMount UsageRecordType = "exec.cachemount"
UsageRecordTypeRegular UsageRecordType = "regular"
)

8
vendor/github.com/moby/buildkit/client/exporters.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
package client
const (
ExporterImage = "image"
ExporterLocal = "local"
ExporterOCI = "oci"
ExporterDocker = "docker"
)

19
vendor/github.com/moby/buildkit/client/filter.go generated vendored Normal file
View File

@ -0,0 +1,19 @@
package client
func WithFilter(f []string) Filter {
return Filter(f)
}
type Filter []string
func (f Filter) SetDiskUsageOption(di *DiskUsageInfo) {
di.Filter = f
}
func (f Filter) SetPruneOption(pi *PruneInfo) {
pi.Filter = f
}
func (f Filter) SetListWorkersOption(lwi *ListWorkersInfo) {
lwi.Filter = f
}

46
vendor/github.com/moby/buildkit/client/graph.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
package client
import (
"time"
digest "github.com/opencontainers/go-digest"
)
type Vertex struct {
Digest digest.Digest
Inputs []digest.Digest
Name string
Started *time.Time
Completed *time.Time
Cached bool
Error string
}
type VertexStatus struct {
ID string
Vertex digest.Digest
Name string
Total int64
Current int64
Timestamp time.Time
Started *time.Time
Completed *time.Time
}
type VertexLog struct {
Vertex digest.Digest
Stream int
Data []byte
Timestamp time.Time
}
type SolveStatus struct {
Vertexes []*Vertex
Statuses []*VertexStatus
Logs []*VertexLog
}
type SolveResponse struct {
// ExporterResponse is also used for CacheExporter
ExporterResponse map[string]string
}

625
vendor/github.com/moby/buildkit/client/llb/exec.go generated vendored Normal file
View File

@ -0,0 +1,625 @@
package llb
import (
_ "crypto/sha256"
"fmt"
"net"
"sort"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/system"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
type Meta struct {
Args []string
Env EnvList
Cwd string
User string
ProxyEnv *ProxyEnv
ExtraHosts []HostIP
Network pb.NetMode
}
func NewExecOp(root Output, meta Meta, readOnly bool, c Constraints) *ExecOp {
e := &ExecOp{meta: meta, constraints: c}
rootMount := &mount{
target: pb.RootMount,
source: root,
readonly: readOnly,
}
e.mounts = append(e.mounts, rootMount)
if readOnly {
e.root = root
} else {
o := &output{vertex: e, getIndex: e.getMountIndexFn(rootMount)}
if p := c.Platform; p != nil {
o.platform = p
}
e.root = o
}
rootMount.output = e.root
return e
}
type mount struct {
target string
readonly bool
source Output
output Output
selector string
cacheID string
tmpfs bool
cacheSharing CacheMountSharingMode
// hasOutput bool
}
type ExecOp struct {
MarshalCache
root Output
mounts []*mount
meta Meta
constraints Constraints
isValidated bool
secrets []SecretInfo
ssh []SSHInfo
}
func (e *ExecOp) AddMount(target string, source Output, opt ...MountOption) Output {
m := &mount{
target: target,
source: source,
}
for _, o := range opt {
o(m)
}
e.mounts = append(e.mounts, m)
if m.readonly {
m.output = source
} else if m.tmpfs {
m.output = &output{vertex: e, err: errors.Errorf("tmpfs mount for %s can't be used as a parent", target)}
} else {
o := &output{vertex: e, getIndex: e.getMountIndexFn(m)}
if p := e.constraints.Platform; p != nil {
o.platform = p
}
m.output = o
}
e.Store(nil, nil, nil)
e.isValidated = false
return m.output
}
func (e *ExecOp) GetMount(target string) Output {
for _, m := range e.mounts {
if m.target == target {
return m.output
}
}
return nil
}
func (e *ExecOp) Validate() error {
if e.isValidated {
return nil
}
if len(e.meta.Args) == 0 {
return errors.Errorf("arguments are required")
}
if e.meta.Cwd == "" {
return errors.Errorf("working directory is required")
}
for _, m := range e.mounts {
if m.source != nil {
if err := m.source.Vertex().Validate(); err != nil {
return err
}
}
}
e.isValidated = true
return nil
}
func (e *ExecOp) Marshal(c *Constraints) (digest.Digest, []byte, *pb.OpMetadata, error) {
if e.Cached(c) {
return e.Load()
}
if err := e.Validate(); err != nil {
return "", nil, nil, err
}
// make sure mounts are sorted
sort.Slice(e.mounts, func(i, j int) bool {
return e.mounts[i].target < e.mounts[j].target
})
if len(e.ssh) > 0 {
for i, s := range e.ssh {
if s.Target == "" {
e.ssh[i].Target = fmt.Sprintf("/run/buildkit/ssh_agent.%d", i)
}
}
if _, ok := e.meta.Env.Get("SSH_AUTH_SOCK"); !ok {
e.meta.Env = e.meta.Env.AddOrReplace("SSH_AUTH_SOCK", e.ssh[0].Target)
}
}
if c.Caps != nil {
if err := c.Caps.Supports(pb.CapExecMetaSetsDefaultPath); err != nil {
e.meta.Env = e.meta.Env.SetDefault("PATH", system.DefaultPathEnv)
} else {
addCap(&e.constraints, pb.CapExecMetaSetsDefaultPath)
}
}
meta := &pb.Meta{
Args: e.meta.Args,
Env: e.meta.Env.ToArray(),
Cwd: e.meta.Cwd,
User: e.meta.User,
}
if len(e.meta.ExtraHosts) > 0 {
hosts := make([]*pb.HostIP, len(e.meta.ExtraHosts))
for i, h := range e.meta.ExtraHosts {
hosts[i] = &pb.HostIP{Host: h.Host, IP: h.IP.String()}
}
meta.ExtraHosts = hosts
}
peo := &pb.ExecOp{
Meta: meta,
Network: e.meta.Network,
}
if e.meta.Network != NetModeSandbox {
addCap(&e.constraints, pb.CapExecMetaNetwork)
}
if p := e.meta.ProxyEnv; p != nil {
peo.Meta.ProxyEnv = &pb.ProxyEnv{
HttpProxy: p.HttpProxy,
HttpsProxy: p.HttpsProxy,
FtpProxy: p.FtpProxy,
NoProxy: p.NoProxy,
}
addCap(&e.constraints, pb.CapExecMetaProxy)
}
addCap(&e.constraints, pb.CapExecMetaBase)
for _, m := range e.mounts {
if m.selector != "" {
addCap(&e.constraints, pb.CapExecMountSelector)
}
if m.cacheID != "" {
addCap(&e.constraints, pb.CapExecMountCache)
addCap(&e.constraints, pb.CapExecMountCacheSharing)
} else if m.tmpfs {
addCap(&e.constraints, pb.CapExecMountTmpfs)
} else if m.source != nil {
addCap(&e.constraints, pb.CapExecMountBind)
}
}
if len(e.secrets) > 0 {
addCap(&e.constraints, pb.CapExecMountSecret)
}
if len(e.ssh) > 0 {
addCap(&e.constraints, pb.CapExecMountSSH)
}
pop, md := MarshalConstraints(c, &e.constraints)
pop.Op = &pb.Op_Exec{
Exec: peo,
}
outIndex := 0
for _, m := range e.mounts {
inputIndex := pb.InputIndex(len(pop.Inputs))
if m.source != nil {
if m.tmpfs {
return "", nil, nil, errors.Errorf("tmpfs mounts must use scratch")
}
inp, err := m.source.ToInput(c)
if err != nil {
return "", nil, nil, err
}
newInput := true
for i, inp2 := range pop.Inputs {
if *inp == *inp2 {
inputIndex = pb.InputIndex(i)
newInput = false
break
}
}
if newInput {
pop.Inputs = append(pop.Inputs, inp)
}
} else {
inputIndex = pb.Empty
}
outputIndex := pb.OutputIndex(-1)
if !m.readonly && m.cacheID == "" && !m.tmpfs {
outputIndex = pb.OutputIndex(outIndex)
outIndex++
}
pm := &pb.Mount{
Input: inputIndex,
Dest: m.target,
Readonly: m.readonly,
Output: outputIndex,
Selector: m.selector,
}
if m.cacheID != "" {
pm.MountType = pb.MountType_CACHE
pm.CacheOpt = &pb.CacheOpt{
ID: m.cacheID,
}
switch m.cacheSharing {
case CacheMountShared:
pm.CacheOpt.Sharing = pb.CacheSharingOpt_SHARED
case CacheMountPrivate:
pm.CacheOpt.Sharing = pb.CacheSharingOpt_PRIVATE
case CacheMountLocked:
pm.CacheOpt.Sharing = pb.CacheSharingOpt_LOCKED
}
}
if m.tmpfs {
pm.MountType = pb.MountType_TMPFS
}
peo.Mounts = append(peo.Mounts, pm)
}
for _, s := range e.secrets {
pm := &pb.Mount{
Dest: s.Target,
MountType: pb.MountType_SECRET,
SecretOpt: &pb.SecretOpt{
ID: s.ID,
Uid: uint32(s.UID),
Gid: uint32(s.GID),
Optional: s.Optional,
Mode: uint32(s.Mode),
},
}
peo.Mounts = append(peo.Mounts, pm)
}
for _, s := range e.ssh {
pm := &pb.Mount{
Dest: s.Target,
MountType: pb.MountType_SSH,
SSHOpt: &pb.SSHOpt{
ID: s.ID,
Uid: uint32(s.UID),
Gid: uint32(s.GID),
Mode: uint32(s.Mode),
Optional: s.Optional,
},
}
peo.Mounts = append(peo.Mounts, pm)
}
dt, err := pop.Marshal()
if err != nil {
return "", nil, nil, err
}
e.Store(dt, md, c)
return e.Load()
}
func (e *ExecOp) Output() Output {
return e.root
}
func (e *ExecOp) Inputs() (inputs []Output) {
mm := map[Output]struct{}{}
for _, m := range e.mounts {
if m.source != nil {
mm[m.source] = struct{}{}
}
}
for o := range mm {
inputs = append(inputs, o)
}
return
}
func (e *ExecOp) getMountIndexFn(m *mount) func() (pb.OutputIndex, error) {
return func() (pb.OutputIndex, error) {
// make sure mounts are sorted
sort.Slice(e.mounts, func(i, j int) bool {
return e.mounts[i].target < e.mounts[j].target
})
i := 0
for _, m2 := range e.mounts {
if m2.readonly || m2.cacheID != "" {
continue
}
if m == m2 {
return pb.OutputIndex(i), nil
}
i++
}
return pb.OutputIndex(0), errors.Errorf("invalid mount: %s", m.target)
}
}
type ExecState struct {
State
exec *ExecOp
}
func (e ExecState) AddMount(target string, source State, opt ...MountOption) State {
return source.WithOutput(e.exec.AddMount(target, source.Output(), opt...))
}
func (e ExecState) GetMount(target string) State {
return NewState(e.exec.GetMount(target))
}
func (e ExecState) Root() State {
return e.State
}
type MountOption func(*mount)
func Readonly(m *mount) {
m.readonly = true
}
func SourcePath(src string) MountOption {
return func(m *mount) {
m.selector = src
}
}
func AsPersistentCacheDir(id string, sharing CacheMountSharingMode) MountOption {
return func(m *mount) {
m.cacheID = id
m.cacheSharing = sharing
}
}
func Tmpfs() MountOption {
return func(m *mount) {
m.tmpfs = true
}
}
type RunOption interface {
SetRunOption(es *ExecInfo)
}
type runOptionFunc func(*ExecInfo)
func (fn runOptionFunc) SetRunOption(ei *ExecInfo) {
fn(ei)
}
func Network(n pb.NetMode) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = network(n)(ei.State)
})
}
func Shlex(str string) RunOption {
return Shlexf(str)
}
func Shlexf(str string, v ...interface{}) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = shlexf(str, v...)(ei.State)
})
}
func Args(a []string) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = args(a...)(ei.State)
})
}
func AddEnv(key, value string) RunOption {
return AddEnvf(key, value)
}
func AddEnvf(key, value string, v ...interface{}) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.AddEnvf(key, value, v...)
})
}
func User(str string) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.User(str)
})
}
func Dir(str string) RunOption {
return Dirf(str)
}
func Dirf(str string, v ...interface{}) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.Dirf(str, v...)
})
}
func AddExtraHost(host string, ip net.IP) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.AddExtraHost(host, ip)
})
}
func Reset(s State) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.Reset(s)
})
}
func With(so ...StateOption) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.With(so...)
})
}
func AddMount(dest string, mountState State, opts ...MountOption) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.Mounts = append(ei.Mounts, MountInfo{dest, mountState.Output(), opts})
})
}
func AddSSHSocket(opts ...SSHOption) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
s := &SSHInfo{
Mode: 0600,
}
for _, opt := range opts {
opt.SetSSHOption(s)
}
ei.SSH = append(ei.SSH, *s)
})
}
type SSHOption interface {
SetSSHOption(*SSHInfo)
}
type sshOptionFunc func(*SSHInfo)
func (fn sshOptionFunc) SetSSHOption(si *SSHInfo) {
fn(si)
}
func SSHID(id string) SSHOption {
return sshOptionFunc(func(si *SSHInfo) {
si.ID = id
})
}
func SSHSocketTarget(target string) SSHOption {
return sshOptionFunc(func(si *SSHInfo) {
si.Target = target
})
}
func SSHSocketOpt(target string, uid, gid, mode int) SSHOption {
return sshOptionFunc(func(si *SSHInfo) {
si.Target = target
si.UID = uid
si.GID = gid
si.Mode = mode
})
}
var SSHOptional = sshOptionFunc(func(si *SSHInfo) {
si.Optional = true
})
type SSHInfo struct {
ID string
Target string
Mode int
UID int
GID int
Optional bool
}
func AddSecret(dest string, opts ...SecretOption) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
s := &SecretInfo{ID: dest, Target: dest, Mode: 0400}
for _, opt := range opts {
opt.SetSecretOption(s)
}
ei.Secrets = append(ei.Secrets, *s)
})
}
type SecretOption interface {
SetSecretOption(*SecretInfo)
}
type secretOptionFunc func(*SecretInfo)
func (fn secretOptionFunc) SetSecretOption(si *SecretInfo) {
fn(si)
}
type SecretInfo struct {
ID string
Target string
Mode int
UID int
GID int
Optional bool
}
var SecretOptional = secretOptionFunc(func(si *SecretInfo) {
si.Optional = true
})
func SecretID(id string) SecretOption {
return secretOptionFunc(func(si *SecretInfo) {
si.ID = id
})
}
func SecretFileOpt(uid, gid, mode int) SecretOption {
return secretOptionFunc(func(si *SecretInfo) {
si.UID = uid
si.GID = gid
si.Mode = mode
})
}
func ReadonlyRootFS() RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.ReadonlyRootFS = true
})
}
func WithProxy(ps ProxyEnv) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.ProxyEnv = &ps
})
}
type ExecInfo struct {
constraintsWrapper
State State
Mounts []MountInfo
ReadonlyRootFS bool
ProxyEnv *ProxyEnv
Secrets []SecretInfo
SSH []SSHInfo
}
type MountInfo struct {
Target string
Source Output
Opts []MountOption
}
type ProxyEnv struct {
HttpProxy string
HttpsProxy string
FtpProxy string
NoProxy string
}
type CacheMountSharingMode int
const (
CacheMountShared CacheMountSharingMode = iota
CacheMountPrivate
CacheMountLocked
)
const (
NetModeSandbox = pb.NetMode_UNSET
NetModeHost = pb.NetMode_HOST
NetModeNone = pb.NetMode_NONE
)

727
vendor/github.com/moby/buildkit/client/llb/fileop.go generated vendored Normal file
View File

@ -0,0 +1,727 @@
package llb
import (
_ "crypto/sha256"
"os"
"path"
"strconv"
"strings"
"time"
"github.com/moby/buildkit/solver/pb"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
// Examples:
// local := llb.Local(...)
// llb.Image().Dir("/abc").File(Mkdir("./foo").Mkfile("/abc/foo/bar", []byte("data")))
// llb.Image().File(Mkdir("/foo").Mkfile("/foo/bar", []byte("data")))
// llb.Image().File(Copy(local, "/foo", "/bar")).File(Copy(local, "/foo2", "/bar2"))
//
// a := Mkdir("./foo") // *FileAction /ced/foo
// b := Mkdir("./bar") // /abc/bar
// c := b.Copy(a.WithState(llb.Scratch().Dir("/ced")), "./foo", "./baz") // /abc/baz
// llb.Image().Dir("/abc").File(c)
//
// In future this can be extended to multiple outputs with:
// a := Mkdir("./foo")
// b, id := a.GetSelector()
// c := b.Mkdir("./bar")
// filestate = state.File(c)
// filestate.GetOutput(id).Exec()
func NewFileOp(s State, action *FileAction, c Constraints) *FileOp {
action = action.bind(s)
f := &FileOp{
action: action,
constraints: c,
}
f.output = &output{vertex: f, getIndex: func() (pb.OutputIndex, error) {
return pb.OutputIndex(0), nil
}}
return f
}
// CopyInput is either llb.State or *FileActionWithState
type CopyInput interface {
isFileOpCopyInput()
}
type subAction interface {
toProtoAction(string, pb.InputIndex) pb.IsFileAction
}
type FileAction struct {
state *State
prev *FileAction
action subAction
err error
}
func (fa *FileAction) Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction {
a := Mkdir(p, m, opt...)
a.prev = fa
return a
}
func (fa *FileAction) Mkfile(p string, m os.FileMode, dt []byte, opt ...MkfileOption) *FileAction {
a := Mkfile(p, m, dt, opt...)
a.prev = fa
return a
}
func (fa *FileAction) Rm(p string, opt ...RmOption) *FileAction {
a := Rm(p, opt...)
a.prev = fa
return a
}
func (fa *FileAction) Copy(input CopyInput, src, dest string, opt ...CopyOption) *FileAction {
a := Copy(input, src, dest, opt...)
a.prev = fa
return a
}
func (fa *FileAction) allOutputs(m map[Output]struct{}) {
if fa == nil {
return
}
if fa.state != nil && fa.state.Output() != nil {
m[fa.state.Output()] = struct{}{}
}
if a, ok := fa.action.(*fileActionCopy); ok {
if a.state != nil {
if out := a.state.Output(); out != nil {
m[out] = struct{}{}
}
} else if a.fas != nil {
a.fas.allOutputs(m)
}
}
fa.prev.allOutputs(m)
}
func (fa *FileAction) bind(s State) *FileAction {
if fa == nil {
return nil
}
fa2 := *fa
fa2.prev = fa.prev.bind(s)
fa2.state = &s
return &fa2
}
func (fa *FileAction) WithState(s State) CopyInput {
return &fileActionWithState{FileAction: fa.bind(s)}
}
type fileActionWithState struct {
*FileAction
}
func (fas *fileActionWithState) isFileOpCopyInput() {}
func Mkdir(p string, m os.FileMode, opt ...MkdirOption) *FileAction {
var mi MkdirInfo
for _, o := range opt {
o.SetMkdirOption(&mi)
}
return &FileAction{
action: &fileActionMkdir{
file: p,
mode: m,
info: mi,
},
}
}
type fileActionMkdir struct {
file string
mode os.FileMode
info MkdirInfo
}
func (a *fileActionMkdir) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
return &pb.FileAction_Mkdir{
Mkdir: &pb.FileActionMkDir{
Path: normalizePath(parent, a.file, false),
Mode: int32(a.mode & 0777),
MakeParents: a.info.MakeParents,
Owner: a.info.ChownOpt.marshal(base),
Timestamp: marshalTime(a.info.CreatedTime),
},
}
}
type MkdirOption interface {
SetMkdirOption(*MkdirInfo)
}
type ChownOption interface {
MkdirOption
MkfileOption
CopyOption
}
type mkdirOptionFunc func(*MkdirInfo)
func (fn mkdirOptionFunc) SetMkdirOption(mi *MkdirInfo) {
fn(mi)
}
var _ MkdirOption = &MkdirInfo{}
func WithParents(b bool) MkdirOption {
return mkdirOptionFunc(func(mi *MkdirInfo) {
mi.MakeParents = b
})
}
type MkdirInfo struct {
MakeParents bool
ChownOpt *ChownOpt
CreatedTime *time.Time
}
func (mi *MkdirInfo) SetMkdirOption(mi2 *MkdirInfo) {
*mi2 = *mi
}
func WithUser(name string) ChownOption {
opt := ChownOpt{}
parts := strings.SplitN(name, ":", 2)
for i, v := range parts {
switch i {
case 0:
uid, err := parseUID(v)
if err != nil {
opt.User = &UserOpt{Name: v}
} else {
opt.User = &UserOpt{UID: uid}
}
case 1:
gid, err := parseUID(v)
if err != nil {
opt.Group = &UserOpt{Name: v}
} else {
opt.Group = &UserOpt{UID: gid}
}
}
}
return opt
}
func parseUID(str string) (int, error) {
if str == "root" {
return 0, nil
}
uid, err := strconv.ParseInt(str, 10, 32)
if err != nil {
return 0, err
}
return int(uid), nil
}
func WithUIDGID(uid, gid int) ChownOption {
return ChownOpt{
User: &UserOpt{UID: uid},
Group: &UserOpt{UID: gid},
}
}
type ChownOpt struct {
User *UserOpt
Group *UserOpt
}
func (co ChownOpt) SetMkdirOption(mi *MkdirInfo) {
mi.ChownOpt = &co
}
func (co ChownOpt) SetMkfileOption(mi *MkfileInfo) {
mi.ChownOpt = &co
}
func (co ChownOpt) SetCopyOption(mi *CopyInfo) {
mi.ChownOpt = &co
}
func (cp *ChownOpt) marshal(base pb.InputIndex) *pb.ChownOpt {
if cp == nil {
return nil
}
return &pb.ChownOpt{
User: cp.User.marshal(base),
Group: cp.Group.marshal(base),
}
}
type UserOpt struct {
UID int
Name string
}
func (up *UserOpt) marshal(base pb.InputIndex) *pb.UserOpt {
if up == nil {
return nil
}
if up.Name != "" {
return &pb.UserOpt{User: &pb.UserOpt_ByName{ByName: &pb.NamedUserOpt{
Name: up.Name, Input: base}}}
}
return &pb.UserOpt{User: &pb.UserOpt_ByID{ByID: uint32(up.UID)}}
}
func Mkfile(p string, m os.FileMode, dt []byte, opts ...MkfileOption) *FileAction {
var mi MkfileInfo
for _, o := range opts {
o.SetMkfileOption(&mi)
}
return &FileAction{
action: &fileActionMkfile{
file: p,
mode: m,
dt: dt,
info: mi,
},
}
}
type MkfileOption interface {
SetMkfileOption(*MkfileInfo)
}
type MkfileInfo struct {
ChownOpt *ChownOpt
CreatedTime *time.Time
}
func (mi *MkfileInfo) SetMkfileOption(mi2 *MkfileInfo) {
*mi2 = *mi
}
var _ MkfileOption = &MkfileInfo{}
type fileActionMkfile struct {
file string
mode os.FileMode
dt []byte
info MkfileInfo
}
func (a *fileActionMkfile) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
return &pb.FileAction_Mkfile{
Mkfile: &pb.FileActionMkFile{
Path: normalizePath(parent, a.file, false),
Mode: int32(a.mode & 0777),
Data: a.dt,
Owner: a.info.ChownOpt.marshal(base),
Timestamp: marshalTime(a.info.CreatedTime),
},
}
}
func Rm(p string, opts ...RmOption) *FileAction {
var mi RmInfo
for _, o := range opts {
o.SetRmOption(&mi)
}
return &FileAction{
action: &fileActionRm{
file: p,
info: mi,
},
}
}
type RmOption interface {
SetRmOption(*RmInfo)
}
type rmOptionFunc func(*RmInfo)
func (fn rmOptionFunc) SetRmOption(mi *RmInfo) {
fn(mi)
}
type RmInfo struct {
AllowNotFound bool
AllowWildcard bool
}
func (mi *RmInfo) SetRmOption(mi2 *RmInfo) {
*mi2 = *mi
}
var _ RmOption = &RmInfo{}
func WithAllowNotFound(b bool) RmOption {
return rmOptionFunc(func(mi *RmInfo) {
mi.AllowNotFound = b
})
}
func WithAllowWildcard(b bool) RmOption {
return rmOptionFunc(func(mi *RmInfo) {
mi.AllowWildcard = b
})
}
type fileActionRm struct {
file string
info RmInfo
}
func (a *fileActionRm) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
return &pb.FileAction_Rm{
Rm: &pb.FileActionRm{
Path: normalizePath(parent, a.file, false),
AllowNotFound: a.info.AllowNotFound,
AllowWildcard: a.info.AllowWildcard,
},
}
}
func Copy(input CopyInput, src, dest string, opts ...CopyOption) *FileAction {
var state *State
var fas *fileActionWithState
var err error
if st, ok := input.(State); ok {
state = &st
} else if v, ok := input.(*fileActionWithState); ok {
fas = v
} else {
err = errors.Errorf("invalid input type %T for copy", input)
}
var mi CopyInfo
for _, o := range opts {
o.SetCopyOption(&mi)
}
return &FileAction{
action: &fileActionCopy{
state: state,
fas: fas,
src: src,
dest: dest,
info: mi,
},
err: err,
}
}
type CopyOption interface {
SetCopyOption(*CopyInfo)
}
type CopyInfo struct {
Mode *os.FileMode
FollowSymlinks bool
CopyDirContentsOnly bool
AttemptUnpack bool
CreateDestPath bool
AllowWildcard bool
AllowEmptyWildcard bool
ChownOpt *ChownOpt
CreatedTime *time.Time
}
func (mi *CopyInfo) SetCopyOption(mi2 *CopyInfo) {
*mi2 = *mi
}
var _ CopyOption = &CopyInfo{}
type fileActionCopy struct {
state *State
fas *fileActionWithState
src string
dest string
info CopyInfo
}
func (a *fileActionCopy) toProtoAction(parent string, base pb.InputIndex) pb.IsFileAction {
c := &pb.FileActionCopy{
Src: a.sourcePath(),
Dest: normalizePath(parent, a.dest, true),
Owner: a.info.ChownOpt.marshal(base),
AllowWildcard: a.info.AllowWildcard,
AllowEmptyWildcard: a.info.AllowEmptyWildcard,
FollowSymlink: a.info.FollowSymlinks,
DirCopyContents: a.info.CopyDirContentsOnly,
AttemptUnpackDockerCompatibility: a.info.AttemptUnpack,
CreateDestPath: a.info.CreateDestPath,
Timestamp: marshalTime(a.info.CreatedTime),
}
if a.info.Mode != nil {
c.Mode = int32(*a.info.Mode)
} else {
c.Mode = -1
}
return &pb.FileAction_Copy{
Copy: c,
}
}
func (c *fileActionCopy) sourcePath() string {
p := path.Clean(c.src)
if !path.IsAbs(p) {
if c.state != nil {
p = path.Join("/", c.state.GetDir(), p)
} else if c.fas != nil {
p = path.Join("/", c.fas.state.GetDir(), p)
}
}
return p
}
type CreatedTime time.Time
func WithCreatedTime(t time.Time) CreatedTime {
return CreatedTime(t)
}
func (c CreatedTime) SetMkdirOption(mi *MkdirInfo) {
mi.CreatedTime = (*time.Time)(&c)
}
func (c CreatedTime) SetMkfileOption(mi *MkfileInfo) {
mi.CreatedTime = (*time.Time)(&c)
}
func (c CreatedTime) SetCopyOption(mi *CopyInfo) {
mi.CreatedTime = (*time.Time)(&c)
}
func marshalTime(t *time.Time) int64 {
if t == nil {
return -1
}
return t.UnixNano()
}
type FileOp struct {
MarshalCache
action *FileAction
output Output
constraints Constraints
isValidated bool
}
func (f *FileOp) Validate() error {
if f.isValidated {
return nil
}
if f.action == nil {
return errors.Errorf("action is required")
}
f.isValidated = true
return nil
}
type marshalState struct {
visited map[*FileAction]*fileActionState
inputs []*pb.Input
actions []*fileActionState
}
func newMarshalState() *marshalState {
return &marshalState{
visited: map[*FileAction]*fileActionState{},
}
}
type fileActionState struct {
base pb.InputIndex
input pb.InputIndex
inputRelative *int
input2 pb.InputIndex
input2Relative *int
target int
action subAction
fa *FileAction
}
func (ms *marshalState) addInput(st *fileActionState, c *Constraints, o Output) (pb.InputIndex, error) {
inp, err := o.ToInput(c)
if err != nil {
return 0, err
}
for i, inp2 := range ms.inputs {
if *inp == *inp2 {
return pb.InputIndex(i), nil
}
}
i := pb.InputIndex(len(ms.inputs))
ms.inputs = append(ms.inputs, inp)
return i, nil
}
func (ms *marshalState) add(fa *FileAction, c *Constraints) (*fileActionState, error) {
if st, ok := ms.visited[fa]; ok {
return st, nil
}
if fa.err != nil {
return nil, fa.err
}
var prevState *fileActionState
if parent := fa.prev; parent != nil {
var err error
prevState, err = ms.add(parent, c)
if err != nil {
return nil, err
}
}
st := &fileActionState{
action: fa.action,
input: -1,
input2: -1,
base: -1,
fa: fa,
}
if source := fa.state.Output(); source != nil {
inp, err := ms.addInput(st, c, source)
if err != nil {
return nil, err
}
st.base = inp
}
if fa.prev == nil {
st.input = st.base
} else {
st.inputRelative = &prevState.target
}
if a, ok := fa.action.(*fileActionCopy); ok {
if a.state != nil {
if out := a.state.Output(); out != nil {
inp, err := ms.addInput(st, c, out)
if err != nil {
return nil, err
}
st.input2 = inp
}
} else if a.fas != nil {
src, err := ms.add(a.fas.FileAction, c)
if err != nil {
return nil, err
}
st.input2Relative = &src.target
} else {
return nil, errors.Errorf("invalid empty source for copy")
}
}
st.target = len(ms.actions)
ms.visited[fa] = st
ms.actions = append(ms.actions, st)
return st, nil
}
func (f *FileOp) Marshal(c *Constraints) (digest.Digest, []byte, *pb.OpMetadata, error) {
if f.Cached(c) {
return f.Load()
}
if err := f.Validate(); err != nil {
return "", nil, nil, err
}
addCap(&f.constraints, pb.CapFileBase)
pfo := &pb.FileOp{}
pop, md := MarshalConstraints(c, &f.constraints)
pop.Op = &pb.Op_File{
File: pfo,
}
state := newMarshalState()
_, err := state.add(f.action, c)
if err != nil {
return "", nil, nil, err
}
pop.Inputs = state.inputs
for i, st := range state.actions {
output := pb.OutputIndex(-1)
if i+1 == len(state.actions) {
output = 0
}
var parent string
if st.fa.state != nil {
parent = st.fa.state.GetDir()
}
pfo.Actions = append(pfo.Actions, &pb.FileAction{
Input: getIndex(st.input, len(state.inputs), st.inputRelative),
SecondaryInput: getIndex(st.input2, len(state.inputs), st.input2Relative),
Output: output,
Action: st.action.toProtoAction(parent, st.base),
})
}
dt, err := pop.Marshal()
if err != nil {
return "", nil, nil, err
}
f.Store(dt, md, c)
return f.Load()
}
func normalizePath(parent, p string, keepSlash bool) string {
origPath := p
p = path.Clean(p)
if !path.IsAbs(p) {
p = path.Join("/", parent, p)
}
if keepSlash {
if strings.HasSuffix(origPath, "/") && !strings.HasSuffix(p, "/") {
p += "/"
} else if strings.HasSuffix(origPath, "/.") {
if p != "/" {
p += "/"
}
p += "."
}
}
return p
}
func (f *FileOp) Output() Output {
return f.output
}
func (f *FileOp) Inputs() (inputs []Output) {
mm := map[Output]struct{}{}
f.action.allOutputs(mm)
for o := range mm {
inputs = append(inputs, o)
}
return inputs
}
func getIndex(input pb.InputIndex, len int, relative *int) pb.InputIndex {
if relative != nil {
return pb.InputIndex(len + *relative)
}
return input
}

112
vendor/github.com/moby/buildkit/client/llb/marshal.go generated vendored Normal file
View File

@ -0,0 +1,112 @@
package llb
import (
"io"
"io/ioutil"
"github.com/containerd/containerd/platforms"
"github.com/moby/buildkit/solver/pb"
digest "github.com/opencontainers/go-digest"
)
// Definition is the LLB definition structure with per-vertex metadata entries
// Corresponds to the Definition structure defined in solver/pb.Definition.
type Definition struct {
Def [][]byte
Metadata map[digest.Digest]pb.OpMetadata
}
func (def *Definition) ToPB() *pb.Definition {
md := make(map[digest.Digest]pb.OpMetadata)
for k, v := range def.Metadata {
md[k] = v
}
return &pb.Definition{
Def: def.Def,
Metadata: md,
}
}
func (def *Definition) FromPB(x *pb.Definition) {
def.Def = x.Def
def.Metadata = make(map[digest.Digest]pb.OpMetadata)
for k, v := range x.Metadata {
def.Metadata[k] = v
}
}
func WriteTo(def *Definition, w io.Writer) error {
b, err := def.ToPB().Marshal()
if err != nil {
return err
}
_, err = w.Write(b)
return err
}
func ReadFrom(r io.Reader) (*Definition, error) {
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
var pbDef pb.Definition
if err := pbDef.Unmarshal(b); err != nil {
return nil, err
}
var def Definition
def.FromPB(&pbDef)
return &def, nil
}
func MarshalConstraints(base, override *Constraints) (*pb.Op, *pb.OpMetadata) {
c := *base
c.WorkerConstraints = append([]string{}, c.WorkerConstraints...)
if p := override.Platform; p != nil {
c.Platform = p
}
for _, wc := range override.WorkerConstraints {
c.WorkerConstraints = append(c.WorkerConstraints, wc)
}
c.Metadata = mergeMetadata(c.Metadata, override.Metadata)
if c.Platform == nil {
defaultPlatform := platforms.Normalize(platforms.DefaultSpec())
c.Platform = &defaultPlatform
}
return &pb.Op{
Platform: &pb.Platform{
OS: c.Platform.OS,
Architecture: c.Platform.Architecture,
Variant: c.Platform.Variant,
OSVersion: c.Platform.OSVersion,
OSFeatures: c.Platform.OSFeatures,
},
Constraints: &pb.WorkerConstraints{
Filter: c.WorkerConstraints,
},
}, &c.Metadata
}
type MarshalCache struct {
digest digest.Digest
dt []byte
md *pb.OpMetadata
constraints *Constraints
}
func (mc *MarshalCache) Cached(c *Constraints) bool {
return mc.dt != nil && mc.constraints == c
}
func (mc *MarshalCache) Load() (digest.Digest, []byte, *pb.OpMetadata, error) {
return mc.digest, mc.dt, mc.md, nil
}
func (mc *MarshalCache) Store(dt []byte, md *pb.OpMetadata, c *Constraints) {
mc.digest = digest.FromBytes(dt)
mc.dt = dt
mc.md = md
mc.constraints = c
}

211
vendor/github.com/moby/buildkit/client/llb/meta.go generated vendored Normal file
View File

@ -0,0 +1,211 @@
package llb
import (
"fmt"
"net"
"path"
"github.com/containerd/containerd/platforms"
"github.com/google/shlex"
"github.com/moby/buildkit/solver/pb"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
type contextKeyT string
var (
keyArgs = contextKeyT("llb.exec.args")
keyDir = contextKeyT("llb.exec.dir")
keyEnv = contextKeyT("llb.exec.env")
keyUser = contextKeyT("llb.exec.user")
keyExtraHost = contextKeyT("llb.exec.extrahost")
keyPlatform = contextKeyT("llb.platform")
keyNetwork = contextKeyT("llb.network")
)
func addEnvf(key, value string, v ...interface{}) StateOption {
return func(s State) State {
return s.WithValue(keyEnv, getEnv(s).AddOrReplace(key, fmt.Sprintf(value, v...)))
}
}
func dir(str string) StateOption {
return dirf(str)
}
func dirf(str string, v ...interface{}) StateOption {
return func(s State) State {
value := fmt.Sprintf(str, v...)
if !path.IsAbs(value) {
prev := getDir(s)
if prev == "" {
prev = "/"
}
value = path.Join(prev, value)
}
return s.WithValue(keyDir, value)
}
}
func user(str string) StateOption {
return func(s State) State {
return s.WithValue(keyUser, str)
}
}
func reset(s_ State) StateOption {
return func(s State) State {
s = NewState(s.Output())
s.ctx = s_.ctx
return s
}
}
func getEnv(s State) EnvList {
v := s.Value(keyEnv)
if v != nil {
return v.(EnvList)
}
return EnvList{}
}
func getDir(s State) string {
v := s.Value(keyDir)
if v != nil {
return v.(string)
}
return ""
}
func getArgs(s State) []string {
v := s.Value(keyArgs)
if v != nil {
return v.([]string)
}
return nil
}
func getUser(s State) string {
v := s.Value(keyUser)
if v != nil {
return v.(string)
}
return ""
}
func args(args ...string) StateOption {
return func(s State) State {
return s.WithValue(keyArgs, args)
}
}
func shlexf(str string, v ...interface{}) StateOption {
return func(s State) State {
arg, err := shlex.Split(fmt.Sprintf(str, v...))
if err != nil {
// TODO: handle error
}
return args(arg...)(s)
}
}
func platform(p specs.Platform) StateOption {
return func(s State) State {
return s.WithValue(keyPlatform, platforms.Normalize(p))
}
}
func getPlatform(s State) *specs.Platform {
v := s.Value(keyPlatform)
if v != nil {
p := v.(specs.Platform)
return &p
}
return nil
}
func extraHost(host string, ip net.IP) StateOption {
return func(s State) State {
return s.WithValue(keyExtraHost, append(getExtraHosts(s), HostIP{Host: host, IP: ip}))
}
}
func getExtraHosts(s State) []HostIP {
v := s.Value(keyExtraHost)
if v != nil {
return v.([]HostIP)
}
return nil
}
type HostIP struct {
Host string
IP net.IP
}
func network(v pb.NetMode) StateOption {
return func(s State) State {
return s.WithValue(keyNetwork, v)
}
}
func getNetwork(s State) pb.NetMode {
v := s.Value(keyNetwork)
if v != nil {
n := v.(pb.NetMode)
return n
}
return NetModeSandbox
}
type EnvList []KeyValue
type KeyValue struct {
key string
value string
}
func (e EnvList) AddOrReplace(k, v string) EnvList {
e = e.Delete(k)
e = append(e, KeyValue{key: k, value: v})
return e
}
func (e EnvList) SetDefault(k, v string) EnvList {
if _, ok := e.Get(k); !ok {
e = append(e, KeyValue{key: k, value: v})
}
return e
}
func (e EnvList) Delete(k string) EnvList {
e = append([]KeyValue(nil), e...)
if i, ok := e.Index(k); ok {
return append(e[:i], e[i+1:]...)
}
return e
}
func (e EnvList) Get(k string) (string, bool) {
if index, ok := e.Index(k); ok {
return e[index].value, true
}
return "", false
}
func (e EnvList) Index(k string) (int, bool) {
for i, kv := range e {
if kv.key == k {
return i, true
}
}
return -1, false
}
func (e EnvList) ToArray() []string {
out := make([]string, 0, len(e))
for _, kv := range e {
out = append(out, kv.key+"="+kv.value)
}
return out
}

20
vendor/github.com/moby/buildkit/client/llb/resolver.go generated vendored Normal file
View File

@ -0,0 +1,20 @@
package llb
import (
"context"
gw "github.com/moby/buildkit/frontend/gateway/client"
digest "github.com/opencontainers/go-digest"
)
// WithMetaResolver adds a metadata resolver to an image
func WithMetaResolver(mr ImageMetaResolver) ImageOption {
return imageOptionFunc(func(ii *ImageInfo) {
ii.metaResolver = mr
})
}
// ImageMetaResolver can resolve image config metadata from a reference
type ImageMetaResolver interface {
ResolveImageConfig(ctx context.Context, ref string, opt gw.ResolveImageConfigOpt) (digest.Digest, []byte, error)
}

429
vendor/github.com/moby/buildkit/client/llb/source.go generated vendored Normal file
View File

@ -0,0 +1,429 @@
package llb
import (
"context"
_ "crypto/sha256"
"encoding/json"
"os"
"strconv"
"strings"
"github.com/docker/distribution/reference"
gw "github.com/moby/buildkit/frontend/gateway/client"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/apicaps"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
)
type SourceOp struct {
MarshalCache
id string
attrs map[string]string
output Output
constraints Constraints
err error
}
func NewSource(id string, attrs map[string]string, c Constraints) *SourceOp {
s := &SourceOp{
id: id,
attrs: attrs,
constraints: c,
}
s.output = &output{vertex: s, platform: c.Platform}
return s
}
func (s *SourceOp) Validate() error {
if s.err != nil {
return s.err
}
if s.id == "" {
return errors.Errorf("source identifier can't be empty")
}
return nil
}
func (s *SourceOp) Marshal(constraints *Constraints) (digest.Digest, []byte, *pb.OpMetadata, error) {
if s.Cached(constraints) {
return s.Load()
}
if err := s.Validate(); err != nil {
return "", nil, nil, err
}
if strings.HasPrefix(s.id, "local://") {
if _, hasSession := s.attrs[pb.AttrLocalSessionID]; !hasSession {
uid := s.constraints.LocalUniqueID
if uid == "" {
uid = constraints.LocalUniqueID
}
s.attrs[pb.AttrLocalUniqueID] = uid
addCap(&s.constraints, pb.CapSourceLocalUnique)
}
}
proto, md := MarshalConstraints(constraints, &s.constraints)
proto.Op = &pb.Op_Source{
Source: &pb.SourceOp{Identifier: s.id, Attrs: s.attrs},
}
if !platformSpecificSource(s.id) {
proto.Platform = nil
}
dt, err := proto.Marshal()
if err != nil {
return "", nil, nil, err
}
s.Store(dt, md, constraints)
return s.Load()
}
func (s *SourceOp) Output() Output {
return s.output
}
func (s *SourceOp) Inputs() []Output {
return nil
}
func Image(ref string, opts ...ImageOption) State {
r, err := reference.ParseNormalizedNamed(ref)
if err == nil {
ref = reference.TagNameOnly(r).String()
}
var info ImageInfo
for _, opt := range opts {
opt.SetImageOption(&info)
}
addCap(&info.Constraints, pb.CapSourceImage)
attrs := map[string]string{}
if info.resolveMode != 0 {
attrs[pb.AttrImageResolveMode] = info.resolveMode.String()
if info.resolveMode == ResolveModeForcePull {
addCap(&info.Constraints, pb.CapSourceImageResolveMode) // only require cap for security enforced mode
}
}
if info.RecordType != "" {
attrs[pb.AttrImageRecordType] = info.RecordType
}
src := NewSource("docker-image://"+ref, attrs, info.Constraints) // controversial
if err != nil {
src.err = err
}
if info.metaResolver != nil {
_, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, gw.ResolveImageConfigOpt{
Platform: info.Constraints.Platform,
ResolveMode: info.resolveMode.String(),
})
if err != nil {
src.err = err
} else {
st, err := NewState(src.Output()).WithImageConfig(dt)
if err == nil {
return st
}
src.err = err
}
}
return NewState(src.Output())
}
type ImageOption interface {
SetImageOption(*ImageInfo)
}
type imageOptionFunc func(*ImageInfo)
func (fn imageOptionFunc) SetImageOption(ii *ImageInfo) {
fn(ii)
}
var MarkImageInternal = imageOptionFunc(func(ii *ImageInfo) {
ii.RecordType = "internal"
})
type ResolveMode int
const (
ResolveModeDefault ResolveMode = iota
ResolveModeForcePull
ResolveModePreferLocal
)
func (r ResolveMode) SetImageOption(ii *ImageInfo) {
ii.resolveMode = r
}
func (r ResolveMode) String() string {
switch r {
case ResolveModeDefault:
return pb.AttrImageResolveModeDefault
case ResolveModeForcePull:
return pb.AttrImageResolveModeForcePull
case ResolveModePreferLocal:
return pb.AttrImageResolveModePreferLocal
default:
return ""
}
}
type ImageInfo struct {
constraintsWrapper
metaResolver ImageMetaResolver
resolveMode ResolveMode
RecordType string
}
func Git(remote, ref string, opts ...GitOption) State {
url := ""
for _, prefix := range []string{
"http://", "https://", "git://", "git@",
} {
if strings.HasPrefix(remote, prefix) {
url = strings.Split(remote, "#")[0]
remote = strings.TrimPrefix(remote, prefix)
}
}
id := remote
if ref != "" {
id += "#" + ref
}
gi := &GitInfo{}
for _, o := range opts {
o.SetGitOption(gi)
}
attrs := map[string]string{}
if gi.KeepGitDir {
attrs[pb.AttrKeepGitDir] = "true"
addCap(&gi.Constraints, pb.CapSourceGitKeepDir)
}
if url != "" {
attrs[pb.AttrFullRemoteURL] = url
addCap(&gi.Constraints, pb.CapSourceGitFullURL)
}
addCap(&gi.Constraints, pb.CapSourceGit)
source := NewSource("git://"+id, attrs, gi.Constraints)
return NewState(source.Output())
}
type GitOption interface {
SetGitOption(*GitInfo)
}
type gitOptionFunc func(*GitInfo)
func (fn gitOptionFunc) SetGitOption(gi *GitInfo) {
fn(gi)
}
type GitInfo struct {
constraintsWrapper
KeepGitDir bool
}
func KeepGitDir() GitOption {
return gitOptionFunc(func(gi *GitInfo) {
gi.KeepGitDir = true
})
}
func Scratch() State {
return NewState(nil)
}
func Local(name string, opts ...LocalOption) State {
gi := &LocalInfo{}
for _, o := range opts {
o.SetLocalOption(gi)
}
attrs := map[string]string{}
if gi.SessionID != "" {
attrs[pb.AttrLocalSessionID] = gi.SessionID
addCap(&gi.Constraints, pb.CapSourceLocalSessionID)
}
if gi.IncludePatterns != "" {
attrs[pb.AttrIncludePatterns] = gi.IncludePatterns
addCap(&gi.Constraints, pb.CapSourceLocalIncludePatterns)
}
if gi.FollowPaths != "" {
attrs[pb.AttrFollowPaths] = gi.FollowPaths
addCap(&gi.Constraints, pb.CapSourceLocalFollowPaths)
}
if gi.ExcludePatterns != "" {
attrs[pb.AttrExcludePatterns] = gi.ExcludePatterns
addCap(&gi.Constraints, pb.CapSourceLocalExcludePatterns)
}
if gi.SharedKeyHint != "" {
attrs[pb.AttrSharedKeyHint] = gi.SharedKeyHint
addCap(&gi.Constraints, pb.CapSourceLocalSharedKeyHint)
}
addCap(&gi.Constraints, pb.CapSourceLocal)
source := NewSource("local://"+name, attrs, gi.Constraints)
return NewState(source.Output())
}
type LocalOption interface {
SetLocalOption(*LocalInfo)
}
type localOptionFunc func(*LocalInfo)
func (fn localOptionFunc) SetLocalOption(li *LocalInfo) {
fn(li)
}
func SessionID(id string) LocalOption {
return localOptionFunc(func(li *LocalInfo) {
li.SessionID = id
})
}
func IncludePatterns(p []string) LocalOption {
return localOptionFunc(func(li *LocalInfo) {
if len(p) == 0 {
li.IncludePatterns = ""
return
}
dt, _ := json.Marshal(p) // empty on error
li.IncludePatterns = string(dt)
})
}
func FollowPaths(p []string) LocalOption {
return localOptionFunc(func(li *LocalInfo) {
if len(p) == 0 {
li.FollowPaths = ""
return
}
dt, _ := json.Marshal(p) // empty on error
li.FollowPaths = string(dt)
})
}
func ExcludePatterns(p []string) LocalOption {
return localOptionFunc(func(li *LocalInfo) {
if len(p) == 0 {
li.ExcludePatterns = ""
return
}
dt, _ := json.Marshal(p) // empty on error
li.ExcludePatterns = string(dt)
})
}
func SharedKeyHint(h string) LocalOption {
return localOptionFunc(func(li *LocalInfo) {
li.SharedKeyHint = h
})
}
type LocalInfo struct {
constraintsWrapper
SessionID string
IncludePatterns string
ExcludePatterns string
FollowPaths string
SharedKeyHint string
}
func HTTP(url string, opts ...HTTPOption) State {
hi := &HTTPInfo{}
for _, o := range opts {
o.SetHTTPOption(hi)
}
attrs := map[string]string{}
if hi.Checksum != "" {
attrs[pb.AttrHTTPChecksum] = hi.Checksum.String()
addCap(&hi.Constraints, pb.CapSourceHTTPChecksum)
}
if hi.Filename != "" {
attrs[pb.AttrHTTPFilename] = hi.Filename
}
if hi.Perm != 0 {
attrs[pb.AttrHTTPPerm] = "0" + strconv.FormatInt(int64(hi.Perm), 8)
addCap(&hi.Constraints, pb.CapSourceHTTPPerm)
}
if hi.UID != 0 {
attrs[pb.AttrHTTPUID] = strconv.Itoa(hi.UID)
addCap(&hi.Constraints, pb.CapSourceHTTPUIDGID)
}
if hi.GID != 0 {
attrs[pb.AttrHTTPGID] = strconv.Itoa(hi.GID)
addCap(&hi.Constraints, pb.CapSourceHTTPUIDGID)
}
addCap(&hi.Constraints, pb.CapSourceHTTP)
source := NewSource(url, attrs, hi.Constraints)
return NewState(source.Output())
}
type HTTPInfo struct {
constraintsWrapper
Checksum digest.Digest
Filename string
Perm int
UID int
GID int
}
type HTTPOption interface {
SetHTTPOption(*HTTPInfo)
}
type httpOptionFunc func(*HTTPInfo)
func (fn httpOptionFunc) SetHTTPOption(hi *HTTPInfo) {
fn(hi)
}
func Checksum(dgst digest.Digest) HTTPOption {
return httpOptionFunc(func(hi *HTTPInfo) {
hi.Checksum = dgst
})
}
func Chmod(perm os.FileMode) HTTPOption {
return httpOptionFunc(func(hi *HTTPInfo) {
hi.Perm = int(perm) & 0777
})
}
func Filename(name string) HTTPOption {
return httpOptionFunc(func(hi *HTTPInfo) {
hi.Filename = name
})
}
func Chown(uid, gid int) HTTPOption {
return httpOptionFunc(func(hi *HTTPInfo) {
hi.UID = uid
hi.GID = gid
})
}
func platformSpecificSource(id string) bool {
return strings.HasPrefix(id, "docker-image://")
}
func addCap(c *Constraints, id apicaps.CapID) {
if c.Metadata.Caps == nil {
c.Metadata.Caps = make(map[apicaps.CapID]bool)
}
c.Metadata.Caps[id] = true
}

507
vendor/github.com/moby/buildkit/client/llb/state.go generated vendored Normal file
View File

@ -0,0 +1,507 @@
package llb
import (
"context"
"encoding/json"
"fmt"
"net"
"strings"
"github.com/containerd/containerd/platforms"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/apicaps"
digest "github.com/opencontainers/go-digest"
specs "github.com/opencontainers/image-spec/specs-go/v1"
)
type StateOption func(State) State
type Output interface {
ToInput(*Constraints) (*pb.Input, error)
Vertex() Vertex
}
type Vertex interface {
Validate() error
Marshal(*Constraints) (digest.Digest, []byte, *pb.OpMetadata, error)
Output() Output
Inputs() []Output
}
func NewState(o Output) State {
s := State{
out: o,
ctx: context.Background(),
}
s = dir("/")(s)
s = s.ensurePlatform()
return s
}
type State struct {
out Output
ctx context.Context
opts []ConstraintsOpt
}
func (s State) ensurePlatform() State {
if o, ok := s.out.(interface {
Platform() *specs.Platform
}); ok {
if p := o.Platform(); p != nil {
s = platform(*p)(s)
}
}
return s
}
func (s State) WithValue(k, v interface{}) State {
return State{
out: s.out,
ctx: context.WithValue(s.ctx, k, v),
}
}
func (s State) Value(k interface{}) interface{} {
return s.ctx.Value(k)
}
func (s State) SetMarshalDefaults(co ...ConstraintsOpt) State {
s.opts = co
return s
}
func (s State) Marshal(co ...ConstraintsOpt) (*Definition, error) {
def := &Definition{
Metadata: make(map[digest.Digest]pb.OpMetadata, 0),
}
if s.Output() == nil {
return def, nil
}
defaultPlatform := platforms.Normalize(platforms.DefaultSpec())
c := &Constraints{
Platform: &defaultPlatform,
LocalUniqueID: identity.NewID(),
}
for _, o := range append(s.opts, co...) {
o.SetConstraintsOption(c)
}
def, err := marshal(s.Output().Vertex(), def, map[digest.Digest]struct{}{}, map[Vertex]struct{}{}, c)
if err != nil {
return def, err
}
inp, err := s.Output().ToInput(c)
if err != nil {
return def, err
}
proto := &pb.Op{Inputs: []*pb.Input{inp}}
dt, err := proto.Marshal()
if err != nil {
return def, err
}
def.Def = append(def.Def, dt)
dgst := digest.FromBytes(dt)
md := def.Metadata[dgst]
md.Caps = map[apicaps.CapID]bool{
pb.CapConstraints: true,
pb.CapPlatform: true,
}
for _, m := range def.Metadata {
if m.IgnoreCache {
md.Caps[pb.CapMetaIgnoreCache] = true
}
if m.Description != nil {
md.Caps[pb.CapMetaDescription] = true
}
if m.ExportCache != nil {
md.Caps[pb.CapMetaExportCache] = true
}
}
def.Metadata[dgst] = md
return def, nil
}
func marshal(v Vertex, def *Definition, cache map[digest.Digest]struct{}, vertexCache map[Vertex]struct{}, c *Constraints) (*Definition, error) {
if _, ok := vertexCache[v]; ok {
return def, nil
}
for _, inp := range v.Inputs() {
var err error
def, err = marshal(inp.Vertex(), def, cache, vertexCache, c)
if err != nil {
return def, err
}
}
dgst, dt, opMeta, err := v.Marshal(c)
if err != nil {
return def, err
}
vertexCache[v] = struct{}{}
if opMeta != nil {
def.Metadata[dgst] = mergeMetadata(def.Metadata[dgst], *opMeta)
}
if _, ok := cache[dgst]; ok {
return def, nil
}
def.Def = append(def.Def, dt)
cache[dgst] = struct{}{}
return def, nil
}
func (s State) Validate() error {
return s.Output().Vertex().Validate()
}
func (s State) Output() Output {
return s.out
}
func (s State) WithOutput(o Output) State {
s = State{
out: o,
ctx: s.ctx,
}
s = s.ensurePlatform()
return s
}
func (s State) WithImageConfig(c []byte) (State, error) {
var img struct {
Config struct {
Env []string `json:"Env,omitempty"`
WorkingDir string `json:"WorkingDir,omitempty"`
User string `json:"User,omitempty"`
} `json:"config,omitempty"`
}
if err := json.Unmarshal(c, &img); err != nil {
return State{}, err
}
for _, env := range img.Config.Env {
parts := strings.SplitN(env, "=", 2)
if len(parts[0]) > 0 {
var v string
if len(parts) > 1 {
v = parts[1]
}
s = s.AddEnv(parts[0], v)
}
}
s = s.Dir(img.Config.WorkingDir)
return s, nil
}
func (s State) Run(ro ...RunOption) ExecState {
ei := &ExecInfo{State: s}
if p := s.GetPlatform(); p != nil {
ei.Constraints.Platform = p
}
for _, o := range ro {
o.SetRunOption(ei)
}
meta := Meta{
Args: getArgs(ei.State),
Cwd: getDir(ei.State),
Env: getEnv(ei.State),
User: getUser(ei.State),
ProxyEnv: ei.ProxyEnv,
ExtraHosts: getExtraHosts(ei.State),
Network: getNetwork(ei.State),
}
exec := NewExecOp(s.Output(), meta, ei.ReadonlyRootFS, ei.Constraints)
for _, m := range ei.Mounts {
exec.AddMount(m.Target, m.Source, m.Opts...)
}
exec.secrets = ei.Secrets
exec.ssh = ei.SSH
return ExecState{
State: s.WithOutput(exec.Output()),
exec: exec,
}
}
func (s State) File(a *FileAction, opts ...ConstraintsOpt) State {
var c Constraints
for _, o := range opts {
o.SetConstraintsOption(&c)
}
return s.WithOutput(NewFileOp(s, a, c).Output())
}
func (s State) AddEnv(key, value string) State {
return s.AddEnvf(key, value)
}
func (s State) AddEnvf(key, value string, v ...interface{}) State {
return addEnvf(key, value, v...)(s)
}
func (s State) Dir(str string) State {
return s.Dirf(str)
}
func (s State) Dirf(str string, v ...interface{}) State {
return dirf(str, v...)(s)
}
func (s State) GetEnv(key string) (string, bool) {
return getEnv(s).Get(key)
}
func (s State) Env() []string {
return getEnv(s).ToArray()
}
func (s State) GetDir() string {
return getDir(s)
}
func (s State) GetArgs() []string {
return getArgs(s)
}
func (s State) Reset(s2 State) State {
return reset(s2)(s)
}
func (s State) User(v string) State {
return user(v)(s)
}
func (s State) Platform(p specs.Platform) State {
return platform(p)(s)
}
func (s State) GetPlatform() *specs.Platform {
return getPlatform(s)
}
func (s State) Network(n pb.NetMode) State {
return network(n)(s)
}
func (s State) GetNetwork() pb.NetMode {
return getNetwork(s)
}
func (s State) With(so ...StateOption) State {
for _, o := range so {
s = o(s)
}
return s
}
func (s State) AddExtraHost(host string, ip net.IP) State {
return extraHost(host, ip)(s)
}
func (s State) isFileOpCopyInput() {}
type output struct {
vertex Vertex
getIndex func() (pb.OutputIndex, error)
err error
platform *specs.Platform
}
func (o *output) ToInput(c *Constraints) (*pb.Input, error) {
if o.err != nil {
return nil, o.err
}
var index pb.OutputIndex
if o.getIndex != nil {
var err error
index, err = o.getIndex()
if err != nil {
return nil, err
}
}
dgst, _, _, err := o.vertex.Marshal(c)
if err != nil {
return nil, err
}
return &pb.Input{Digest: dgst, Index: index}, nil
}
func (o *output) Vertex() Vertex {
return o.vertex
}
func (o *output) Platform() *specs.Platform {
return o.platform
}
type ConstraintsOpt interface {
SetConstraintsOption(*Constraints)
RunOption
LocalOption
HTTPOption
ImageOption
GitOption
}
type constraintsOptFunc func(m *Constraints)
func (fn constraintsOptFunc) SetConstraintsOption(m *Constraints) {
fn(m)
}
func (fn constraintsOptFunc) SetRunOption(ei *ExecInfo) {
ei.applyConstraints(fn)
}
func (fn constraintsOptFunc) SetLocalOption(li *LocalInfo) {
li.applyConstraints(fn)
}
func (fn constraintsOptFunc) SetHTTPOption(hi *HTTPInfo) {
hi.applyConstraints(fn)
}
func (fn constraintsOptFunc) SetImageOption(ii *ImageInfo) {
ii.applyConstraints(fn)
}
func (fn constraintsOptFunc) SetGitOption(gi *GitInfo) {
gi.applyConstraints(fn)
}
func mergeMetadata(m1, m2 pb.OpMetadata) pb.OpMetadata {
if m2.IgnoreCache {
m1.IgnoreCache = true
}
if len(m2.Description) > 0 {
if m1.Description == nil {
m1.Description = make(map[string]string)
}
for k, v := range m2.Description {
m1.Description[k] = v
}
}
if m2.ExportCache != nil {
m1.ExportCache = m2.ExportCache
}
for k := range m2.Caps {
if m1.Caps == nil {
m1.Caps = make(map[apicaps.CapID]bool, len(m2.Caps))
}
m1.Caps[k] = true
}
return m1
}
var IgnoreCache = constraintsOptFunc(func(c *Constraints) {
c.Metadata.IgnoreCache = true
})
func WithDescription(m map[string]string) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
if c.Metadata.Description == nil {
c.Metadata.Description = map[string]string{}
}
for k, v := range m {
c.Metadata.Description[k] = v
}
})
}
func WithCustomName(name string) ConstraintsOpt {
return WithDescription(map[string]string{
"llb.customname": name,
})
}
func WithCustomNamef(name string, a ...interface{}) ConstraintsOpt {
return WithCustomName(fmt.Sprintf(name, a...))
}
// WithExportCache forces results for this vertex to be exported with the cache
func WithExportCache() ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
c.Metadata.ExportCache = &pb.ExportCache{Value: true}
})
}
// WithoutExportCache sets results for this vertex to be not exported with
// the cache
func WithoutExportCache() ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
// ExportCache with value false means to disable exporting
c.Metadata.ExportCache = &pb.ExportCache{Value: false}
})
}
// WithoutDefaultExportCache resets the cache export for the vertex to use
// the default defined by the build configuration.
func WithoutDefaultExportCache() ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
// nil means no vertex based config has been set
c.Metadata.ExportCache = nil
})
}
// WithCaps exposes supported LLB caps to the marshaler
func WithCaps(caps apicaps.CapSet) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
c.Caps = &caps
})
}
type constraintsWrapper struct {
Constraints
}
func (cw *constraintsWrapper) applyConstraints(f func(c *Constraints)) {
f(&cw.Constraints)
}
type Constraints struct {
Platform *specs.Platform
WorkerConstraints []string
Metadata pb.OpMetadata
LocalUniqueID string
Caps *apicaps.CapSet
}
func Platform(p specs.Platform) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
c.Platform = &p
})
}
func LocalUniqueID(v string) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
c.LocalUniqueID = v
})
}
var (
LinuxAmd64 = Platform(specs.Platform{OS: "linux", Architecture: "amd64"})
LinuxArmhf = Platform(specs.Platform{OS: "linux", Architecture: "arm", Variant: "v7"})
LinuxArm = LinuxArmhf
LinuxArmel = Platform(specs.Platform{OS: "linux", Architecture: "arm", Variant: "v6"})
LinuxArm64 = Platform(specs.Platform{OS: "linux", Architecture: "arm64"})
LinuxS390x = Platform(specs.Platform{OS: "linux", Architecture: "s390x"})
LinuxPpc64le = Platform(specs.Platform{OS: "linux", Architecture: "ppc64le"})
Darwin = Platform(specs.Platform{OS: "darwin", Architecture: "amd64"})
Windows = Platform(specs.Platform{OS: "windows", Architecture: "amd64"})
)
func Require(filters ...string) ConstraintsOpt {
return constraintsOptFunc(func(c *Constraints) {
for _, f := range filters {
c.WorkerConstraints = append(c.WorkerConstraints, f)
}
})
}

View File

@ -0,0 +1,113 @@
package ociindex
import (
"encoding/json"
"io/ioutil"
"os"
"github.com/gofrs/flock"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
const (
// IndexJSONLockFileSuffix is the suffix of the lock file
IndexJSONLockFileSuffix = ".lock"
)
// PutDescToIndex puts desc to index with tag.
// Existing manifests with the same tag will be removed from the index.
func PutDescToIndex(index *v1.Index, desc v1.Descriptor, tag string) error {
if index == nil {
index = &v1.Index{}
}
if index.SchemaVersion == 0 {
index.SchemaVersion = 2
}
if tag != "" {
if desc.Annotations == nil {
desc.Annotations = make(map[string]string)
}
desc.Annotations[v1.AnnotationRefName] = tag
// remove existing manifests with the same tag
var manifests []v1.Descriptor
for _, m := range index.Manifests {
if m.Annotations[v1.AnnotationRefName] != tag {
manifests = append(manifests, m)
}
}
index.Manifests = manifests
}
index.Manifests = append(index.Manifests, desc)
return nil
}
func PutDescToIndexJSONFileLocked(indexJSONPath string, desc v1.Descriptor, tag string) error {
lockPath := indexJSONPath + IndexJSONLockFileSuffix
lock := flock.New(lockPath)
locked, err := lock.TryLock()
if err != nil {
return errors.Wrapf(err, "could not lock %s", lockPath)
}
if !locked {
return errors.Errorf("could not lock %s", lockPath)
}
defer func() {
lock.Unlock()
os.RemoveAll(lockPath)
}()
f, err := os.OpenFile(indexJSONPath, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return errors.Wrapf(err, "could not open %s", indexJSONPath)
}
defer f.Close()
var idx v1.Index
b, err := ioutil.ReadAll(f)
if err != nil {
return errors.Wrapf(err, "could not read %s", indexJSONPath)
}
if len(b) > 0 {
if err := json.Unmarshal(b, &idx); err != nil {
return errors.Wrapf(err, "could not unmarshal %s (%q)", indexJSONPath, string(b))
}
}
if err = PutDescToIndex(&idx, desc, tag); err != nil {
return err
}
b, err = json.Marshal(idx)
if err != nil {
return err
}
if _, err = f.WriteAt(b, 0); err != nil {
return err
}
if err = f.Truncate(int64(len(b))); err != nil {
return err
}
return nil
}
func ReadIndexJSONFileLocked(indexJSONPath string) (*v1.Index, error) {
lockPath := indexJSONPath + IndexJSONLockFileSuffix
lock := flock.New(lockPath)
locked, err := lock.TryRLock()
if err != nil {
return nil, errors.Wrapf(err, "could not lock %s", lockPath)
}
if !locked {
return nil, errors.Errorf("could not lock %s", lockPath)
}
defer func() {
lock.Unlock()
os.RemoveAll(lockPath)
}()
b, err := ioutil.ReadFile(indexJSONPath)
if err != nil {
return nil, errors.Wrapf(err, "could not read %s", indexJSONPath)
}
var idx v1.Index
if err := json.Unmarshal(b, &idx); err != nil {
return nil, errors.Wrapf(err, "could not unmarshal %s (%q)", indexJSONPath, string(b))
}
return &idx, nil
}

83
vendor/github.com/moby/buildkit/client/prune.go generated vendored Normal file
View File

@ -0,0 +1,83 @@
package client
import (
"context"
"io"
"time"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/pkg/errors"
)
func (c *Client) Prune(ctx context.Context, ch chan UsageInfo, opts ...PruneOption) error {
info := &PruneInfo{}
for _, o := range opts {
o.SetPruneOption(info)
}
req := &controlapi.PruneRequest{
Filter: info.Filter,
KeepDuration: int64(info.KeepDuration),
KeepBytes: int64(info.KeepBytes),
}
if info.All {
req.All = true
}
cl, err := c.controlClient().Prune(ctx, req)
if err != nil {
return errors.Wrap(err, "failed to call prune")
}
for {
d, err := cl.Recv()
if err != nil {
if err == io.EOF {
return nil
}
return err
}
if ch != nil {
ch <- UsageInfo{
ID: d.ID,
Mutable: d.Mutable,
InUse: d.InUse,
Size: d.Size_,
Parent: d.Parent,
CreatedAt: d.CreatedAt,
Description: d.Description,
UsageCount: int(d.UsageCount),
LastUsedAt: d.LastUsedAt,
RecordType: UsageRecordType(d.RecordType),
Shared: d.Shared,
}
}
}
}
type PruneOption interface {
SetPruneOption(*PruneInfo)
}
type PruneInfo struct {
Filter []string
All bool
KeepDuration time.Duration
KeepBytes int64
}
type pruneOptionFunc func(*PruneInfo)
func (f pruneOptionFunc) SetPruneOption(pi *PruneInfo) {
f(pi)
}
var PruneAll = pruneOptionFunc(func(pi *PruneInfo) {
pi.All = true
})
func WithKeepOpt(duration time.Duration, bytes int64) PruneOption {
return pruneOptionFunc(func(pi *PruneInfo) {
pi.KeepDuration = duration
pi.KeepBytes = bytes
})
}

464
vendor/github.com/moby/buildkit/client/solve.go generated vendored Normal file
View File

@ -0,0 +1,464 @@
package client
import (
"context"
"encoding/json"
"io"
"os"
"path/filepath"
"strings"
"time"
"github.com/containerd/containerd/content"
contentlocal "github.com/containerd/containerd/content/local"
controlapi "github.com/moby/buildkit/api/services/control"
"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/client/ociindex"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/session"
sessioncontent "github.com/moby/buildkit/session/content"
"github.com/moby/buildkit/session/filesync"
"github.com/moby/buildkit/session/grpchijack"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/util/entitlements"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
opentracing "github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
fstypes "github.com/tonistiigi/fsutil/types"
"golang.org/x/sync/errgroup"
)
type SolveOpt struct {
Exports []ExportEntry
LocalDirs map[string]string
SharedKey string
Frontend string
FrontendAttrs map[string]string
CacheExports []CacheOptionsEntry
CacheImports []CacheOptionsEntry
Session []session.Attachable
AllowedEntitlements []entitlements.Entitlement
}
type ExportEntry struct {
Type string
Attrs map[string]string
Output io.WriteCloser // for ExporterOCI and ExporterDocker
OutputDir string // for ExporterLocal
}
type CacheOptionsEntry struct {
Type string
Attrs map[string]string
}
// Solve calls Solve on the controller.
// def must be nil if (and only if) opt.Frontend is set.
func (c *Client) Solve(ctx context.Context, def *llb.Definition, opt SolveOpt, statusChan chan *SolveStatus) (*SolveResponse, error) {
defer func() {
if statusChan != nil {
close(statusChan)
}
}()
if opt.Frontend == "" && def == nil {
return nil, errors.New("invalid empty definition")
}
if opt.Frontend != "" && def != nil {
return nil, errors.Errorf("invalid definition for frontend %s", opt.Frontend)
}
return c.solve(ctx, def, nil, opt, statusChan)
}
type runGatewayCB func(ref string, s *session.Session) error
func (c *Client) solve(ctx context.Context, def *llb.Definition, runGateway runGatewayCB, opt SolveOpt, statusChan chan *SolveStatus) (*SolveResponse, error) {
if def != nil && runGateway != nil {
return nil, errors.New("invalid with def and cb")
}
syncedDirs, err := prepareSyncedDirs(def, opt.LocalDirs)
if err != nil {
return nil, err
}
ref := identity.NewID()
eg, ctx := errgroup.WithContext(ctx)
statusContext, cancelStatus := context.WithCancel(context.Background())
defer cancelStatus()
if span := opentracing.SpanFromContext(ctx); span != nil {
statusContext = opentracing.ContextWithSpan(statusContext, span)
}
s, err := session.NewSession(statusContext, defaultSessionName(), opt.SharedKey)
if err != nil {
return nil, errors.Wrap(err, "failed to create session")
}
if len(syncedDirs) > 0 {
s.Allow(filesync.NewFSSyncProvider(syncedDirs))
}
for _, a := range opt.Session {
s.Allow(a)
}
var ex ExportEntry
if len(opt.Exports) > 1 {
return nil, errors.New("currently only single Exports can be specified")
}
if len(opt.Exports) == 1 {
ex = opt.Exports[0]
}
switch ex.Type {
case ExporterLocal:
if ex.Output != nil {
return nil, errors.New("output file writer is not supported by local exporter")
}
if ex.OutputDir == "" {
return nil, errors.New("output directory is required for local exporter")
}
s.Allow(filesync.NewFSSyncTargetDir(ex.OutputDir))
case ExporterOCI, ExporterDocker:
if ex.OutputDir != "" {
return nil, errors.Errorf("output directory %s is not supported by %s exporter", ex.OutputDir, ex.Type)
}
if ex.Output == nil {
return nil, errors.Errorf("output file writer is required for %s exporter", ex.Type)
}
s.Allow(filesync.NewFSSyncTarget(ex.Output))
default:
if ex.Output != nil {
return nil, errors.Errorf("output file writer is not supported by %s exporter", ex.Type)
}
if ex.OutputDir != "" {
return nil, errors.Errorf("output directory %s is not supported by %s exporter", ex.OutputDir, ex.Type)
}
}
cacheOpt, err := parseCacheOptions(opt)
if err != nil {
return nil, err
}
if len(cacheOpt.contentStores) > 0 {
s.Allow(sessioncontent.NewAttachable(cacheOpt.contentStores))
}
for k, v := range cacheOpt.frontendAttrs {
opt.FrontendAttrs[k] = v
}
eg.Go(func() error {
return s.Run(statusContext, grpchijack.Dialer(c.controlClient()))
})
solveCtx, cancelSolve := context.WithCancel(ctx)
var res *SolveResponse
eg.Go(func() error {
ctx := solveCtx
defer cancelSolve()
defer func() { // make sure the Status ends cleanly on build errors
go func() {
<-time.After(3 * time.Second)
cancelStatus()
}()
logrus.Debugf("stopping session")
s.Close()
}()
var pbd *pb.Definition
if def != nil {
pbd = def.ToPB()
}
resp, err := c.controlClient().Solve(ctx, &controlapi.SolveRequest{
Ref: ref,
Definition: pbd,
Exporter: ex.Type,
ExporterAttrs: ex.Attrs,
Session: s.ID(),
Frontend: opt.Frontend,
FrontendAttrs: opt.FrontendAttrs,
Cache: cacheOpt.options,
Entitlements: opt.AllowedEntitlements,
})
if err != nil {
return errors.Wrap(err, "failed to solve")
}
res = &SolveResponse{
ExporterResponse: resp.ExporterResponse,
}
return nil
})
if runGateway != nil {
eg.Go(func() error {
err := runGateway(ref, s)
if err == nil {
return nil
}
// If the callback failed then the main
// `Solve` (called above) should error as
// well. However as a fallback we wait up to
// 5s for that to happen before failing this
// goroutine.
select {
case <-solveCtx.Done():
case <-time.After(5 * time.Second):
cancelSolve()
}
return err
})
}
eg.Go(func() error {
stream, err := c.controlClient().Status(statusContext, &controlapi.StatusRequest{
Ref: ref,
})
if err != nil {
return errors.Wrap(err, "failed to get status")
}
for {
resp, err := stream.Recv()
if err != nil {
if err == io.EOF {
return nil
}
return errors.Wrap(err, "failed to receive status")
}
s := SolveStatus{}
for _, v := range resp.Vertexes {
s.Vertexes = append(s.Vertexes, &Vertex{
Digest: v.Digest,
Inputs: v.Inputs,
Name: v.Name,
Started: v.Started,
Completed: v.Completed,
Error: v.Error,
Cached: v.Cached,
})
}
for _, v := range resp.Statuses {
s.Statuses = append(s.Statuses, &VertexStatus{
ID: v.ID,
Vertex: v.Vertex,
Name: v.Name,
Total: v.Total,
Current: v.Current,
Timestamp: v.Timestamp,
Started: v.Started,
Completed: v.Completed,
})
}
for _, v := range resp.Logs {
s.Logs = append(s.Logs, &VertexLog{
Vertex: v.Vertex,
Stream: int(v.Stream),
Data: v.Msg,
Timestamp: v.Timestamp,
})
}
if statusChan != nil {
statusChan <- &s
}
}
})
if err := eg.Wait(); err != nil {
return nil, err
}
// Update index.json of exported cache content store
// FIXME(AkihiroSuda): dedupe const definition of cache/remotecache.ExporterResponseManifestDesc = "cache.manifest"
if manifestDescJSON := res.ExporterResponse["cache.manifest"]; manifestDescJSON != "" {
var manifestDesc ocispec.Descriptor
if err = json.Unmarshal([]byte(manifestDescJSON), &manifestDesc); err != nil {
return nil, err
}
for indexJSONPath, tag := range cacheOpt.indicesToUpdate {
if err = ociindex.PutDescToIndexJSONFileLocked(indexJSONPath, manifestDesc, tag); err != nil {
return nil, err
}
}
}
return res, nil
}
func prepareSyncedDirs(def *llb.Definition, localDirs map[string]string) ([]filesync.SyncedDir, error) {
for _, d := range localDirs {
fi, err := os.Stat(d)
if err != nil {
return nil, errors.Wrapf(err, "could not find %s", d)
}
if !fi.IsDir() {
return nil, errors.Errorf("%s not a directory", d)
}
}
resetUIDAndGID := func(p string, st *fstypes.Stat) bool {
st.Uid = 0
st.Gid = 0
return true
}
dirs := make([]filesync.SyncedDir, 0, len(localDirs))
if def == nil {
for name, d := range localDirs {
dirs = append(dirs, filesync.SyncedDir{Name: name, Dir: d, Map: resetUIDAndGID})
}
} else {
for _, dt := range def.Def {
var op pb.Op
if err := (&op).Unmarshal(dt); err != nil {
return nil, errors.Wrap(err, "failed to parse llb proto op")
}
if src := op.GetSource(); src != nil {
if strings.HasPrefix(src.Identifier, "local://") { // TODO: just make a type property
name := strings.TrimPrefix(src.Identifier, "local://")
d, ok := localDirs[name]
if !ok {
return nil, errors.Errorf("local directory %s not enabled", name)
}
dirs = append(dirs, filesync.SyncedDir{Name: name, Dir: d, Map: resetUIDAndGID}) // TODO: excludes
}
}
}
}
return dirs, nil
}
func defaultSessionName() string {
wd, err := os.Getwd()
if err != nil {
return "unknown"
}
return filepath.Base(wd)
}
type cacheOptions struct {
options controlapi.CacheOptions
contentStores map[string]content.Store // key: ID of content store ("local:" + csDir)
indicesToUpdate map[string]string // key: index.JSON file name, value: tag
frontendAttrs map[string]string
}
func parseCacheOptions(opt SolveOpt) (*cacheOptions, error) {
var (
cacheExports []*controlapi.CacheOptionsEntry
cacheImports []*controlapi.CacheOptionsEntry
// legacy API is used for registry caches, because the daemon might not support the new API
legacyExportRef string
legacyImportRefs []string
)
contentStores := make(map[string]content.Store)
indicesToUpdate := make(map[string]string) // key: index.JSON file name, value: tag
frontendAttrs := make(map[string]string)
legacyExportAttrs := make(map[string]string)
for _, ex := range opt.CacheExports {
if ex.Type == "local" {
csDir := ex.Attrs["dest"]
if csDir == "" {
return nil, errors.New("local cache exporter requires dest")
}
if err := os.MkdirAll(csDir, 0755); err != nil {
return nil, err
}
cs, err := contentlocal.NewStore(csDir)
if err != nil {
return nil, err
}
contentStores["local:"+csDir] = cs
// TODO(AkihiroSuda): support custom index JSON path and tag
indexJSONPath := filepath.Join(csDir, "index.json")
indicesToUpdate[indexJSONPath] = "latest"
}
if ex.Type == "registry" && legacyExportRef == "" {
legacyExportRef = ex.Attrs["ref"]
for k, v := range ex.Attrs {
if k != "ref" {
legacyExportAttrs[k] = v
}
}
} else {
cacheExports = append(cacheExports, &controlapi.CacheOptionsEntry{
Type: ex.Type,
Attrs: ex.Attrs,
})
}
}
for _, im := range opt.CacheImports {
attrs := im.Attrs
if im.Type == "local" {
csDir := im.Attrs["src"]
if csDir == "" {
return nil, errors.New("local cache importer requires src")
}
if err := os.MkdirAll(csDir, 0755); err != nil {
return nil, err
}
cs, err := contentlocal.NewStore(csDir)
if err != nil {
return nil, err
}
contentStores["local:"+csDir] = cs
// if digest is not specified, load from "latest" tag
if attrs["digest"] == "" {
idx, err := ociindex.ReadIndexJSONFileLocked(filepath.Join(csDir, "index.json"))
if err != nil {
return nil, err
}
for _, m := range idx.Manifests {
if m.Annotations[ocispec.AnnotationRefName] == "latest" {
attrs["digest"] = string(m.Digest)
break
}
}
if attrs["digest"] == "" {
return nil, errors.New("local cache importer requires either explicit digest or \"latest\" tag on index.json")
}
}
}
if im.Type == "registry" {
legacyImportRef := attrs["ref"]
legacyImportRefs = append(legacyImportRefs, legacyImportRef)
} else {
cacheImports = append(cacheImports, &controlapi.CacheOptionsEntry{
Type: im.Type,
Attrs: attrs,
})
}
}
if opt.Frontend != "" {
// use legacy API for registry importers, because the frontend might not support the new API
if len(legacyImportRefs) > 0 {
frontendAttrs["cache-from"] = strings.Join(legacyImportRefs, ",")
}
// use new API for other importers
if len(cacheImports) > 0 {
s, err := json.Marshal(cacheImports)
if err != nil {
return nil, err
}
frontendAttrs["cache-imports"] = string(s)
}
}
res := cacheOptions{
options: controlapi.CacheOptions{
// old API (for registry caches, planned to be removed in early 2019)
ExportRefDeprecated: legacyExportRef,
ExportAttrsDeprecated: legacyExportAttrs,
ImportRefsDeprecated: legacyImportRefs,
// new API
Exports: cacheExports,
Imports: cacheImports,
},
contentStores: contentStores,
indicesToUpdate: indicesToUpdate,
frontendAttrs: frontendAttrs,
}
return &res, nil
}

70
vendor/github.com/moby/buildkit/client/workers.go generated vendored Normal file
View File

@ -0,0 +1,70 @@
package client
import (
"context"
"time"
controlapi "github.com/moby/buildkit/api/services/control"
apitypes "github.com/moby/buildkit/api/types"
"github.com/moby/buildkit/solver/pb"
specs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)
// WorkerInfo contains information about a worker
type WorkerInfo struct {
ID string
Labels map[string]string
Platforms []specs.Platform
GCPolicy []PruneInfo
}
// ListWorkers lists all active workers
func (c *Client) ListWorkers(ctx context.Context, opts ...ListWorkersOption) ([]*WorkerInfo, error) {
info := &ListWorkersInfo{}
for _, o := range opts {
o.SetListWorkersOption(info)
}
req := &controlapi.ListWorkersRequest{Filter: info.Filter}
resp, err := c.controlClient().ListWorkers(ctx, req)
if err != nil {
return nil, errors.Wrap(err, "failed to list workers")
}
var wi []*WorkerInfo
for _, w := range resp.Record {
wi = append(wi, &WorkerInfo{
ID: w.ID,
Labels: w.Labels,
Platforms: pb.ToSpecPlatforms(w.Platforms),
GCPolicy: fromAPIGCPolicy(w.GCPolicy),
})
}
return wi, nil
}
// ListWorkersOption is an option for a worker list query
type ListWorkersOption interface {
SetListWorkersOption(*ListWorkersInfo)
}
// ListWorkersInfo is a payload for worker list query
type ListWorkersInfo struct {
Filter []string
}
func fromAPIGCPolicy(in []*apitypes.GCPolicy) []PruneInfo {
out := make([]PruneInfo, 0, len(in))
for _, p := range in {
out = append(out, PruneInfo{
All: p.All,
Filter: p.Filters,
KeepDuration: time.Duration(p.KeepDuration),
KeepBytes: p.KeepBytes,
})
}
return out
}