package dockerfile import ( "strconv" "strings" "sync" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types/container" "github.com/docker/docker/builder" "github.com/docker/docker/builder/remotecontext" "github.com/pkg/errors" ) // imageContexts is a helper for stacking up built image rootfs and reusing // them as contexts type imageContexts struct { b *Builder list []*imageMount byName map[string]*imageMount cache *pathCache currentName string } func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) { im := &imageMount{ic: ic} if len(name) > 0 { if ic.byName == nil { ic.byName = make(map[string]*imageMount) } if _, ok := ic.byName[name]; ok { return nil, errors.Errorf("duplicate name %s", name) } ic.byName[name] = im } if increment { ic.list = append(ic.list, im) } ic.currentName = name return im, nil } func (ic *imageContexts) update(imageID string, runConfig *container.Config) { ic.list[len(ic.list)-1].id = imageID ic.list[len(ic.list)-1].runConfig = runConfig } func (ic *imageContexts) validate(i int) error { if i < 0 || i >= len(ic.list)-1 { var extraMsg string if i == len(ic.list)-1 { extraMsg = " refers current build block" } return errors.Errorf("invalid from flag value %d%s", i, extraMsg) } return nil } func (ic *imageContexts) get(indexOrName string) (*imageMount, error) { index, err := strconv.Atoi(indexOrName) if err == nil { if err := ic.validate(index); err != nil { return nil, err } return ic.list[index], nil } if im, ok := ic.byName[strings.ToLower(indexOrName)]; ok { return im, nil } im, err := mountByRef(ic.b, indexOrName) if err != nil { return nil, errors.Wrapf(err, "invalid from flag value %s", indexOrName) } return im, nil } func (ic *imageContexts) unmount() (retErr error) { for _, im := range ic.list { if err := im.unmount(); err != nil { logrus.Error(err) retErr = err } } for _, im := range ic.byName { if err := im.unmount(); err != nil { logrus.Error(err) retErr = err } } return } func (ic *imageContexts) isCurrentTarget(target string) bool { if target == "" { return false } return strings.EqualFold(ic.currentName, target) } func (ic *imageContexts) getCache(id, path string) (interface{}, bool) { if ic.cache != nil { if id == "" { return nil, false } return ic.cache.get(id + path) } return nil, false } func (ic *imageContexts) setCache(id, path string, v interface{}) { if ic.cache != nil { ic.cache.set(id+path, v) } } // imageMount is a reference for getting access to a buildcontext that is backed // by an existing image type imageMount struct { id string ctx builder.Context release func() error ic *imageContexts runConfig *container.Config } func (im *imageMount) context() (builder.Context, error) { if im.ctx == nil { if im.id == "" { return nil, errors.Errorf("could not copy from empty context") } p, release, err := im.ic.b.docker.MountImage(im.id) if err != nil { return nil, errors.Wrapf(err, "failed to mount %s", im.id) } ctx, err := remotecontext.NewLazyContext(p) if err != nil { return nil, errors.Wrapf(err, "failed to create lazycontext for %s", p) } im.release = release im.ctx = ctx } return im.ctx, nil } func (im *imageMount) unmount() error { if im.release != nil { if err := im.release(); err != nil { return errors.Wrapf(err, "failed to unmount previous build image %s", im.id) } im.release = nil } return nil } func (im *imageMount) ImageID() string { return im.id } func (im *imageMount) RunConfig() *container.Config { return im.runConfig } type pathCache struct { mu sync.Mutex items map[string]interface{} } func (c *pathCache) set(k string, v interface{}) { c.mu.Lock() if c.items == nil { c.items = make(map[string]interface{}) } c.items[k] = v c.mu.Unlock() } func (c *pathCache) get(k string) (interface{}, bool) { c.mu.Lock() if c.items == nil { c.mu.Unlock() return nil, false } v, ok := c.items[k] c.mu.Unlock() return v, ok }