build: buildkit now honors daemon's DNS config

Signed-off-by: Tibor Vass <tibor@docker.com>
This commit is contained in:
Tibor Vass 2019-06-06 01:36:33 +00:00
parent f550cb5792
commit a1cdd4bfcc
41 changed files with 330 additions and 160 deletions

View file

@ -75,6 +75,7 @@ type Opt struct {
BuilderConfig config.BuilderConfig
Rootless bool
IdentityMapping *idtools.IdentityMapping
DNSConfig config.DNSConfig
}
// Builder can build using BuildKit backend

View file

@ -113,7 +113,9 @@ func newController(rt http.RoundTripper, opt Opt) (*control.Controller, error) {
return nil, err
}
exec, err := newExecutor(root, opt.DefaultCgroupParent, opt.NetworkController, opt.Rootless, opt.IdentityMapping)
dns := getDNSConfig(opt.DNSConfig)
exec, err := newExecutor(root, opt.DefaultCgroupParent, opt.NetworkController, dns, opt.Rootless, opt.IdentityMapping)
if err != nil {
return nil, err
}

View file

@ -8,9 +8,11 @@ import (
"strconv"
"sync"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/libnetwork"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
"github.com/moby/buildkit/executor/runcexecutor"
"github.com/moby/buildkit/identity"
"github.com/moby/buildkit/solver/pb"
@ -21,7 +23,7 @@ import (
const networkName = "bridge"
func newExecutor(root, cgroupParent string, net libnetwork.NetworkController, rootless bool, idmap *idtools.IdentityMapping) (executor.Executor, error) {
func newExecutor(root, cgroupParent string, net libnetwork.NetworkController, dnsConfig *oci.DNSConfig, rootless bool, idmap *idtools.IdentityMapping) (executor.Executor, error) {
networkProviders := map[pb.NetMode]network.Provider{
pb.NetMode_UNSET: &bridgeProvider{NetworkController: net, Root: filepath.Join(root, "net")},
pb.NetMode_HOST: network.NewHostProvider(),
@ -34,6 +36,7 @@ func newExecutor(root, cgroupParent string, net libnetwork.NetworkController, ro
Rootless: rootless,
NoPivot: os.Getenv("DOCKER_RAMDISK") != "",
IdentityMapping: idmap,
DNS: dnsConfig,
}, networkProviders)
}
@ -117,3 +120,14 @@ func (iface *lnInterface) Close() error {
}
return iface.err
}
func getDNSConfig(cfg config.DNSConfig) *oci.DNSConfig {
if cfg.DNS != nil || cfg.DNSSearch != nil || cfg.DNSOptions != nil {
return &oci.DNSConfig{
Nameservers: cfg.DNS,
SearchDomains: cfg.DNSSearch,
Options: cfg.DNSOptions,
}
}
return nil
}

View file

@ -5,13 +5,15 @@ import (
"errors"
"io"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/libnetwork"
"github.com/moby/buildkit/cache"
"github.com/moby/buildkit/executor"
"github.com/moby/buildkit/executor/oci"
)
func newExecutor(_, _ string, _ libnetwork.NetworkController, _ bool, _ *idtools.IdentityMapping) (executor.Executor, error) {
func newExecutor(_, _ string, _ libnetwork.NetworkController, _ *oci.DNSConfig, _ bool, _ *idtools.IdentityMapping) (executor.Executor, error) {
return &winExecutor{}, nil
}
@ -21,3 +23,7 @@ type winExecutor struct {
func (e *winExecutor) Exec(ctx context.Context, meta executor.Meta, rootfs cache.Mountable, mounts []executor.Mount, stdin io.ReadCloser, stdout, stderr io.WriteCloser) error {
return errors.New("buildkit executor not implemented for windows")
}
func getDNSConfig(config.DNSConfig) *oci.DNSConfig {
return nil
}

View file

@ -319,6 +319,7 @@ func newRouterOptions(config *config.Config, d *daemon.Daemon) (routerOptions, e
BuilderConfig: config.Builder,
Rootless: d.Rootless(),
IdentityMapping: d.IdentityMapping(),
DNSConfig: config.DNSConfig,
})
if err != nil {
return opts, err

View file

@ -109,6 +109,13 @@ type CommonTLSOptions struct {
KeyFile string `json:"tlskey,omitempty"`
}
// DNSConfig defines the DNS configurations.
type DNSConfig struct {
DNS []string `json:"dns,omitempty"`
DNSOptions []string `json:"dns-opts,omitempty"`
DNSSearch []string `json:"dns-search,omitempty"`
}
// CommonConfig defines the configuration of a docker daemon which is
// common across platforms.
// It includes json tags to deserialize configuration from a file
@ -119,9 +126,6 @@ type CommonConfig struct {
AutoRestart bool `json:"-"`
Context map[string][]string `json:"-"`
DisableBridge bool `json:"-"`
DNS []string `json:"dns,omitempty"`
DNSOptions []string `json:"dns-opts,omitempty"`
DNSSearch []string `json:"dns-search,omitempty"`
ExecOptions []string `json:"exec-opts,omitempty"`
GraphDriver string `json:"storage-driver,omitempty"`
GraphOptions []string `json:"storage-opts,omitempty"`
@ -200,6 +204,7 @@ type CommonConfig struct {
MetricsAddress string `json:"metrics-addr"`
DNSConfig
LogConfig
BridgeConfig // bridgeConfig holds bridge network specific configuration.
NetworkConfig

View file

@ -244,28 +244,36 @@ func TestValidateConfigurationErrors(t *testing.T) {
{
config: &Config{
CommonConfig: CommonConfig{
DNS: []string{"1.1.1.1o"},
DNSConfig: DNSConfig{
DNS: []string{"1.1.1.1o"},
},
},
},
},
{
config: &Config{
CommonConfig: CommonConfig{
DNS: []string{"2.2.2.2", "1.1.1.1o"},
DNSConfig: DNSConfig{
DNS: []string{"2.2.2.2", "1.1.1.1o"},
},
},
},
},
{
config: &Config{
CommonConfig: CommonConfig{
DNSSearch: []string{"123456"},
DNSConfig: DNSConfig{
DNSSearch: []string{"123456"},
},
},
},
},
{
config: &Config{
CommonConfig: CommonConfig{
DNSSearch: []string{"a.b.c", "123456"},
DNSConfig: DNSConfig{
DNSSearch: []string{"a.b.c", "123456"},
},
},
},
},
@ -329,14 +337,18 @@ func TestValidateConfiguration(t *testing.T) {
{
config: &Config{
CommonConfig: CommonConfig{
DNS: []string{"1.1.1.1"},
DNSConfig: DNSConfig{
DNS: []string{"1.1.1.1"},
},
},
},
},
{
config: &Config{
CommonConfig: CommonConfig{
DNSSearch: []string{"a.b.c"},
DNSConfig: DNSConfig{
DNSSearch: []string{"a.b.c"},
},
},
},
},

View file

@ -27,7 +27,7 @@ github.com/imdario/mergo 7c29201646fa3de8506f70121347
golang.org/x/sync e225da77a7e68af35c70ccbf71af2b83e6acac3c
# buildkit
github.com/moby/buildkit c24275065aca6605bd83c57c6735510f4ebeb6d9
github.com/moby/buildkit a258bd18b2c55aac4e8a10a3074757d66d45cef6
github.com/tonistiigi/fsutil 3bbb99cdbd76619ab717299830c60f6f2a533a6b
github.com/grpc-ecosystem/grpc-opentracing 8e809c8a86450a29b90dcc9efbf062d0fe6d9746
github.com/opentracing/opentracing-go 1361b9cd60be79c4c3a7fa9841b3c132e40066a7

View file

@ -299,7 +299,7 @@ Run `make images` to build the images as `moby/buildkit:local` and `moby/buildki
If you are running `moby/buildkit:master` or `moby/buildkit:master-rootless` as a Docker/Kubernetes container, you can use special `BUILDKIT_HOST` URL for connecting to the BuildKit daemon in the container:
```
export BUILDKIT_HOST=docker://<container>
export BUILDKIT_HOST=docker-container://<container>
```
```

View file

@ -157,14 +157,14 @@ func (cm *cacheManager) get(ctx context.Context, id string, fromSnapshotter bool
func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotter bool, opts ...RefOption) (cr *cacheRecord, retErr error) {
if rec, ok := cm.records[id]; ok {
if rec.isDead() {
return nil, errNotFound
return nil, errors.Wrapf(errNotFound, "failed to get dead record %s", id)
}
return rec, nil
}
md, ok := cm.md.Get(id)
if !ok && !fromSnapshotter {
return nil, errNotFound
return nil, errors.WithStack(errNotFound)
}
if mutableID := getEqualMutable(md); mutableID != "" {
mutable, err := cm.getRecord(ctx, mutableID, fromSnapshotter)
@ -222,7 +222,7 @@ func (cm *cacheManager) getRecord(ctx context.Context, id string, fromSnapshotte
if err := rec.remove(ctx, true); err != nil {
return nil, err
}
return nil, errNotFound
return nil, errors.Wrapf(errNotFound, "failed to get deleted record %s", id)
}
if err := initializeMetadata(rec, opts...); err != nil {
@ -330,14 +330,14 @@ func (cm *cacheManager) Prune(ctx context.Context, ch chan client.UsageInfo, opt
func (cm *cacheManager) pruneOnce(ctx context.Context, ch chan client.UsageInfo, opt client.PruneInfo) error {
filter, err := filters.ParseAll(opt.Filter...)
if err != nil {
return err
return errors.Wrapf(err, "failed to parse prune filters %v", opt.Filter)
}
var check ExternalRefChecker
if f := cm.PruneRefChecker; f != nil && (!opt.All || len(opt.Filter) > 0) {
c, err := f()
if err != nil {
return err
return errors.WithStack(err)
}
check = c
}
@ -549,7 +549,7 @@ func (cm *cacheManager) markShared(m map[string]*cacheUsageInfo) error {
}
c, err := cm.PruneRefChecker()
if err != nil {
return err
return errors.WithStack(err)
}
var markAllParentsShared func(string)
@ -590,7 +590,7 @@ type cacheUsageInfo struct {
func (cm *cacheManager) DiskUsage(ctx context.Context, opt client.DiskUsageInfo) ([]*client.UsageInfo, error) {
filter, err := filters.ParseAll(opt.Filter...)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to parse diskusage filters %v", opt.Filter)
}
cm.mu.Lock()

View file

@ -55,7 +55,7 @@ func (s *Store) All() ([]*StorageItem, error) {
return nil
})
})
return out, err
return out, errors.WithStack(err)
}
func (s *Store) Probe(index string) (bool, error) {
@ -77,7 +77,7 @@ func (s *Store) Probe(index string) (bool, error) {
}
return nil
})
return exists, err
return exists, errors.WithStack(err)
}
func (s *Store) Search(index string) ([]*StorageItem, error) {
@ -114,7 +114,7 @@ func (s *Store) Search(index string) ([]*StorageItem, error) {
}
return nil
})
return out, err
return out, errors.WithStack(err)
}
func (s *Store) View(id string, fn func(b *bolt.Bucket) error) error {
@ -132,7 +132,7 @@ func (s *Store) View(id string, fn func(b *bolt.Bucket) error) error {
}
func (s *Store) Clear(id string) error {
return s.db.Update(func(tx *bolt.Tx) error {
return errors.WithStack(s.db.Update(func(tx *bolt.Tx) error {
external := tx.Bucket([]byte(externalBucket))
if external != nil {
external.DeleteBucket([]byte(id))
@ -160,21 +160,21 @@ func (s *Store) Clear(id string) error {
}
}
return main.DeleteBucket([]byte(id))
})
}))
}
func (s *Store) Update(id string, fn func(b *bolt.Bucket) error) error {
return s.db.Update(func(tx *bolt.Tx) error {
return errors.WithStack(s.db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte(mainBucket))
if err != nil {
return err
return errors.WithStack(err)
}
b, err = b.CreateBucketIfNotExists([]byte(id))
if err != nil {
return err
return errors.WithStack(err)
}
return fn(b)
})
}))
}
func (s *Store) Get(id string) (*StorageItem, bool) {
@ -200,7 +200,7 @@ func (s *Store) Get(id string) (*StorageItem, bool) {
}
func (s *Store) Close() error {
return s.db.Close()
return errors.WithStack(s.db.Close())
}
type StorageItem struct {
@ -222,13 +222,13 @@ func newStorageItem(id string, b *bolt.Bucket, s *Store) (*StorageItem, error) {
var sv Value
if len(v) > 0 {
if err := json.Unmarshal(v, &sv); err != nil {
return err
return errors.WithStack(err)
}
si.values[string(k)] = &sv
}
return nil
}); err != nil {
return si, err
return si, errors.WithStack(err)
}
}
return si, nil
@ -283,23 +283,23 @@ func (s *StorageItem) GetExternal(k string) ([]byte, error) {
return nil
})
if err != nil {
return nil, err
return nil, errors.WithStack(err)
}
return dt, nil
}
func (s *StorageItem) SetExternal(k string, dt []byte) error {
return s.storage.db.Update(func(tx *bolt.Tx) error {
return errors.WithStack(s.storage.db.Update(func(tx *bolt.Tx) error {
b, err := tx.CreateBucketIfNotExists([]byte(externalBucket))
if err != nil {
return err
return errors.WithStack(err)
}
b, err = b.CreateBucketIfNotExists([]byte(s.id))
if err != nil {
return err
return errors.WithStack(err)
}
return b.Put([]byte(k), dt)
})
}))
}
func (s *StorageItem) Queue(fn func(b *bolt.Bucket) error) {
@ -311,15 +311,15 @@ func (s *StorageItem) Queue(fn func(b *bolt.Bucket) error) {
func (s *StorageItem) Commit() error {
s.mu.Lock()
defer s.mu.Unlock()
return s.Update(func(b *bolt.Bucket) error {
return errors.WithStack(s.Update(func(b *bolt.Bucket) error {
for _, fn := range s.queue {
if err := fn(b); err != nil {
return err
return errors.WithStack(err)
}
}
s.queue = s.queue[:0]
return nil
})
}))
}
func (s *StorageItem) Indexes() (out []string) {
@ -341,18 +341,18 @@ func (s *StorageItem) SetValue(b *bolt.Bucket, key string, v *Value) error {
}
dt, err := json.Marshal(v)
if err != nil {
return err
return errors.WithStack(err)
}
if err := b.Put([]byte(key), dt); err != nil {
return err
return errors.WithStack(err)
}
if v.Index != "" {
b, err := b.Tx().CreateBucketIfNotExists([]byte(indexBucket))
if err != nil {
return err
return errors.WithStack(err)
}
if err := b.Put([]byte(indexKey(v.Index, s.ID())), []byte{}); err != nil {
return err
return errors.WithStack(err)
}
}
s.values[key] = v
@ -367,14 +367,13 @@ type Value struct {
func NewValue(v interface{}) (*Value, error) {
dt, err := json.Marshal(v)
if err != nil {
return nil, err
return nil, errors.WithStack(err)
}
return &Value{Value: json.RawMessage(dt)}, nil
}
func (v *Value) Unmarshal(target interface{}) error {
err := json.Unmarshal(v.Value, target)
return err
return errors.WithStack(json.Unmarshal(v.Value, target))
}
func indexKey(index, target string) string {

View file

@ -190,7 +190,7 @@ func (cr *cacheRecord) remove(ctx context.Context, removeSnapshot bool) error {
}
if removeSnapshot {
if err := cr.cm.Snapshotter.Remove(ctx, cr.ID()); err != nil {
return err
return errors.Wrapf(err, "failed to remove %s", cr.ID())
}
}
if err := cr.cm.md.Clear(cr.ID()); err != nil {
@ -259,7 +259,7 @@ func (sr *immutableRef) release(ctx context.Context) error {
if len(sr.refs) == 0 {
if sr.viewMount != nil { // TODO: release viewMount earlier if possible
if err := sr.cm.Snapshotter.Remove(ctx, sr.view); err != nil {
return err
return errors.Wrapf(err, "failed to remove view %s", sr.view)
}
sr.view = ""
sr.viewMount = nil

View file

@ -100,7 +100,7 @@ func readBlob(ctx context.Context, provider content.Provider, desc ocispec.Descr
}
}
}
return dt, err
return dt, errors.WithStack(err)
}
func (ci *contentCacheImporter) importInlineCache(ctx context.Context, dt []byte, id string, w worker.Worker) (solver.CacheManager, error) {
@ -120,7 +120,7 @@ func (ci *contentCacheImporter) importInlineCache(ctx context.Context, dt []byte
var m ocispec.Manifest
if err := json.Unmarshal(dt, &m); err != nil {
return err
return errors.WithStack(err)
}
if m.Config.Digest == "" || len(m.Layers) == 0 {
@ -129,13 +129,13 @@ func (ci *contentCacheImporter) importInlineCache(ctx context.Context, dt []byte
p, err := content.ReadBlob(ctx, ci.provider, m.Config)
if err != nil {
return err
return errors.WithStack(err)
}
var img image
if err := json.Unmarshal(p, &img); err != nil {
return err
return errors.WithStack(err)
}
if len(img.Rootfs.DiffIDs) != len(m.Layers) {
@ -149,7 +149,7 @@ func (ci *contentCacheImporter) importInlineCache(ctx context.Context, dt []byte
var config v1.CacheConfig
if err := json.Unmarshal(img.Cache, &config.Records); err != nil {
return err
return errors.WithStack(err)
}
createdDates, createdMsg, err := parseCreatedLayerInfo(img)
@ -181,7 +181,7 @@ func (ci *contentCacheImporter) importInlineCache(ctx context.Context, dt []byte
dt, err = json.Marshal(config)
if err != nil {
return err
return errors.WithStack(err)
}
mu.Lock()
@ -217,7 +217,7 @@ func (ci *contentCacheImporter) allDistributionManifests(ctx context.Context, dt
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
var index ocispec.Index
if err := json.Unmarshal(dt, &index); err != nil {
return err
return errors.WithStack(err)
}
for _, d := range index.Manifests {
@ -226,7 +226,7 @@ func (ci *contentCacheImporter) allDistributionManifests(ctx context.Context, dt
}
p, err := content.ReadBlob(ctx, ci.provider, d)
if err != nil {
return err
return errors.WithStack(err)
}
if err := ci.allDistributionManifests(ctx, p, m); err != nil {
return err

View file

@ -254,7 +254,7 @@ func (cs *cacheResultStorage) Load(ctx context.Context, res solver.CacheResult)
ref, err := cs.w.FromRemote(ctx, item.result)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to load result from remote")
}
return worker.NewWorkerRefResult(ref, cs.w), nil
}

View file

@ -12,7 +12,7 @@ import (
func Parse(configJSON []byte, provider DescriptorProvider, t solver.CacheExporterTarget) error {
var config CacheConfig
if err := json.Unmarshal(configJSON, &config); err != nil {
return err
return errors.WithStack(err)
}
return ParseConfig(config, provider, t)

View file

@ -61,23 +61,23 @@ func ReadFile(ctx context.Context, ref cache.ImmutableRef, req ReadRequest) ([]b
err := withMount(ctx, ref, func(root string) error {
fp, err := fs.RootPath(root, req.Filename)
if err != nil {
return err
return errors.WithStack(err)
}
if req.Range == nil {
dt, err = ioutil.ReadFile(fp)
if err != nil {
return err
return errors.WithStack(err)
}
} else {
f, err := os.Open(fp)
if err != nil {
return err
return errors.WithStack(err)
}
dt, err = ioutil.ReadAll(io.NewSectionReader(f, int64(req.Range.Offset), int64(req.Range.Length)))
f.Close()
if err != nil {
return err
return errors.WithStack(err)
}
}
return nil
@ -101,7 +101,7 @@ func ReadDir(ctx context.Context, ref cache.ImmutableRef, req ReadDirRequest) ([
err := withMount(ctx, ref, func(root string) error {
fp, err := fs.RootPath(root, req.Path)
if err != nil {
return err
return errors.WithStack(err)
}
return fsutil.Walk(ctx, fp, &wo, func(path string, info os.FileInfo, err error) error {
if err != nil {
@ -128,10 +128,10 @@ func StatFile(ctx context.Context, ref cache.ImmutableRef, path string) (*fstype
err := withMount(ctx, ref, func(root string) error {
fp, err := fs.RootPath(root, path)
if err != nil {
return err
return errors.WithStack(err)
}
if st, err = fsutil.Stat(fp); err != nil {
return err
return errors.WithStack(err)
}
return nil
})

View file

@ -427,11 +427,13 @@ func Security(s pb.SecurityMode) RunOption {
}
func Shlex(str string) RunOption {
return Shlexf(str)
return runOptionFunc(func(ei *ExecInfo) {
ei.State = shlexf(str, false)(ei.State)
})
}
func Shlexf(str string, v ...interface{}) RunOption {
return runOptionFunc(func(ei *ExecInfo) {
ei.State = shlexf(str, v...)(ei.State)
ei.State = shlexf(str, true, v...)(ei.State)
})
}
@ -442,7 +444,9 @@ func Args(a []string) RunOption {
}
func AddEnv(key, value string) RunOption {
return AddEnvf(key, value)
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.AddEnv(key, value)
})
}
func AddEnvf(key, value string, v ...interface{}) RunOption {
@ -458,7 +462,9 @@ func User(str string) RunOption {
}
func Dir(str string) RunOption {
return Dirf(str)
return runOptionFunc(func(ei *ExecInfo) {
ei.State = ei.State.Dir(str)
})
}
func Dirf(str string, v ...interface{}) RunOption {
return runOptionFunc(func(ei *ExecInfo) {

View file

@ -24,19 +24,24 @@ var (
keySecurity = contextKeyT("llb.security")
)
func addEnvf(key, value string, v ...interface{}) StateOption {
func addEnvf(key, value string, replace bool, v ...interface{}) StateOption {
if replace {
value = fmt.Sprintf(value, v...)
}
return func(s State) State {
return s.WithValue(keyEnv, getEnv(s).AddOrReplace(key, fmt.Sprintf(value, v...)))
return s.WithValue(keyEnv, getEnv(s).AddOrReplace(key, value))
}
}
func dir(str string) StateOption {
return dirf(str)
return dirf(str, false)
}
func dirf(str string, v ...interface{}) StateOption {
func dirf(value string, replace bool, v ...interface{}) StateOption {
if replace {
value = fmt.Sprintf(value, v...)
}
return func(s State) State {
value := fmt.Sprintf(str, v...)
if !path.IsAbs(value) {
prev := getDir(s)
if prev == "" {
@ -100,9 +105,12 @@ func args(args ...string) StateOption {
}
}
func shlexf(str string, v ...interface{}) StateOption {
func shlexf(str string, replace bool, v ...interface{}) StateOption {
if replace {
str = fmt.Sprintf(str, v...)
}
return func(s State) State {
arg, err := shlex.Split(fmt.Sprintf(str, v...))
arg, err := shlex.Split(str)
if err != nil {
// TODO: handle error
}

View file

@ -240,18 +240,18 @@ func (s State) File(a *FileAction, opts ...ConstraintsOpt) State {
}
func (s State) AddEnv(key, value string) State {
return s.AddEnvf(key, value)
return addEnvf(key, value, false)(s)
}
func (s State) AddEnvf(key, value string, v ...interface{}) State {
return addEnvf(key, value, v...)(s)
return addEnvf(key, value, true, v...)(s)
}
func (s State) Dir(str string) State {
return s.Dirf(str)
return dirf(str, false)(s)
}
func (s State) Dirf(str string, v ...interface{}) State {
return dirf(str, v...)(s)
return dirf(str, true, v...)(s)
}
func (s State) GetEnv(key string) (string, bool) {

View file

@ -8,6 +8,7 @@ import (
"github.com/docker/docker/pkg/idtools"
"github.com/docker/libnetwork/resolvconf"
"github.com/docker/libnetwork/types"
"github.com/moby/buildkit/util/flightcontrol"
)
@ -15,7 +16,13 @@ var g flightcontrol.Group
var notFirstRun bool
var lastNotEmpty bool
func GetResolvConf(ctx context.Context, stateDir string, idmap *idtools.IdentityMapping) (string, error) {
type DNSConfig struct {
Nameservers []string
Options []string
SearchDomains []string
}
func GetResolvConf(ctx context.Context, stateDir string, idmap *idtools.IdentityMapping, dns *DNSConfig) (string, error) {
p := filepath.Join(stateDir, "resolv.conf")
_, err := g.Do(ctx, stateDir, func(ctx context.Context) (interface{}, error) {
generate := !notFirstRun
@ -61,9 +68,34 @@ func GetResolvConf(ctx context.Context, stateDir string, idmap *idtools.Identity
dt = f.Content
}
f, err = resolvconf.FilterResolvDNS(dt, true)
if err != nil {
return "", err
if dns != nil {
var (
dnsNameservers = resolvconf.GetNameservers(dt, types.IP)
dnsSearchDomains = resolvconf.GetSearchDomains(dt)
dnsOptions = resolvconf.GetOptions(dt)
)
if len(dns.Nameservers) > 0 {
dnsNameservers = dns.Nameservers
}
if len(dns.SearchDomains) > 0 {
dnsSearchDomains = dns.SearchDomains
}
if len(dns.Options) > 0 {
dnsOptions = dns.Options
}
f, err = resolvconf.Build(p+".tmp", dnsNameservers, dnsSearchDomains, dnsOptions)
if err != nil {
return "", err
}
} else {
// Logic seems odd here: why are we filtering localhost IPs
// only if neither of the DNS configs were specified?
// Logic comes from https://github.com/docker/libnetwork/blob/164a77ee6d24fb2b1d61f8ad3403a51d8453899e/sandbox_dns_unix.go#L230-L269
f, err = resolvconf.FilterResolvDNS(f.Content, true)
if err != nil {
return "", err
}
}
tmpPath := p + ".tmp"

13
vendor/github.com/moby/buildkit/executor/oci/spec.go generated vendored Normal file
View file

@ -0,0 +1,13 @@
package oci
// ProcMode configures PID namespaces
type ProcessMode int
const (
// ProcessSandbox unshares pidns and mount procfs.
ProcessSandbox ProcessMode = iota
// NoProcessSandbox uses host pidns and bind-mount procfs.
// Note that NoProcessSandbox allows build containers to kill (and potentially ptrace) an arbitrary process in the BuildKit host namespace.
// NoProcessSandbox should be enabled only when the BuildKit is running in a container as an unprivileged user.
NoProcessSandbox
)

View file

@ -27,18 +27,6 @@ import (
// Ideally we don't have to import whole containerd just for the default spec
// ProcMode configures PID namespaces
type ProcessMode int
const (
// ProcessSandbox unshares pidns and mount procfs.
ProcessSandbox ProcessMode = iota
// NoProcessSandbox uses host pidns and bind-mount procfs.
// Note that NoProcessSandbox allows build containers to kill (and potentially ptrace) an arbitrary process in the BuildKit host namespace.
// NoProcessSandbox should be enabled only when the BuildKit is running in a container as an unprivileged user.
NoProcessSandbox
)
// GenerateSpec generates spec using containerd functionality.
// opts are ignored for s.Process, s.Hostname, and s.Mounts .
func GenerateSpec(ctx context.Context, meta executor.Meta, mounts []executor.Mount, id, resolvConf, hostsFile string, namespace network.Namespace, processMode ProcessMode, idmap *idtools.IdentityMapping, opts ...oci.SpecOpts) (*specs.Spec, func(), error) {

View file

@ -20,19 +20,11 @@ func GetUser(ctx context.Context, root, username string) (uint32, uint32, []uint
return uid, gid, nil, nil
}
passwdPath, err := user.GetPasswdPath()
if err != nil {
return 0, 0, nil, err
}
groupPath, err := user.GetGroupPath()
if err != nil {
return 0, 0, nil, err
}
passwdFile, err := openUserFile(root, passwdPath)
passwdFile, err := openUserFile(root, "/etc/passwd")
if err == nil {
defer passwdFile.Close()
}
groupFile, err := openUserFile(root, groupPath)
groupFile, err := openUserFile(root, "/etc/group")
if err == nil {
defer groupFile.Close()
}

View file

@ -43,6 +43,7 @@ type Opt struct {
IdentityMapping *idtools.IdentityMapping
// runc run --no-pivot (unrecommended)
NoPivot bool
DNS *oci.DNSConfig
}
var defaultCommandCandidates = []string{"buildkit-runc", "runc"}
@ -57,6 +58,7 @@ type runcExecutor struct {
processMode oci.ProcessMode
idmap *idtools.IdentityMapping
noPivot bool
dns *oci.DNSConfig
}
func New(opt Opt, networkProviders map[pb.NetMode]network.Provider) (executor.Executor, error) {
@ -115,6 +117,7 @@ func New(opt Opt, networkProviders map[pb.NetMode]network.Provider) (executor.Ex
processMode: opt.ProcessMode,
idmap: opt.IdentityMapping,
noPivot: opt.NoPivot,
dns: opt.DNS,
}
return w, nil
}
@ -134,7 +137,7 @@ func (w *runcExecutor) Exec(ctx context.Context, meta executor.Meta, root cache.
logrus.Info("enabling HostNetworking")
}
resolvConf, err := oci.GetResolvConf(ctx, w.root, w.idmap)
resolvConf, err := oci.GetResolvConf(ctx, w.root, w.idmap, w.dns)
if err != nil {
return err
}

View file

@ -50,8 +50,8 @@ const (
keyContextSubDir = "contextsubdir"
)
var httpPrefix = regexp.MustCompile("^https?://")
var gitUrlPathWithFragmentSuffix = regexp.MustCompile("\\.git(?:#.+)?$")
var httpPrefix = regexp.MustCompile(`^https?://`)
var gitUrlPathWithFragmentSuffix = regexp.MustCompile(`\.git(?:#.+)?$`)
func Build(ctx context.Context, c client.Client) (*client.Result, error) {
opts := c.BuildOpts().Opts

View file

@ -128,7 +128,7 @@ func (c *grpcClient) Run(ctx context.Context, f client.BuildFunc) (retError erro
}
}
if retError != nil {
st, _ := status.FromError(retError)
st, _ := status.FromError(errors.Cause(retError))
stp := st.Proto()
req.Error = &rpc.Status{
Code: stp.Code,

View file

@ -4,6 +4,7 @@ import (
"context"
"github.com/moby/buildkit/session"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
@ -16,10 +17,10 @@ func CredentialsFunc(ctx context.Context, c session.Caller) func(string) (string
Host: host,
})
if err != nil {
if st, ok := status.FromError(err); ok && st.Code() == codes.Unimplemented {
if st, ok := status.FromError(errors.Cause(err)); ok && st.Code() == codes.Unimplemented {
return "", "", nil
}
return "", "", err
return "", "", errors.WithStack(err)
}
return resp.Username, resp.Secret, nil
}

View file

@ -9,6 +9,7 @@ import (
"github.com/moby/buildkit/session"
digest "github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"google.golang.org/grpc/metadata"
)
@ -31,47 +32,53 @@ func (cs *callerContentStore) choose(ctx context.Context) context.Context {
func (cs *callerContentStore) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
ctx = cs.choose(ctx)
return cs.store.Info(ctx, dgst)
info, err := cs.store.Info(ctx, dgst)
return info, errors.WithStack(err)
}
func (cs *callerContentStore) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
ctx = cs.choose(ctx)
return cs.store.Update(ctx, info, fieldpaths...)
info, err := cs.store.Update(ctx, info, fieldpaths...)
return info, errors.WithStack(err)
}
func (cs *callerContentStore) Walk(ctx context.Context, fn content.WalkFunc, fs ...string) error {
ctx = cs.choose(ctx)
return cs.store.Walk(ctx, fn, fs...)
return errors.WithStack(cs.store.Walk(ctx, fn, fs...))
}
func (cs *callerContentStore) Delete(ctx context.Context, dgst digest.Digest) error {
ctx = cs.choose(ctx)
return cs.store.Delete(ctx, dgst)
return errors.WithStack(cs.store.Delete(ctx, dgst))
}
func (cs *callerContentStore) ListStatuses(ctx context.Context, fs ...string) ([]content.Status, error) {
ctx = cs.choose(ctx)
return cs.store.ListStatuses(ctx, fs...)
resp, err := cs.store.ListStatuses(ctx, fs...)
return resp, errors.WithStack(err)
}
func (cs *callerContentStore) Status(ctx context.Context, ref string) (content.Status, error) {
ctx = cs.choose(ctx)
return cs.store.Status(ctx, ref)
st, err := cs.store.Status(ctx, ref)
return st, errors.WithStack(err)
}
func (cs *callerContentStore) Abort(ctx context.Context, ref string) error {
ctx = cs.choose(ctx)
return cs.store.Abort(ctx, ref)
return errors.WithStack(cs.store.Abort(ctx, ref))
}
func (cs *callerContentStore) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
ctx = cs.choose(ctx)
return cs.store.Writer(ctx, opts...)
w, err := cs.store.Writer(ctx, opts...)
return w, errors.WithStack(err)
}
func (cs *callerContentStore) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
ctx = cs.choose(ctx)
return cs.store.ReaderAt(ctx, desc)
ra, err := cs.store.ReaderAt(ctx, desc)
return ra, errors.WithStack(err)
}
// NewCallerStore creates content.Store from session.Caller with specified storeID

View file

@ -14,7 +14,7 @@ import (
)
func sendDiffCopy(stream grpc.Stream, fs fsutil.FS, progress progressCb) error {
return fsutil.Send(stream.Context(), stream, fs, progress)
return errors.WithStack(fsutil.Send(stream.Context(), stream, fs, progress))
}
func newStreamWriter(stream grpc.ClientStream) io.WriteCloser {
@ -29,7 +29,7 @@ type bufferedWriteCloser struct {
func (bwc *bufferedWriteCloser) Close() error {
if err := bwc.Writer.Flush(); err != nil {
return err
return errors.WithStack(err)
}
return bwc.Closer.Close()
}
@ -40,19 +40,19 @@ type streamWriterCloser struct {
func (wc *streamWriterCloser) Write(dt []byte) (int, error) {
if err := wc.ClientStream.SendMsg(&BytesMessage{Data: dt}); err != nil {
return 0, err
return 0, errors.WithStack(err)
}
return len(dt), nil
}
func (wc *streamWriterCloser) Close() error {
if err := wc.ClientStream.CloseSend(); err != nil {
return err
return errors.WithStack(err)
}
// block until receiver is done
var bm BytesMessage
if err := wc.ClientStream.RecvMsg(&bm); err != io.EOF {
return err
return errors.WithStack(err)
}
return nil
}
@ -69,19 +69,19 @@ func recvDiffCopy(ds grpc.Stream, dest string, cu CacheUpdater, progress progres
cf = cu.HandleChange
ch = cu.ContentHasher()
}
return fsutil.Receive(ds.Context(), ds, dest, fsutil.ReceiveOpt{
return errors.WithStack(fsutil.Receive(ds.Context(), ds, dest, fsutil.ReceiveOpt{
NotifyHashed: cf,
ContentHasher: ch,
ProgressCb: progress,
Filter: fsutil.FilterFunc(filter),
})
}))
}
func syncTargetDiffCopy(ds grpc.Stream, dest string) error {
if err := os.MkdirAll(dest, 0700); err != nil {
return err
return errors.Wrapf(err, "failed to create synctarget dest dir %s", dest)
}
return fsutil.Receive(ds.Context(), ds, dest, fsutil.ReceiveOpt{
return errors.WithStack(fsutil.Receive(ds.Context(), ds, dest, fsutil.ReceiveOpt{
Merge: true,
Filter: func() func(string, *fstypes.Stat) bool {
uid := os.Getuid()
@ -92,7 +92,7 @@ func syncTargetDiffCopy(ds grpc.Stream, dest string) error {
return true
}
}(),
})
}))
}
func writeTargetFile(ds grpc.Stream, wc io.WriteCloser) error {
@ -102,10 +102,10 @@ func writeTargetFile(ds grpc.Stream, wc io.WriteCloser) error {
if errors.Cause(err) == io.EOF {
return nil
}
return err
return errors.WithStack(err)
}
if _, err := wc.Write(bm.Data); err != nil {
return err
return errors.WithStack(err)
}
}
}

View file

@ -275,7 +275,7 @@ func CopyToCaller(ctx context.Context, fs fsutil.FS, c session.Caller, progress
cc, err := client.DiffCopy(ctx)
if err != nil {
return err
return errors.WithStack(err)
}
return sendDiffCopy(cc, fs, progress)
@ -291,7 +291,7 @@ func CopyFileWriter(ctx context.Context, c session.Caller) (io.WriteCloser, erro
cc, err := client.DiffCopy(ctx)
if err != nil {
return nil, err
return nil, errors.WithStack(err)
}
return newStreamWriter(cc), nil

View file

@ -21,10 +21,10 @@ func GetSecret(ctx context.Context, c session.Caller, id string) ([]byte, error)
ID: id,
})
if err != nil {
if st, ok := status.FromError(err); ok && (st.Code() == codes.Unimplemented || st.Code() == codes.NotFound) {
if st, ok := status.FromError(errors.Cause(err)); ok && (st.Code() == codes.Unimplemented || st.Code() == codes.NotFound) {
return nil, errors.Wrapf(ErrNotFound, "secret %s not found", id)
}
return nil, err
return nil, errors.WithStack(err)
}
return resp.Data, nil
}

View file

@ -3,6 +3,7 @@ package sshforward
import (
io "io"
"github.com/pkg/errors"
context "golang.org/x/net/context"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
@ -19,7 +20,7 @@ func Copy(ctx context.Context, conn io.ReadWriteCloser, stream grpc.Stream) erro
return nil
}
conn.Close()
return err
return errors.WithStack(err)
}
select {
case <-ctx.Done():
@ -29,7 +30,7 @@ func Copy(ctx context.Context, conn io.ReadWriteCloser, stream grpc.Stream) erro
}
if _, err := conn.Write(p.Data); err != nil {
conn.Close()
return err
return errors.WithStack(err)
}
p.Data = p.Data[:0]
}
@ -43,7 +44,7 @@ func Copy(ctx context.Context, conn io.ReadWriteCloser, stream grpc.Stream) erro
case err == io.EOF:
return nil
case err != nil:
return err
return errors.WithStack(err)
}
select {
case <-ctx.Done():
@ -52,7 +53,7 @@ func Copy(ctx context.Context, conn io.ReadWriteCloser, stream grpc.Stream) erro
}
p := &BytesMessage{Data: buf[:n]}
if err := stream.SendMsg(p); err != nil {
return err
return errors.WithStack(err)
}
}
})

View file

@ -7,6 +7,7 @@ import (
"path/filepath"
"github.com/moby/buildkit/session"
"github.com/pkg/errors"
context "golang.org/x/net/context"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc/metadata"
@ -65,7 +66,7 @@ type SocketOpt struct {
func MountSSHSocket(ctx context.Context, c session.Caller, opt SocketOpt) (sockPath string, closer func() error, err error) {
dir, err := ioutil.TempDir("", ".buildkit-ssh-sock")
if err != nil {
return "", nil, err
return "", nil, errors.WithStack(err)
}
defer func() {
@ -78,16 +79,16 @@ func MountSSHSocket(ctx context.Context, c session.Caller, opt SocketOpt) (sockP
l, err := net.Listen("unix", sockPath)
if err != nil {
return "", nil, err
return "", nil, errors.WithStack(err)
}
if err := os.Chown(sockPath, opt.UID, opt.GID); err != nil {
l.Close()
return "", nil, err
return "", nil, errors.WithStack(err)
}
if err := os.Chmod(sockPath, os.FileMode(opt.Mode)); err != nil {
l.Close()
return "", nil, err
return "", nil, errors.WithStack(err)
}
s := &server{caller: c}
@ -102,12 +103,12 @@ func MountSSHSocket(ctx context.Context, c session.Caller, opt SocketOpt) (sockP
return sockPath, func() error {
err := l.Close()
os.RemoveAll(sockPath)
return err
return errors.WithStack(err)
}, nil
}
func CheckSSHID(ctx context.Context, c session.Caller, id string) error {
client := NewSSHClient(c.Conn())
_, err := client.CheckAgent(ctx, &CheckAgentRequest{ID: id})
return err
return errors.WithStack(err)
}

View file

@ -6,6 +6,7 @@ import (
"net/url"
"github.com/moby/buildkit/session"
"github.com/pkg/errors"
"google.golang.org/grpc/metadata"
)
@ -26,7 +27,7 @@ func New(ctx context.Context, c session.Caller, url *url.URL) (*Upload, error) {
cc, err := client.Pull(ctx)
if err != nil {
return nil, err
return nil, errors.WithStack(err)
}
return &Upload{cc: cc}, nil
@ -44,12 +45,12 @@ func (u *Upload) WriteTo(w io.Writer) (int, error) {
if err == io.EOF {
return n, nil
}
return n, err
return n, errors.WithStack(err)
}
nn, err := w.Write(bm.Data)
n += nn
if err != nil {
return n, err
return n, errors.WithStack(err)
}
}
}

View file

@ -331,7 +331,8 @@ func (e *edge) unpark(incoming []pipe.Sender, updates, allPipes []pipe.Receiver,
if e.cacheMapReq == nil && (e.cacheMap == nil || len(e.cacheRecords) == 0) {
index := e.cacheMapIndex
e.cacheMapReq = f.NewFuncRequest(func(ctx context.Context) (interface{}, error) {
return e.op.CacheMap(ctx, index)
cm, err := e.op.CacheMap(ctx, index)
return cm, errors.Wrap(err, "failed to load cache key")
})
cacheMapReq = true
}
@ -798,7 +799,8 @@ func (e *edge) createInputRequests(desiredState edgeStatusType, f *pipeFactory,
res := dep.result
func(fn ResultBasedCacheFunc, res Result, index Index) {
dep.slowCacheReq = f.NewFuncRequest(func(ctx context.Context) (interface{}, error) {
return e.op.CalcSlowCache(ctx, index, fn, res)
v, err := e.op.CalcSlowCache(ctx, index, fn, res)
return v, errors.Wrap(err, "failed to compute cache key")
})
}(fn, res, dep.index)
addedNew = true
@ -850,7 +852,7 @@ func (e *edge) loadCache(ctx context.Context) (interface{}, error) {
logrus.Debugf("load cache for %s with %s", e.edge.Vertex.Name(), rec.ID)
res, err := e.op.LoadCache(ctx, rec)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to load cache")
}
return NewCachedResult(res, []ExportableCacheKey{{CacheKey: rec.key, Exporter: &exporter{k: rec.key, record: rec, edge: e}}}), nil
@ -861,7 +863,7 @@ func (e *edge) execOp(ctx context.Context) (interface{}, error) {
cacheKeys, inputs := e.commitOptions()
results, subExporters, err := e.op.Exec(ctx, toResultSlice(inputs))
if err != nil {
return nil, err
return nil, errors.WithStack(err)
}
index := e.edge.Index

View file

@ -94,11 +94,11 @@ func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest) (res *
edge, err := Load(req.Definition, ValidateEntitlements(ent), WithCacheSources(cms), RuntimePlatforms(b.platforms), WithValidateCaps())
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to load LLB")
}
ref, err := b.builder.Build(ctx, edge)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "failed to build LLB")
}
res = &frontend.Result{Ref: ref}
@ -109,7 +109,7 @@ func (b *llbBridge) Solve(ctx context.Context, req frontend.SolveRequest) (res *
}
res, err = f.Solve(ctx, b, req.FrontendOpt)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to solve with frontend %s", req.Frontend)
}
} else {
return &frontend.Result{}, nil

View file

@ -10,6 +10,7 @@ import (
"github.com/moby/buildkit/frontend"
"github.com/moby/buildkit/snapshot"
"github.com/moby/buildkit/solver"
"github.com/moby/buildkit/solver/llbsolver"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/worker"
digest "github.com/opencontainers/go-digest"
@ -25,6 +26,9 @@ type buildOp struct {
}
func NewBuildOp(v solver.Vertex, op *pb.Op_Build, b frontend.FrontendLLBBridge, _ worker.Worker) (solver.Op, error) {
if err := llbsolver.ValidateOp(&pb.Op{Op: op}); err != nil {
return nil, err
}
return &buildOp{
op: op.Build,
b: b,

View file

@ -60,6 +60,9 @@ type execOp struct {
}
func NewExecOp(v solver.Vertex, op *pb.Op_Exec, platform *pb.Platform, cm cache.Manager, sm *session.Manager, md *metadata.Store, exec executor.Executor, w worker.Worker) (solver.Op, error) {
if err := llbsolver.ValidateOp(&pb.Op{Op: op}); err != nil {
return nil, err
}
return &execOp{
op: op.Exec,
cm: cm,
@ -324,7 +327,7 @@ func (e *execOp) getSSHMountable(ctx context.Context, m *pb.Mount) (cache.Mounta
if m.SSHOpt.Optional {
return nil, nil
}
if st, ok := status.FromError(err); ok && st.Code() == codes.Unimplemented {
if st, ok := status.FromError(errors.Cause(err)); ok && st.Code() == codes.Unimplemented {
return nil, errors.Errorf("no SSH key %q forwarded from the client", m.SSHOpt.ID)
}
return nil, err

View file

@ -35,6 +35,9 @@ type fileOp struct {
}
func NewFileOp(v solver.Vertex, op *pb.Op_File, cm cache.Manager, md *metadata.Store, w worker.Worker) (solver.Op, error) {
if err := llbsolver.ValidateOp(&pb.Op{Op: op}); err != nil {
return nil, err
}
return &fileOp{
op: op.File,
md: md,

View file

@ -7,6 +7,7 @@ import (
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/solver"
"github.com/moby/buildkit/solver/llbsolver"
"github.com/moby/buildkit/solver/pb"
"github.com/moby/buildkit/source"
"github.com/moby/buildkit/worker"
@ -26,6 +27,9 @@ type sourceOp struct {
}
func NewSourceOp(_ solver.Vertex, op *pb.Op_Source, platform *pb.Platform, sm *source.Manager, sessM *session.Manager, w worker.Worker) (solver.Op, error) {
if err := llbsolver.ValidateOp(&pb.Op{Op: op}); err != nil {
return nil, err
}
return &sourceOp{
op: op,
sm: sm,

View file

@ -188,8 +188,15 @@ func loadLLB(def *pb.Definition, fn func(digest.Digest, *pb.Op, func(digest.Dige
allOps[dgst] = &op
}
if len(allOps) < 2 {
return solver.Edge{}, errors.Errorf("invalid LLB with %d vertexes", len(allOps))
}
lastOp := allOps[dgst]
delete(allOps, dgst)
if len(lastOp.Inputs) == 0 {
return solver.Edge{}, errors.Errorf("invalid LLB with no inputs on last vertex")
}
dgst = lastOp.Inputs[0].Digest
cache := make(map[digest.Digest]solver.Vertex)
@ -203,6 +210,11 @@ func loadLLB(def *pb.Definition, fn func(digest.Digest, *pb.Op, func(digest.Dige
if !ok {
return nil, errors.Errorf("invalid missing input digest %s", dgst)
}
if err := ValidateOp(op); err != nil {
return nil, err
}
v, err := fn(dgst, op, rec)
if err != nil {
return nil, err
@ -240,6 +252,55 @@ func llbOpName(op *pb.Op) string {
}
}
func ValidateOp(op *pb.Op) error {
if op == nil {
return errors.Errorf("invalid nil op")
}
switch op := op.Op.(type) {
case *pb.Op_Source:
if op.Source == nil {
return errors.Errorf("invalid nil source op")
}
case *pb.Op_Exec:
if op.Exec == nil {
return errors.Errorf("invalid nil exec op")
}
if op.Exec.Meta == nil {
return errors.Errorf("invalid exec op with no meta")
}
if len(op.Exec.Meta.Args) == 0 {
return errors.Errorf("invalid exec op with no args")
}
if len(op.Exec.Mounts) == 0 {
return errors.Errorf("invalid exec op with no mounts")
}
isRoot := false
for _, m := range op.Exec.Mounts {
if m.Dest == pb.RootMount {
isRoot = true
break
}
}
if !isRoot {
return errors.Errorf("invalid exec op with no rootfs")
}
case *pb.Op_File:
if op.File == nil {
return errors.Errorf("invalid nil file op")
}
if len(op.File.Actions) == 0 {
return errors.Errorf("invalid file op with no actions")
}
case *pb.Op_Build:
if op.Build == nil {
return errors.Errorf("invalid nil build op")
}
}
return nil
}
func fileOpName(actions []*pb.FileAction) string {
names := make([]string, 0, len(actions))
for _, action := range actions {