Add support for named build stages
| ... | ... |
@@ -187,20 +187,16 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina |
| 187 | 187 |
return err |
| 188 | 188 |
} |
| 189 | 189 |
|
| 190 |
- var contextID *int |
|
| 190 |
+ var im *imageMount |
|
| 191 | 191 |
if flFrom.IsUsed() {
|
| 192 | 192 |
var err error |
| 193 |
- context, err := strconv.Atoi(flFrom.Value) |
|
| 193 |
+ im, err = b.imageContexts.get(flFrom.Value) |
|
| 194 | 194 |
if err != nil {
|
| 195 |
- return errors.Wrap(err, "from expects an integer value corresponding to the context number") |
|
| 196 |
- } |
|
| 197 |
- if err := b.imageContexts.validate(context); err != nil {
|
|
| 198 | 195 |
return err |
| 199 | 196 |
} |
| 200 |
- contextID = &context |
|
| 201 | 197 |
} |
| 202 | 198 |
|
| 203 |
- return b.runContextCommand(args, false, false, "COPY", contextID) |
|
| 199 |
+ return b.runContextCommand(args, false, false, "COPY", im) |
|
| 204 | 200 |
} |
| 205 | 201 |
|
| 206 | 202 |
// FROM imagename |
| ... | ... |
@@ -208,7 +204,13 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina |
| 208 | 208 |
// This sets the image the dockerfile will build on top of. |
| 209 | 209 |
// |
| 210 | 210 |
func from(b *Builder, args []string, attributes map[string]bool, original string) error {
|
| 211 |
- if len(args) != 1 {
|
|
| 211 |
+ ctxName := "" |
|
| 212 |
+ if len(args) == 3 && strings.EqualFold(args[1], "as") {
|
|
| 213 |
+ ctxName = strings.ToLower(args[2]) |
|
| 214 |
+ if ok, _ := regexp.MatchString("^[a-z][a-z0-9-_\\.]*$", ctxName); !ok {
|
|
| 215 |
+ return errors.Errorf("invalid name for build stage: %q, name can't start with a number or contain symbols", ctxName)
|
|
| 216 |
+ } |
|
| 217 |
+ } else if len(args) != 1 {
|
|
| 212 | 218 |
return errExactlyOneArgument("FROM")
|
| 213 | 219 |
} |
| 214 | 220 |
|
| ... | ... |
@@ -221,29 +223,32 @@ func from(b *Builder, args []string, attributes map[string]bool, original string |
| 221 | 221 |
var image builder.Image |
| 222 | 222 |
|
| 223 | 223 |
b.resetImageCache() |
| 224 |
- b.imageContexts.new() |
|
| 224 |
+ if _, err := b.imageContexts.new(ctxName, true); err != nil {
|
|
| 225 |
+ return err |
|
| 226 |
+ } |
|
| 225 | 227 |
|
| 226 |
- // Windows cannot support a container with no base image. |
|
| 227 |
- if name == api.NoBaseImageSpecifier {
|
|
| 228 |
- if runtime.GOOS == "windows" {
|
|
| 229 |
- return errors.New("Windows does not support FROM scratch")
|
|
| 228 |
+ if im, ok := b.imageContexts.byName[name]; ok {
|
|
| 229 |
+ if len(im.ImageID()) > 0 {
|
|
| 230 |
+ image = im |
|
| 230 | 231 |
} |
| 231 |
- b.image = "" |
|
| 232 |
- b.noBaseImage = true |
|
| 233 | 232 |
} else {
|
| 234 |
- // TODO: don't use `name`, instead resolve it to a digest |
|
| 235 |
- if !b.options.PullParent {
|
|
| 236 |
- image, _ = b.docker.GetImageOnBuild(name) |
|
| 237 |
- // TODO: shouldn't we error out if error is different from "not found" ? |
|
| 238 |
- } |
|
| 239 |
- if image == nil {
|
|
| 233 |
+ // Windows cannot support a container with no base image. |
|
| 234 |
+ if name == api.NoBaseImageSpecifier {
|
|
| 235 |
+ if runtime.GOOS == "windows" {
|
|
| 236 |
+ return errors.New("Windows does not support FROM scratch")
|
|
| 237 |
+ } |
|
| 238 |
+ b.image = "" |
|
| 239 |
+ b.noBaseImage = true |
|
| 240 |
+ } else {
|
|
| 240 | 241 |
var err error |
| 241 |
- image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output) |
|
| 242 |
+ image, err = pullOrGetImage(b, name) |
|
| 242 | 243 |
if err != nil {
|
| 243 | 244 |
return err |
| 244 | 245 |
} |
| 245 | 246 |
} |
| 246 |
- b.imageContexts.update(image.ImageID()) |
|
| 247 |
+ } |
|
| 248 |
+ if image != nil {
|
|
| 249 |
+ b.imageContexts.update(image.ImageID(), image.RunConfig()) |
|
| 247 | 250 |
} |
| 248 | 251 |
b.from = image |
| 249 | 252 |
|
| ... | ... |
@@ -831,3 +836,33 @@ func getShell(c *container.Config) []string {
|
| 831 | 831 |
} |
| 832 | 832 |
return append([]string{}, c.Shell[:]...)
|
| 833 | 833 |
} |
| 834 |
+ |
|
| 835 |
+// mountByRef creates an imageMount from a reference. pulling the image if needed. |
|
| 836 |
+func mountByRef(b *Builder, name string) (*imageMount, error) {
|
|
| 837 |
+ image, err := pullOrGetImage(b, name) |
|
| 838 |
+ if err != nil {
|
|
| 839 |
+ return nil, err |
|
| 840 |
+ } |
|
| 841 |
+ im, err := b.imageContexts.new("", false)
|
|
| 842 |
+ if err != nil {
|
|
| 843 |
+ return nil, err |
|
| 844 |
+ } |
|
| 845 |
+ im.id = image.ImageID() |
|
| 846 |
+ return im, nil |
|
| 847 |
+} |
|
| 848 |
+ |
|
| 849 |
+func pullOrGetImage(b *Builder, name string) (builder.Image, error) {
|
|
| 850 |
+ var image builder.Image |
|
| 851 |
+ if !b.options.PullParent {
|
|
| 852 |
+ image, _ = b.docker.GetImageOnBuild(name) |
|
| 853 |
+ // TODO: shouldn't we error out if error is different from "not found" ? |
|
| 854 |
+ } |
|
| 855 |
+ if image == nil {
|
|
| 856 |
+ var err error |
|
| 857 |
+ image, err = b.docker.PullOnBuild(b.clientCtx, name, b.options.AuthConfigs, b.Output) |
|
| 858 |
+ if err != nil {
|
|
| 859 |
+ return nil, err |
|
| 860 |
+ } |
|
| 861 |
+ } |
|
| 862 |
+ return image, nil |
|
| 863 |
+} |
| ... | ... |
@@ -1,9 +1,12 @@ |
| 1 | 1 |
package dockerfile |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "strconv" |
|
| 5 |
+ "strings" |
|
| 4 | 6 |
"sync" |
| 5 | 7 |
|
| 6 | 8 |
"github.com/Sirupsen/logrus" |
| 9 |
+ "github.com/docker/docker/api/types/container" |
|
| 7 | 10 |
"github.com/docker/docker/builder" |
| 8 | 11 |
"github.com/docker/docker/builder/remotecontext" |
| 9 | 12 |
"github.com/pkg/errors" |
| ... | ... |
@@ -12,23 +15,32 @@ import ( |
| 12 | 12 |
// imageContexts is a helper for stacking up built image rootfs and reusing |
| 13 | 13 |
// them as contexts |
| 14 | 14 |
type imageContexts struct {
|
| 15 |
- b *Builder |
|
| 16 |
- list []*imageMount |
|
| 17 |
- cache *pathCache |
|
| 15 |
+ b *Builder |
|
| 16 |
+ list []*imageMount |
|
| 17 |
+ byName map[string]*imageMount |
|
| 18 |
+ cache *pathCache |
|
| 18 | 19 |
} |
| 19 | 20 |
|
| 20 |
-type imageMount struct {
|
|
| 21 |
- id string |
|
| 22 |
- ctx builder.Context |
|
| 23 |
- release func() error |
|
| 24 |
-} |
|
| 25 |
- |
|
| 26 |
-func (ic *imageContexts) new() {
|
|
| 27 |
- ic.list = append(ic.list, &imageMount{})
|
|
| 21 |
+func (ic *imageContexts) new(name string, increment bool) (*imageMount, error) {
|
|
| 22 |
+ im := &imageMount{ic: ic}
|
|
| 23 |
+ if len(name) > 0 {
|
|
| 24 |
+ if ic.byName == nil {
|
|
| 25 |
+ ic.byName = make(map[string]*imageMount) |
|
| 26 |
+ } |
|
| 27 |
+ if _, ok := ic.byName[name]; ok {
|
|
| 28 |
+ return nil, errors.Errorf("duplicate name %s", name)
|
|
| 29 |
+ } |
|
| 30 |
+ ic.byName[name] = im |
|
| 31 |
+ } |
|
| 32 |
+ if increment {
|
|
| 33 |
+ ic.list = append(ic.list, im) |
|
| 34 |
+ } |
|
| 35 |
+ return im, nil |
|
| 28 | 36 |
} |
| 29 | 37 |
|
| 30 |
-func (ic *imageContexts) update(imageID string) {
|
|
| 38 |
+func (ic *imageContexts) update(imageID string, runConfig *container.Config) {
|
|
| 31 | 39 |
ic.list[len(ic.list)-1].id = imageID |
| 40 |
+ ic.list[len(ic.list)-1].runConfig = runConfig |
|
| 32 | 41 |
} |
| 33 | 42 |
|
| 34 | 43 |
func (ic *imageContexts) validate(i int) error {
|
| ... | ... |
@@ -42,16 +54,72 @@ func (ic *imageContexts) validate(i int) error {
|
| 42 | 42 |
return nil |
| 43 | 43 |
} |
| 44 | 44 |
|
| 45 |
-func (ic *imageContexts) context(i int) (builder.Context, error) {
|
|
| 46 |
- if err := ic.validate(i); err != nil {
|
|
| 47 |
- return nil, err |
|
| 45 |
+func (ic *imageContexts) get(indexOrName string) (*imageMount, error) {
|
|
| 46 |
+ index, err := strconv.Atoi(indexOrName) |
|
| 47 |
+ if err == nil {
|
|
| 48 |
+ if err := ic.validate(index); err != nil {
|
|
| 49 |
+ return nil, err |
|
| 50 |
+ } |
|
| 51 |
+ return ic.list[index], nil |
|
| 52 |
+ } |
|
| 53 |
+ if im, ok := ic.byName[strings.ToLower(indexOrName)]; ok {
|
|
| 54 |
+ return im, nil |
|
| 55 |
+ } |
|
| 56 |
+ im, err := mountByRef(ic.b, indexOrName) |
|
| 57 |
+ if err != nil {
|
|
| 58 |
+ return nil, errors.Wrapf(err, "invalid from flag value %s", indexOrName) |
|
| 59 |
+ } |
|
| 60 |
+ return im, nil |
|
| 61 |
+} |
|
| 62 |
+ |
|
| 63 |
+func (ic *imageContexts) unmount() (retErr error) {
|
|
| 64 |
+ for _, im := range ic.list {
|
|
| 65 |
+ if err := im.unmount(); err != nil {
|
|
| 66 |
+ logrus.Error(err) |
|
| 67 |
+ retErr = err |
|
| 68 |
+ } |
|
| 69 |
+ } |
|
| 70 |
+ for _, im := range ic.byName {
|
|
| 71 |
+ if err := im.unmount(); err != nil {
|
|
| 72 |
+ logrus.Error(err) |
|
| 73 |
+ retErr = err |
|
| 74 |
+ } |
|
| 75 |
+ } |
|
| 76 |
+ return |
|
| 77 |
+} |
|
| 78 |
+ |
|
| 79 |
+func (ic *imageContexts) getCache(id, path string) (interface{}, bool) {
|
|
| 80 |
+ if ic.cache != nil {
|
|
| 81 |
+ if id == "" {
|
|
| 82 |
+ return nil, false |
|
| 83 |
+ } |
|
| 84 |
+ return ic.cache.get(id + path) |
|
| 85 |
+ } |
|
| 86 |
+ return nil, false |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+func (ic *imageContexts) setCache(id, path string, v interface{}) {
|
|
| 90 |
+ if ic.cache != nil {
|
|
| 91 |
+ ic.cache.set(id+path, v) |
|
| 48 | 92 |
} |
| 49 |
- im := ic.list[i] |
|
| 93 |
+} |
|
| 94 |
+ |
|
| 95 |
+// imageMount is a reference for getting access to a buildcontext that is backed |
|
| 96 |
+// by an existing image |
|
| 97 |
+type imageMount struct {
|
|
| 98 |
+ id string |
|
| 99 |
+ ctx builder.Context |
|
| 100 |
+ release func() error |
|
| 101 |
+ ic *imageContexts |
|
| 102 |
+ runConfig *container.Config |
|
| 103 |
+} |
|
| 104 |
+ |
|
| 105 |
+func (im *imageMount) context() (builder.Context, error) {
|
|
| 50 | 106 |
if im.ctx == nil {
|
| 51 | 107 |
if im.id == "" {
|
| 52 | 108 |
return nil, errors.Errorf("could not copy from empty context")
|
| 53 | 109 |
} |
| 54 |
- p, release, err := ic.b.docker.MountImage(im.id) |
|
| 110 |
+ p, release, err := im.ic.b.docker.MountImage(im.id) |
|
| 55 | 111 |
if err != nil {
|
| 56 | 112 |
return nil, errors.Wrapf(err, "failed to mount %s", im.id) |
| 57 | 113 |
} |
| ... | ... |
@@ -59,40 +127,27 @@ func (ic *imageContexts) context(i int) (builder.Context, error) {
|
| 59 | 59 |
if err != nil {
|
| 60 | 60 |
return nil, errors.Wrapf(err, "failed to create lazycontext for %s", p) |
| 61 | 61 |
} |
| 62 |
- logrus.Debugf("mounted image: %s %s", im.id, p)
|
|
| 63 | 62 |
im.release = release |
| 64 | 63 |
im.ctx = ctx |
| 65 | 64 |
} |
| 66 | 65 |
return im.ctx, nil |
| 67 | 66 |
} |
| 68 | 67 |
|
| 69 |
-func (ic *imageContexts) unmount() (retErr error) {
|
|
| 70 |
- for _, im := range ic.list {
|
|
| 71 |
- if im.release != nil {
|
|
| 72 |
- if err := im.release(); err != nil {
|
|
| 73 |
- logrus.Error(errors.Wrapf(err, "failed to unmount previous build image")) |
|
| 74 |
- retErr = err |
|
| 75 |
- } |
|
| 68 |
+func (im *imageMount) unmount() error {
|
|
| 69 |
+ if im.release != nil {
|
|
| 70 |
+ if err := im.release(); err != nil {
|
|
| 71 |
+ return errors.Wrapf(err, "failed to unmount previous build image %s", im.id) |
|
| 76 | 72 |
} |
| 73 |
+ im.release = nil |
|
| 77 | 74 |
} |
| 78 |
- return |
|
| 75 |
+ return nil |
|
| 79 | 76 |
} |
| 80 | 77 |
|
| 81 |
-func (ic *imageContexts) getCache(i int, path string) (interface{}, bool) {
|
|
| 82 |
- if ic.cache != nil {
|
|
| 83 |
- im := ic.list[i] |
|
| 84 |
- if im.id == "" {
|
|
| 85 |
- return nil, false |
|
| 86 |
- } |
|
| 87 |
- return ic.cache.get(im.id + path) |
|
| 88 |
- } |
|
| 89 |
- return nil, false |
|
| 78 |
+func (im *imageMount) ImageID() string {
|
|
| 79 |
+ return im.id |
|
| 90 | 80 |
} |
| 91 |
- |
|
| 92 |
-func (ic *imageContexts) setCache(i int, path string, v interface{}) {
|
|
| 93 |
- if ic.cache != nil {
|
|
| 94 |
- ic.cache.set(ic.list[i].id+path, v) |
|
| 95 |
- } |
|
| 81 |
+func (im *imageMount) RunConfig() *container.Config {
|
|
| 82 |
+ return im.runConfig |
|
| 96 | 83 |
} |
| 97 | 84 |
|
| 98 | 85 |
type pathCache struct {
|
| ... | ... |
@@ -85,7 +85,7 @@ func (b *Builder) commit(id string, autoCmd strslice.StrSlice, comment string) e |
| 85 | 85 |
} |
| 86 | 86 |
|
| 87 | 87 |
b.image = imageID |
| 88 |
- b.imageContexts.update(imageID) |
|
| 88 |
+ b.imageContexts.update(imageID, &autoConfig) |
|
| 89 | 89 |
return nil |
| 90 | 90 |
} |
| 91 | 91 |
|
| ... | ... |
@@ -94,7 +94,7 @@ type copyInfo struct {
|
| 94 | 94 |
decompress bool |
| 95 | 95 |
} |
| 96 | 96 |
|
| 97 |
-func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string, contextID *int) error {
|
|
| 97 |
+func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalDecompression bool, cmdName string, imageSource *imageMount) error {
|
|
| 98 | 98 |
if len(args) < 2 {
|
| 99 | 99 |
return fmt.Errorf("Invalid %s format - at least two arguments required", cmdName)
|
| 100 | 100 |
} |
| ... | ... |
@@ -128,7 +128,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowLocalD |
| 128 | 128 |
continue |
| 129 | 129 |
} |
| 130 | 130 |
// not a URL |
| 131 |
- subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true, contextID) |
|
| 131 |
+ subInfos, err := b.calcCopyInfo(cmdName, orig, allowLocalDecompression, true, imageSource) |
|
| 132 | 132 |
if err != nil {
|
| 133 | 133 |
return err |
| 134 | 134 |
} |
| ... | ... |
@@ -298,13 +298,13 @@ func (b *Builder) download(srcURL string) (fi builder.FileInfo, err error) {
|
| 298 | 298 |
return &builder.HashedFileInfo{FileInfo: builder.PathFileInfo{FileInfo: tmpFileSt, FilePath: tmpFileName}, FileHash: hash}, nil
|
| 299 | 299 |
} |
| 300 | 300 |
|
| 301 |
-func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression, allowWildcards bool, contextID *int) ([]copyInfo, error) {
|
|
| 301 |
+func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression, allowWildcards bool, imageSource *imageMount) ([]copyInfo, error) {
|
|
| 302 | 302 |
|
| 303 | 303 |
// Work in daemon-specific OS filepath semantics |
| 304 | 304 |
origPath = filepath.FromSlash(origPath) |
| 305 | 305 |
|
| 306 | 306 |
// validate windows paths from other images |
| 307 |
- if contextID != nil && runtime.GOOS == "windows" {
|
|
| 307 |
+ if imageSource != nil && runtime.GOOS == "windows" {
|
|
| 308 | 308 |
forbid := regexp.MustCompile("(?i)^" + string(os.PathSeparator) + "?(windows(" + string(os.PathSeparator) + ".+)?)?$")
|
| 309 | 309 |
if p := filepath.Clean(origPath); p == "." || forbid.MatchString(p) {
|
| 310 | 310 |
return nil, errors.Errorf("copy from %s is not allowed on windows", origPath)
|
| ... | ... |
@@ -318,8 +318,8 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression |
| 318 | 318 |
|
| 319 | 319 |
context := b.context |
| 320 | 320 |
var err error |
| 321 |
- if contextID != nil {
|
|
| 322 |
- context, err = b.imageContexts.context(*contextID) |
|
| 321 |
+ if imageSource != nil {
|
|
| 322 |
+ context, err = imageSource.context() |
|
| 323 | 323 |
if err != nil {
|
| 324 | 324 |
return nil, err |
| 325 | 325 |
} |
| ... | ... |
@@ -346,7 +346,7 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression |
| 346 | 346 |
|
| 347 | 347 |
// Note we set allowWildcards to false in case the name has |
| 348 | 348 |
// a * in it |
| 349 |
- subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false, contextID) |
|
| 349 |
+ subInfos, err := b.calcCopyInfo(cmdName, path, allowLocalDecompression, false, imageSource) |
|
| 350 | 350 |
if err != nil {
|
| 351 | 351 |
return err |
| 352 | 352 |
} |
| ... | ... |
@@ -370,9 +370,9 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression |
| 370 | 370 |
if !handleHash {
|
| 371 | 371 |
return copyInfos, nil |
| 372 | 372 |
} |
| 373 |
- if contextID != nil {
|
|
| 373 |
+ if imageSource != nil {
|
|
| 374 | 374 |
// fast-cache based on imageID |
| 375 |
- if h, ok := b.imageContexts.getCache(*contextID, origPath); ok {
|
|
| 375 |
+ if h, ok := b.imageContexts.getCache(imageSource.id, origPath); ok {
|
|
| 376 | 376 |
hfi.SetHash(h.(string)) |
| 377 | 377 |
return copyInfos, nil |
| 378 | 378 |
} |
| ... | ... |
@@ -401,8 +401,8 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression |
| 401 | 401 |
hasher := sha256.New() |
| 402 | 402 |
hasher.Write([]byte(strings.Join(subfiles, ","))) |
| 403 | 403 |
hfi.SetHash("dir:" + hex.EncodeToString(hasher.Sum(nil)))
|
| 404 |
- if contextID != nil {
|
|
| 405 |
- b.imageContexts.setCache(*contextID, origPath, hfi.Hash()) |
|
| 404 |
+ if imageSource != nil {
|
|
| 405 |
+ b.imageContexts.setCache(imageSource.id, origPath, hfi.Hash()) |
|
| 406 | 406 |
} |
| 407 | 407 |
|
| 408 | 408 |
return copyInfos, nil |
| ... | ... |
@@ -497,7 +497,7 @@ func (b *Builder) probeCache() (bool, error) {
|
| 497 | 497 |
fmt.Fprint(b.Stdout, " ---> Using cache\n") |
| 498 | 498 |
logrus.Debugf("[BUILDER] Use cached version: %s", b.runConfig.Cmd)
|
| 499 | 499 |
b.image = string(cache) |
| 500 |
- b.imageContexts.update(b.image) |
|
| 500 |
+ b.imageContexts.update(b.image, b.runConfig) |
|
| 501 | 501 |
|
| 502 | 502 |
return true, nil |
| 503 | 503 |
} |
| ... | ... |
@@ -106,7 +106,7 @@ func init() {
|
| 106 | 106 |
command.Entrypoint: parseMaybeJSON, |
| 107 | 107 |
command.Env: parseEnv, |
| 108 | 108 |
command.Expose: parseStringsWhitespaceDelimited, |
| 109 |
- command.From: parseString, |
|
| 109 |
+ command.From: parseStringsWhitespaceDelimited, |
|
| 110 | 110 |
command.Healthcheck: parseHealthConfig, |
| 111 | 111 |
command.Label: parseLabel, |
| 112 | 112 |
command.Maintainer: parseString, |
| ... | ... |
@@ -5752,7 +5752,7 @@ func (s *DockerSuite) TestBuildContChar(c *check.C) {
|
| 5752 | 5752 |
|
| 5753 | 5753 |
func (s *DockerSuite) TestBuildCopyFromPreviousRootFS(c *check.C) {
|
| 5754 | 5754 |
dockerfile := ` |
| 5755 |
- FROM busybox |
|
| 5755 |
+ FROM busybox AS first |
|
| 5756 | 5756 |
COPY foo bar |
| 5757 | 5757 |
FROM busybox |
| 5758 | 5758 |
%s |
| ... | ... |
@@ -5762,7 +5762,8 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFS(c *check.C) {
|
| 5762 | 5762 |
COPY bar / |
| 5763 | 5763 |
COPY --from=1 baz sub/ |
| 5764 | 5764 |
COPY --from=0 bar baz |
| 5765 |
- COPY --from=0 bar bay` |
|
| 5765 |
+ COPY --from=first bar bay` |
|
| 5766 |
+ |
|
| 5766 | 5767 |
ctx := fakeContext(c, fmt.Sprintf(dockerfile, ""), map[string]string{
|
| 5767 | 5768 |
"Dockerfile": dockerfile, |
| 5768 | 5769 |
"foo": "abc", |
| ... | ... |
@@ -5830,7 +5831,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) {
|
| 5830 | 5830 |
|
| 5831 | 5831 |
buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{
|
| 5832 | 5832 |
ExitCode: 1, |
| 5833 |
- Err: "from expects an integer value corresponding to the context number", |
|
| 5833 |
+ Err: "invalid from flag value foo", |
|
| 5834 | 5834 |
}) |
| 5835 | 5835 |
|
| 5836 | 5836 |
dockerfile = ` |
| ... | ... |
@@ -5847,6 +5848,36 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) {
|
| 5847 | 5847 |
ExitCode: 1, |
| 5848 | 5848 |
Err: "invalid from flag value 0 refers current build block", |
| 5849 | 5849 |
}) |
| 5850 |
+ |
|
| 5851 |
+ dockerfile = ` |
|
| 5852 |
+ FROM busybox AS foo |
|
| 5853 |
+ COPY --from=bar foo bar` |
|
| 5854 |
+ |
|
| 5855 |
+ ctx = fakeContext(c, dockerfile, map[string]string{
|
|
| 5856 |
+ "Dockerfile": dockerfile, |
|
| 5857 |
+ "foo": "abc", |
|
| 5858 |
+ }) |
|
| 5859 |
+ defer ctx.Close() |
|
| 5860 |
+ |
|
| 5861 |
+ buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{
|
|
| 5862 |
+ ExitCode: 1, |
|
| 5863 |
+ Err: "invalid from flag value bar", |
|
| 5864 |
+ }) |
|
| 5865 |
+ |
|
| 5866 |
+ dockerfile = ` |
|
| 5867 |
+ FROM busybox AS 1 |
|
| 5868 |
+ COPY --from=1 foo bar` |
|
| 5869 |
+ |
|
| 5870 |
+ ctx = fakeContext(c, dockerfile, map[string]string{
|
|
| 5871 |
+ "Dockerfile": dockerfile, |
|
| 5872 |
+ "foo": "abc", |
|
| 5873 |
+ }) |
|
| 5874 |
+ defer ctx.Close() |
|
| 5875 |
+ |
|
| 5876 |
+ buildImage("build1", withExternalBuildContext(ctx)).Assert(c, icmd.Expected{
|
|
| 5877 |
+ ExitCode: 1, |
|
| 5878 |
+ Err: "invalid name for build stage", |
|
| 5879 |
+ }) |
|
| 5850 | 5880 |
} |
| 5851 | 5881 |
|
| 5852 | 5882 |
func (s *DockerSuite) TestBuildCopyFromPreviousFrom(c *check.C) {
|
| ... | ... |
@@ -5863,9 +5894,9 @@ func (s *DockerSuite) TestBuildCopyFromPreviousFrom(c *check.C) {
|
| 5863 | 5863 |
result.Assert(c, icmd.Success) |
| 5864 | 5864 |
|
| 5865 | 5865 |
dockerfile = ` |
| 5866 |
- FROM build1:latest |
|
| 5866 |
+ FROM build1:latest AS foo |
|
| 5867 | 5867 |
FROM busybox |
| 5868 |
- COPY --from=0 bar / |
|
| 5868 |
+ COPY --from=foo bar / |
|
| 5869 | 5869 |
COPY foo /` |
| 5870 | 5870 |
ctx = fakeContext(c, dockerfile, map[string]string{
|
| 5871 | 5871 |
"Dockerfile": dockerfile, |
| ... | ... |
@@ -5882,6 +5913,117 @@ func (s *DockerSuite) TestBuildCopyFromPreviousFrom(c *check.C) {
|
| 5882 | 5882 |
c.Assert(strings.TrimSpace(out), check.Equals, "def") |
| 5883 | 5883 |
} |
| 5884 | 5884 |
|
| 5885 |
+func (s *DockerSuite) TestBuildCopyFromImplicitFrom(c *check.C) {
|
|
| 5886 |
+ dockerfile := ` |
|
| 5887 |
+ FROM busybox |
|
| 5888 |
+ COPY --from=busybox /etc/passwd /mypasswd |
|
| 5889 |
+ RUN cmp /etc/passwd /mypasswd` |
|
| 5890 |
+ |
|
| 5891 |
+ if DaemonIsWindows() {
|
|
| 5892 |
+ dockerfile = ` |
|
| 5893 |
+ FROM busybox |
|
| 5894 |
+ COPY --from=busybox License.txt foo` |
|
| 5895 |
+ } |
|
| 5896 |
+ |
|
| 5897 |
+ ctx := fakeContext(c, dockerfile, map[string]string{
|
|
| 5898 |
+ "Dockerfile": dockerfile, |
|
| 5899 |
+ }) |
|
| 5900 |
+ defer ctx.Close() |
|
| 5901 |
+ |
|
| 5902 |
+ result := buildImage("build1", withExternalBuildContext(ctx))
|
|
| 5903 |
+ result.Assert(c, icmd.Success) |
|
| 5904 |
+ |
|
| 5905 |
+ if DaemonIsWindows() {
|
|
| 5906 |
+ out, _ := dockerCmd(c, "run", "build1", "cat", "License.txt") |
|
| 5907 |
+ c.Assert(len(out), checker.GreaterThan, 10) |
|
| 5908 |
+ out2, _ := dockerCmd(c, "run", "build1", "cat", "foo") |
|
| 5909 |
+ c.Assert(out, check.Equals, out2) |
|
| 5910 |
+ } |
|
| 5911 |
+} |
|
| 5912 |
+ |
|
| 5913 |
+func (s *DockerRegistrySuite) TestBuildCopyFromImplicitPullingFrom(c *check.C) {
|
|
| 5914 |
+ repoName := fmt.Sprintf("%v/dockercli/testf", privateRegistryURL)
|
|
| 5915 |
+ |
|
| 5916 |
+ dockerfile := ` |
|
| 5917 |
+ FROM busybox |
|
| 5918 |
+ COPY foo bar` |
|
| 5919 |
+ ctx := fakeContext(c, dockerfile, map[string]string{
|
|
| 5920 |
+ "Dockerfile": dockerfile, |
|
| 5921 |
+ "foo": "abc", |
|
| 5922 |
+ }) |
|
| 5923 |
+ defer ctx.Close() |
|
| 5924 |
+ |
|
| 5925 |
+ result := buildImage(repoName, withExternalBuildContext(ctx)) |
|
| 5926 |
+ result.Assert(c, icmd.Success) |
|
| 5927 |
+ |
|
| 5928 |
+ dockerCmd(c, "push", repoName) |
|
| 5929 |
+ dockerCmd(c, "rmi", repoName) |
|
| 5930 |
+ |
|
| 5931 |
+ dockerfile = ` |
|
| 5932 |
+ FROM busybox |
|
| 5933 |
+ COPY --from=%s bar baz` |
|
| 5934 |
+ |
|
| 5935 |
+ ctx = fakeContext(c, fmt.Sprintf(dockerfile, repoName), map[string]string{
|
|
| 5936 |
+ "Dockerfile": dockerfile, |
|
| 5937 |
+ }) |
|
| 5938 |
+ defer ctx.Close() |
|
| 5939 |
+ |
|
| 5940 |
+ result = buildImage("build1", withExternalBuildContext(ctx))
|
|
| 5941 |
+ result.Assert(c, icmd.Success) |
|
| 5942 |
+ |
|
| 5943 |
+ dockerCmdWithResult("run", "build1", "cat", "baz").Assert(c, icmd.Expected{Out: "abc"})
|
|
| 5944 |
+} |
|
| 5945 |
+ |
|
| 5946 |
+func (s *DockerSuite) TestBuildFromPreviousBlock(c *check.C) {
|
|
| 5947 |
+ dockerfile := ` |
|
| 5948 |
+ FROM busybox as foo |
|
| 5949 |
+ COPY foo / |
|
| 5950 |
+ FROM foo as foo1 |
|
| 5951 |
+ RUN echo 1 >> foo |
|
| 5952 |
+ FROM foo as foO2 |
|
| 5953 |
+ RUN echo 2 >> foo |
|
| 5954 |
+ FROM foo |
|
| 5955 |
+ COPY --from=foo1 foo f1 |
|
| 5956 |
+ COPY --from=FOo2 foo f2 |
|
| 5957 |
+ ` // foo2 case also tests that names are canse insensitive |
|
| 5958 |
+ ctx := fakeContext(c, dockerfile, map[string]string{
|
|
| 5959 |
+ "Dockerfile": dockerfile, |
|
| 5960 |
+ "foo": "bar", |
|
| 5961 |
+ }) |
|
| 5962 |
+ defer ctx.Close() |
|
| 5963 |
+ |
|
| 5964 |
+ result := buildImage("build1", withExternalBuildContext(ctx))
|
|
| 5965 |
+ result.Assert(c, icmd.Success) |
|
| 5966 |
+ |
|
| 5967 |
+ dockerCmdWithResult("run", "build1", "cat", "foo").Assert(c, icmd.Expected{Out: "bar"})
|
|
| 5968 |
+ |
|
| 5969 |
+ dockerCmdWithResult("run", "build1", "cat", "f1").Assert(c, icmd.Expected{Out: "bar1"})
|
|
| 5970 |
+ |
|
| 5971 |
+ dockerCmdWithResult("run", "build1", "cat", "f2").Assert(c, icmd.Expected{Out: "bar2"})
|
|
| 5972 |
+} |
|
| 5973 |
+ |
|
| 5974 |
+func (s *DockerTrustSuite) TestCopyFromTrustedBuild(c *check.C) {
|
|
| 5975 |
+ img1 := s.setupTrustedImage(c, "trusted-build1") |
|
| 5976 |
+ img2 := s.setupTrustedImage(c, "trusted-build2") |
|
| 5977 |
+ dockerFile := fmt.Sprintf(` |
|
| 5978 |
+ FROM %s AS build-base |
|
| 5979 |
+ RUN echo ok > /foo |
|
| 5980 |
+ FROM %s |
|
| 5981 |
+ COPY --from=build-base foo bar`, img1, img2) |
|
| 5982 |
+ |
|
| 5983 |
+ name := "testcopyfromtrustedbuild" |
|
| 5984 |
+ |
|
| 5985 |
+ r := buildImage(name, trustedBuild, build.WithDockerfile(dockerFile)) |
|
| 5986 |
+ r.Assert(c, icmd.Expected{
|
|
| 5987 |
+ Out: fmt.Sprintf("FROM %s@sha", img1[:len(img1)-7]),
|
|
| 5988 |
+ }) |
|
| 5989 |
+ r.Assert(c, icmd.Expected{
|
|
| 5990 |
+ Out: fmt.Sprintf("FROM %s@sha", img2[:len(img2)-7]),
|
|
| 5991 |
+ }) |
|
| 5992 |
+ |
|
| 5993 |
+ dockerCmdWithResult("run", name, "cat", "bar").Assert(c, icmd.Expected{Out: "ok"})
|
|
| 5994 |
+} |
|
| 5995 |
+ |
|
| 5885 | 5996 |
// TestBuildOpaqueDirectory tests that a build succeeds which |
| 5886 | 5997 |
// creates opaque directories. |
| 5887 | 5998 |
// See https://github.com/docker/docker/issues/25244 |