Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -189,15 +189,20 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina |
| 189 | 189 |
|
| 190 | 190 |
var contextID *int |
| 191 | 191 |
if flFrom.IsUsed() {
|
| 192 |
- var err error |
|
| 193 |
- context, err := strconv.Atoi(flFrom.Value) |
|
| 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 |
- return err |
|
| 192 |
+ flFrom.Value = strings.ToLower(flFrom.Value) |
|
| 193 |
+ if context, ok := b.imageContexts.byName[flFrom.Value]; ok {
|
|
| 194 |
+ contextID = &context |
|
| 195 |
+ } else {
|
|
| 196 |
+ var err error |
|
| 197 |
+ context, err := strconv.Atoi(flFrom.Value) |
|
| 198 |
+ if err != nil {
|
|
| 199 |
+ return errors.Wrap(err, "from expects an integer value corresponding to the context number") |
|
| 200 |
+ } |
|
| 201 |
+ if err := b.imageContexts.validate(context); err != nil {
|
|
| 202 |
+ return err |
|
| 203 |
+ } |
|
| 204 |
+ contextID = &context |
|
| 199 | 205 |
} |
| 200 |
- contextID = &context |
|
| 201 | 206 |
} |
| 202 | 207 |
|
| 203 | 208 |
return b.runContextCommand(args, false, false, "COPY", contextID) |
| ... | ... |
@@ -208,7 +213,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,7 +232,9 @@ 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); err != nil {
|
|
| 225 |
+ return err |
|
| 226 |
+ } |
|
| 225 | 227 |
|
| 226 | 228 |
// Windows cannot support a container with no base image. |
| 227 | 229 |
if name == api.NoBaseImageSpecifier {
|
| ... | ... |
@@ -12,9 +12,10 @@ 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]int |
|
| 18 |
+ cache *pathCache |
|
| 18 | 19 |
} |
| 19 | 20 |
|
| 20 | 21 |
type imageMount struct {
|
| ... | ... |
@@ -23,8 +24,18 @@ type imageMount struct {
|
| 23 | 23 |
release func() error |
| 24 | 24 |
} |
| 25 | 25 |
|
| 26 |
-func (ic *imageContexts) new() {
|
|
| 26 |
+func (ic *imageContexts) new(name string) error {
|
|
| 27 |
+ if len(name) > 0 {
|
|
| 28 |
+ if ic.byName == nil {
|
|
| 29 |
+ ic.byName = make(map[string]int) |
|
| 30 |
+ } |
|
| 31 |
+ if _, ok := ic.byName[name]; ok {
|
|
| 32 |
+ return errors.Errorf("duplicate name %s", name)
|
|
| 33 |
+ } |
|
| 34 |
+ ic.byName[name] = len(ic.list) |
|
| 35 |
+ } |
|
| 27 | 36 |
ic.list = append(ic.list, &imageMount{})
|
| 37 |
+ return nil |
|
| 28 | 38 |
} |
| 29 | 39 |
|
| 30 | 40 |
func (ic *imageContexts) update(imageID string) {
|
| ... | ... |
@@ -80,7 +80,7 @@ func init() {
|
| 80 | 80 |
command.Entrypoint: parseMaybeJSON, |
| 81 | 81 |
command.Env: parseEnv, |
| 82 | 82 |
command.Expose: parseStringsWhitespaceDelimited, |
| 83 |
- command.From: parseString, |
|
| 83 |
+ command.From: parseStringsWhitespaceDelimited, |
|
| 84 | 84 |
command.Healthcheck: parseHealthConfig, |
| 85 | 85 |
command.Label: parseLabel, |
| 86 | 86 |
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", |
| ... | ... |
@@ -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 context 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, |