Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -23,6 +23,7 @@ import ( |
| 23 | 23 |
"github.com/docker/docker/api/types/backend" |
| 24 | 24 |
"github.com/docker/docker/api/types/container" |
| 25 | 25 |
"github.com/docker/docker/api/types/strslice" |
| 26 |
+ "github.com/docker/docker/builder/dockerfile/parser" |
|
| 26 | 27 |
"github.com/docker/docker/pkg/signal" |
| 27 | 28 |
"github.com/docker/go-connections/nat" |
| 28 | 29 |
"github.com/pkg/errors" |
| ... | ... |
@@ -196,24 +197,21 @@ func from(req dispatchRequest) error {
|
| 196 | 196 |
} |
| 197 | 197 |
|
| 198 | 198 |
req.builder.resetImageCache() |
| 199 |
- req.state.noBaseImage = false |
|
| 200 |
- req.state.stageName = stageName |
|
| 201 | 199 |
image, err := req.builder.getFromImage(req.shlex, req.args[0]) |
| 202 | 200 |
if err != nil {
|
| 203 | 201 |
return err |
| 204 | 202 |
} |
| 205 |
- if image == nil {
|
|
| 206 |
- req.state.imageID = "" |
|
| 207 |
- req.state.noBaseImage = true |
|
| 208 |
- image = newImageMount(nil, nil) |
|
| 209 |
- } |
|
| 210 | 203 |
if err := req.builder.imageContexts.add(stageName, image); err != nil {
|
| 211 | 204 |
return err |
| 212 | 205 |
} |
| 213 |
- req.state.baseImage = image |
|
| 214 |
- |
|
| 206 |
+ req.state.beginStage(stageName, image) |
|
| 215 | 207 |
req.builder.buildArgs.ResetAllowed() |
| 216 |
- return req.builder.processImageFrom(req.state, image) |
|
| 208 |
+ if image.ImageID() == "" {
|
|
| 209 |
+ // Typically this means they used "FROM scratch" |
|
| 210 |
+ return nil |
|
| 211 |
+ } |
|
| 212 |
+ |
|
| 213 |
+ return processOnBuild(req) |
|
| 217 | 214 |
} |
| 218 | 215 |
|
| 219 | 216 |
func parseBuildStageName(args []string) (string, error) {
|
| ... | ... |
@@ -243,11 +241,7 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (*imageMount, error |
| 243 | 243 |
} |
| 244 | 244 |
|
| 245 | 245 |
if im, ok := b.imageContexts.byName[name]; ok {
|
| 246 |
- if len(im.ImageID()) > 0 {
|
|
| 247 |
- return im, nil |
|
| 248 |
- } |
|
| 249 |
- // FROM scratch does not have an ImageID |
|
| 250 |
- return nil, nil |
|
| 246 |
+ return im, nil |
|
| 251 | 247 |
} |
| 252 | 248 |
|
| 253 | 249 |
// Windows cannot support a container with no base image. |
| ... | ... |
@@ -255,7 +249,7 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (*imageMount, error |
| 255 | 255 |
if runtime.GOOS == "windows" {
|
| 256 | 256 |
return nil, errors.New("Windows does not support FROM scratch")
|
| 257 | 257 |
} |
| 258 |
- return nil, nil |
|
| 258 |
+ return newImageMount(nil, nil), nil |
|
| 259 | 259 |
} |
| 260 | 260 |
return b.getImage(name) |
| 261 | 261 |
} |
| ... | ... |
@@ -272,6 +266,53 @@ func (b *Builder) getImage(name string) (*imageMount, error) {
|
| 272 | 272 |
return newImageMount(image, layer), nil |
| 273 | 273 |
} |
| 274 | 274 |
|
| 275 |
+func processOnBuild(req dispatchRequest) error {
|
|
| 276 |
+ dispatchState := req.state |
|
| 277 |
+ // Process ONBUILD triggers if they exist |
|
| 278 |
+ if nTriggers := len(dispatchState.runConfig.OnBuild); nTriggers != 0 {
|
|
| 279 |
+ word := "trigger" |
|
| 280 |
+ if nTriggers > 1 {
|
|
| 281 |
+ word = "triggers" |
|
| 282 |
+ } |
|
| 283 |
+ fmt.Fprintf(req.builder.Stderr, "# Executing %d build %s...\n", nTriggers, word) |
|
| 284 |
+ } |
|
| 285 |
+ |
|
| 286 |
+ // Copy the ONBUILD triggers, and remove them from the config, since the config will be committed. |
|
| 287 |
+ onBuildTriggers := dispatchState.runConfig.OnBuild |
|
| 288 |
+ dispatchState.runConfig.OnBuild = []string{}
|
|
| 289 |
+ |
|
| 290 |
+ // Reset stdin settings as all build actions run without stdin |
|
| 291 |
+ dispatchState.runConfig.OpenStdin = false |
|
| 292 |
+ dispatchState.runConfig.StdinOnce = false |
|
| 293 |
+ |
|
| 294 |
+ // parse the ONBUILD triggers by invoking the parser |
|
| 295 |
+ for _, step := range onBuildTriggers {
|
|
| 296 |
+ dockerfile, err := parser.Parse(strings.NewReader(step)) |
|
| 297 |
+ if err != nil {
|
|
| 298 |
+ return err |
|
| 299 |
+ } |
|
| 300 |
+ |
|
| 301 |
+ for _, n := range dockerfile.AST.Children {
|
|
| 302 |
+ if err := checkDispatch(n); err != nil {
|
|
| 303 |
+ return err |
|
| 304 |
+ } |
|
| 305 |
+ |
|
| 306 |
+ upperCasedCmd := strings.ToUpper(n.Value) |
|
| 307 |
+ switch upperCasedCmd {
|
|
| 308 |
+ case "ONBUILD": |
|
| 309 |
+ return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
|
| 310 |
+ case "MAINTAINER", "FROM": |
|
| 311 |
+ return errors.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd)
|
|
| 312 |
+ } |
|
| 313 |
+ } |
|
| 314 |
+ |
|
| 315 |
+ if _, err := dispatchFromDockerfile(req.builder, dockerfile, dispatchState); err != nil {
|
|
| 316 |
+ return err |
|
| 317 |
+ } |
|
| 318 |
+ } |
|
| 319 |
+ return nil |
|
| 320 |
+} |
|
| 321 |
+ |
|
| 275 | 322 |
// ONBUILD RUN echo yo |
| 276 | 323 |
// |
| 277 | 324 |
// ONBUILD triggers run when the image is used in a FROM statement. |
| ... | ... |
@@ -13,6 +13,7 @@ import ( |
| 13 | 13 |
"github.com/docker/docker/api/types/strslice" |
| 14 | 14 |
"github.com/docker/docker/builder" |
| 15 | 15 |
"github.com/docker/docker/builder/dockerfile/parser" |
| 16 |
+ "github.com/docker/docker/pkg/system" |
|
| 16 | 17 |
"github.com/docker/docker/pkg/testutil" |
| 17 | 18 |
"github.com/docker/go-connections/nat" |
| 18 | 19 |
"github.com/stretchr/testify/assert" |
| ... | ... |
@@ -192,8 +193,9 @@ func TestFromScratch(t *testing.T) {
|
| 192 | 192 |
} |
| 193 | 193 |
|
| 194 | 194 |
require.NoError(t, err) |
| 195 |
+ assert.True(t, req.state.hasFromImage()) |
|
| 195 | 196 |
assert.Equal(t, "", req.state.imageID) |
| 196 |
- assert.Equal(t, true, req.state.noBaseImage) |
|
| 197 |
+ assert.Equal(t, []string{"PATH=" + system.DefaultPathEnv}, req.state.runConfig.Env)
|
|
| 197 | 198 |
} |
| 198 | 199 |
|
| 199 | 200 |
func TestFromWithArg(t *testing.T) {
|
| ... | ... |
@@ -28,6 +28,7 @@ import ( |
| 28 | 28 |
"github.com/docker/docker/builder" |
| 29 | 29 |
"github.com/docker/docker/builder/dockerfile/command" |
| 30 | 30 |
"github.com/docker/docker/builder/dockerfile/parser" |
| 31 |
+ "github.com/docker/docker/pkg/system" |
|
| 31 | 32 |
"github.com/docker/docker/runconfig/opts" |
| 32 | 33 |
"github.com/pkg/errors" |
| 33 | 34 |
) |
| ... | ... |
@@ -184,37 +185,55 @@ type dispatchOptions struct {
|
| 184 | 184 |
|
| 185 | 185 |
// dispatchState is a data object which is modified by dispatchers |
| 186 | 186 |
type dispatchState struct {
|
| 187 |
- runConfig *container.Config |
|
| 188 |
- maintainer string |
|
| 189 |
- cmdSet bool |
|
| 190 |
- noBaseImage bool |
|
| 191 |
- imageID string |
|
| 192 |
- baseImage builder.Image |
|
| 193 |
- stageName string |
|
| 187 |
+ runConfig *container.Config |
|
| 188 |
+ maintainer string |
|
| 189 |
+ cmdSet bool |
|
| 190 |
+ imageID string |
|
| 191 |
+ baseImage builder.Image |
|
| 192 |
+ stageName string |
|
| 194 | 193 |
} |
| 195 | 194 |
|
| 196 | 195 |
func newDispatchState() *dispatchState {
|
| 197 | 196 |
return &dispatchState{runConfig: &container.Config{}}
|
| 198 | 197 |
} |
| 199 | 198 |
|
| 200 |
-func (r *dispatchState) updateRunConfig() {
|
|
| 201 |
- r.runConfig.Image = r.imageID |
|
| 199 |
+func (s *dispatchState) updateRunConfig() {
|
|
| 200 |
+ s.runConfig.Image = s.imageID |
|
| 202 | 201 |
} |
| 203 | 202 |
|
| 204 | 203 |
// hasFromImage returns true if the builder has processed a `FROM <image>` line |
| 205 |
-func (r *dispatchState) hasFromImage() bool {
|
|
| 206 |
- return r.imageID != "" || r.noBaseImage |
|
| 204 |
+func (s *dispatchState) hasFromImage() bool {
|
|
| 205 |
+ return s.imageID != "" || (s.baseImage != nil && s.baseImage.ImageID() == "") |
|
| 207 | 206 |
} |
| 208 | 207 |
|
| 209 |
-func (r *dispatchState) runConfigEnvMapping() map[string]string {
|
|
| 210 |
- return opts.ConvertKVStringsToMap(r.runConfig.Env) |
|
| 211 |
-} |
|
| 212 |
- |
|
| 213 |
-func (r *dispatchState) isCurrentStage(target string) bool {
|
|
| 208 |
+func (s *dispatchState) isCurrentStage(target string) bool {
|
|
| 214 | 209 |
if target == "" {
|
| 215 | 210 |
return false |
| 216 | 211 |
} |
| 217 |
- return strings.EqualFold(r.stageName, target) |
|
| 212 |
+ return strings.EqualFold(s.stageName, target) |
|
| 213 |
+} |
|
| 214 |
+ |
|
| 215 |
+func (s *dispatchState) beginStage(stageName string, image builder.Image) {
|
|
| 216 |
+ s.stageName = stageName |
|
| 217 |
+ s.imageID = image.ImageID() |
|
| 218 |
+ |
|
| 219 |
+ if image.RunConfig() != nil {
|
|
| 220 |
+ s.runConfig = image.RunConfig() |
|
| 221 |
+ } |
|
| 222 |
+ s.baseImage = image |
|
| 223 |
+ s.setDefaultPath() |
|
| 224 |
+} |
|
| 225 |
+ |
|
| 226 |
+// Add the default PATH to runConfig.ENV if one exists for the platform and there |
|
| 227 |
+// is no PATH set. Note that windows won't have one as it's set by HCS |
|
| 228 |
+func (s *dispatchState) setDefaultPath() {
|
|
| 229 |
+ if system.DefaultPathEnv == "" {
|
|
| 230 |
+ return |
|
| 231 |
+ } |
|
| 232 |
+ envMap := opts.ConvertKVStringsToMap(s.runConfig.Env) |
|
| 233 |
+ if _, ok := envMap["PATH"]; !ok {
|
|
| 234 |
+ s.runConfig.Env = append(s.runConfig.Env, "PATH="+system.DefaultPathEnv) |
|
| 235 |
+ } |
|
| 218 | 236 |
} |
| 219 | 237 |
|
| 220 | 238 |
func handleOnBuildNode(ast *parser.Node, msg *bytes.Buffer) (*parser.Node, []string, error) {
|
| ... | ... |
@@ -45,9 +45,9 @@ func (ic *imageContexts) update(imageID string, runConfig *container.Config) {
|
| 45 | 45 |
func (ic *imageContexts) validate(i int) error {
|
| 46 | 46 |
if i < 0 || i >= len(ic.list)-1 {
|
| 47 | 47 |
if i == len(ic.list)-1 {
|
| 48 |
- return errors.Errorf("%d refers to current build stage", i)
|
|
| 48 |
+ return errors.New("refers to current build stage")
|
|
| 49 | 49 |
} |
| 50 |
- return errors.Errorf("index out of bounds")
|
|
| 50 |
+ return errors.New("index out of bounds")
|
|
| 51 | 51 |
} |
| 52 | 52 |
return nil |
| 53 | 53 |
} |
| ... | ... |
@@ -122,7 +122,7 @@ func (im *imageMount) context() (builder.Source, error) {
|
| 122 | 122 |
if im.id == "" || im.layer == nil {
|
| 123 | 123 |
return nil, errors.Errorf("empty context")
|
| 124 | 124 |
} |
| 125 |
- mountPath, err := im.layer.Mount() |
|
| 125 |
+ mountPath, err := im.layer.Mount(im.id) |
|
| 126 | 126 |
if err != nil {
|
| 127 | 127 |
return nil, errors.Wrapf(err, "failed to mount %s", im.id) |
| 128 | 128 |
} |
| ... | ... |
@@ -136,6 +136,9 @@ func (im *imageMount) context() (builder.Source, error) {
|
| 136 | 136 |
} |
| 137 | 137 |
|
| 138 | 138 |
func (im *imageMount) unmount() error {
|
| 139 |
+ if im.layer == nil {
|
|
| 140 |
+ return nil |
|
| 141 |
+ } |
|
| 139 | 142 |
if err := im.layer.Release(); err != nil {
|
| 140 | 143 |
return errors.Wrapf(err, "failed to unmount previous build image %s", im.id) |
| 141 | 144 |
} |
| ... | ... |
@@ -22,7 +22,6 @@ import ( |
| 22 | 22 |
"github.com/docker/docker/api/types/backend" |
| 23 | 23 |
"github.com/docker/docker/api/types/container" |
| 24 | 24 |
"github.com/docker/docker/builder" |
| 25 |
- "github.com/docker/docker/builder/dockerfile/parser" |
|
| 26 | 25 |
"github.com/docker/docker/builder/remotecontext" |
| 27 | 26 |
"github.com/docker/docker/pkg/httputils" |
| 28 | 27 |
"github.com/docker/docker/pkg/ioutils" |
| ... | ... |
@@ -480,74 +479,6 @@ func (b *Builder) calcCopyInfo(cmdName, origPath string, allowLocalDecompression |
| 480 | 480 |
return copyInfos, nil |
| 481 | 481 |
} |
| 482 | 482 |
|
| 483 |
-func (b *Builder) processImageFrom(dispatchState *dispatchState, img builder.Image) error {
|
|
| 484 |
- if img != nil {
|
|
| 485 |
- dispatchState.imageID = img.ImageID() |
|
| 486 |
- |
|
| 487 |
- if img.RunConfig() != nil {
|
|
| 488 |
- dispatchState.runConfig = img.RunConfig() |
|
| 489 |
- } |
|
| 490 |
- } |
|
| 491 |
- |
|
| 492 |
- // Check to see if we have a default PATH, note that windows won't |
|
| 493 |
- // have one as it's set by HCS |
|
| 494 |
- if system.DefaultPathEnv != "" {
|
|
| 495 |
- if _, ok := dispatchState.runConfigEnvMapping()["PATH"]; !ok {
|
|
| 496 |
- dispatchState.runConfig.Env = append(dispatchState.runConfig.Env, |
|
| 497 |
- "PATH="+system.DefaultPathEnv) |
|
| 498 |
- } |
|
| 499 |
- } |
|
| 500 |
- |
|
| 501 |
- if img == nil {
|
|
| 502 |
- // Typically this means they used "FROM scratch" |
|
| 503 |
- return nil |
|
| 504 |
- } |
|
| 505 |
- |
|
| 506 |
- // Process ONBUILD triggers if they exist |
|
| 507 |
- if nTriggers := len(dispatchState.runConfig.OnBuild); nTriggers != 0 {
|
|
| 508 |
- word := "trigger" |
|
| 509 |
- if nTriggers > 1 {
|
|
| 510 |
- word = "triggers" |
|
| 511 |
- } |
|
| 512 |
- fmt.Fprintf(b.Stderr, "# Executing %d build %s...\n", nTriggers, word) |
|
| 513 |
- } |
|
| 514 |
- |
|
| 515 |
- // Copy the ONBUILD triggers, and remove them from the config, since the config will be committed. |
|
| 516 |
- onBuildTriggers := dispatchState.runConfig.OnBuild |
|
| 517 |
- dispatchState.runConfig.OnBuild = []string{}
|
|
| 518 |
- |
|
| 519 |
- // Reset stdin settings as all build actions run without stdin |
|
| 520 |
- dispatchState.runConfig.OpenStdin = false |
|
| 521 |
- dispatchState.runConfig.StdinOnce = false |
|
| 522 |
- |
|
| 523 |
- // parse the ONBUILD triggers by invoking the parser |
|
| 524 |
- for _, step := range onBuildTriggers {
|
|
| 525 |
- dockerfile, err := parser.Parse(strings.NewReader(step)) |
|
| 526 |
- if err != nil {
|
|
| 527 |
- return err |
|
| 528 |
- } |
|
| 529 |
- |
|
| 530 |
- for _, n := range dockerfile.AST.Children {
|
|
| 531 |
- if err := checkDispatch(n); err != nil {
|
|
| 532 |
- return err |
|
| 533 |
- } |
|
| 534 |
- |
|
| 535 |
- upperCasedCmd := strings.ToUpper(n.Value) |
|
| 536 |
- switch upperCasedCmd {
|
|
| 537 |
- case "ONBUILD": |
|
| 538 |
- return errors.New("Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed")
|
|
| 539 |
- case "MAINTAINER", "FROM": |
|
| 540 |
- return errors.Errorf("%s isn't allowed as an ONBUILD trigger", upperCasedCmd)
|
|
| 541 |
- } |
|
| 542 |
- } |
|
| 543 |
- |
|
| 544 |
- if _, err := dispatchFromDockerfile(b, dockerfile, dispatchState); err != nil {
|
|
| 545 |
- return err |
|
| 546 |
- } |
|
| 547 |
- } |
|
| 548 |
- return nil |
|
| 549 |
-} |
|
| 550 |
- |
|
| 551 | 483 |
// probeCache checks if cache match can be found for current build instruction. |
| 552 | 484 |
// If an image is found, probeCache returns `(true, nil)`. |
| 553 | 485 |
// If no image is found, it returns `(false, nil)`. |
| ... | ... |
@@ -17,7 +17,7 @@ import ( |
| 17 | 17 |
type releaseableLayer struct {
|
| 18 | 18 |
rwLayer layer.RWLayer |
| 19 | 19 |
release func(layer.RWLayer) error |
| 20 |
- mount func() (layer.RWLayer, error) |
|
| 20 |
+ mount func(string) (layer.RWLayer, error) |
|
| 21 | 21 |
} |
| 22 | 22 |
|
| 23 | 23 |
func (rl *releaseableLayer) Release() error {
|
| ... | ... |
@@ -28,10 +28,9 @@ func (rl *releaseableLayer) Release() error {
|
| 28 | 28 |
return rl.release(rl.rwLayer) |
| 29 | 29 |
} |
| 30 | 30 |
|
| 31 |
-func (rl *releaseableLayer) Mount() (string, error) {
|
|
| 31 |
+func (rl *releaseableLayer) Mount(imageID string) (string, error) {
|
|
| 32 | 32 |
var err error |
| 33 |
- // daemon.layerStore.CreateRWLayer(mountID, img.RootFS.ChainID(), nil) |
|
| 34 |
- rl.rwLayer, err = rl.mount() |
|
| 33 |
+ rl.rwLayer, err = rl.mount(imageID) |
|
| 35 | 34 |
if err != nil {
|
| 36 | 35 |
return "", errors.Wrap(err, "failed to create rwlayer") |
| 37 | 36 |
} |
| ... | ... |
@@ -47,8 +46,12 @@ func (rl *releaseableLayer) Mount() (string, error) {
|
| 47 | 47 |
return mountPath, err |
| 48 | 48 |
} |
| 49 | 49 |
|
| 50 |
-func (daemon *Daemon) getReleasableLayerForImage(img *image.Image) (*releaseableLayer, error) {
|
|
| 51 |
- mountFunc := func() (layer.RWLayer, error) {
|
|
| 50 |
+func (daemon *Daemon) getReleasableLayerForImage() *releaseableLayer {
|
|
| 51 |
+ mountFunc := func(imageID string) (layer.RWLayer, error) {
|
|
| 52 |
+ img, err := daemon.GetImage(imageID) |
|
| 53 |
+ if err != nil {
|
|
| 54 |
+ return nil, err |
|
| 55 |
+ } |
|
| 52 | 56 |
mountID := stringid.GenerateRandomID() |
| 53 | 57 |
return daemon.layerStore.CreateRWLayer(mountID, img.RootFS.ChainID(), nil) |
| 54 | 58 |
} |
| ... | ... |
@@ -59,7 +62,7 @@ func (daemon *Daemon) getReleasableLayerForImage(img *image.Image) (*releaseable |
| 59 | 59 |
return err |
| 60 | 60 |
} |
| 61 | 61 |
|
| 62 |
- return &releaseableLayer{mount: mountFunc, release: releaseFunc}, nil
|
|
| 62 |
+ return &releaseableLayer{mount: mountFunc, release: releaseFunc}
|
|
| 63 | 63 |
} |
| 64 | 64 |
|
| 65 | 65 |
// TODO: could this use the regular daemon PullImage ? |
| ... | ... |
@@ -94,15 +97,10 @@ func (daemon *Daemon) GetImageAndLayer(ctx context.Context, refOrID string, opts |
| 94 | 94 |
image, _ := daemon.GetImage(refOrID) |
| 95 | 95 |
// TODO: shouldn't we error out if error is different from "not found" ? |
| 96 | 96 |
if image != nil {
|
| 97 |
- layer, err := daemon.getReleasableLayerForImage(image) |
|
| 98 |
- return image, layer, err |
|
| 97 |
+ return image, daemon.getReleasableLayerForImage(), nil |
|
| 99 | 98 |
} |
| 100 | 99 |
} |
| 101 | 100 |
|
| 102 | 101 |
image, err := daemon.pullForBuilder(ctx, refOrID, opts.AuthConfig, opts.Output) |
| 103 |
- if err != nil {
|
|
| 104 |
- return nil, nil, err |
|
| 105 |
- } |
|
| 106 |
- layer, err := daemon.getReleasableLayerForImage(image) |
|
| 107 |
- return image, layer, err |
|
| 102 |
+ return image, daemon.getReleasableLayerForImage(), err |
|
| 108 | 103 |
} |
| ... | ... |
@@ -5946,7 +5946,7 @@ func (s *DockerSuite) TestBuildCopyFromPreviousRootFSErrors(c *check.C) {
|
| 5946 | 5946 |
dockerfile: ` |
| 5947 | 5947 |
FROM busybox |
| 5948 | 5948 |
COPY --from=0 foo bar`, |
| 5949 |
- expectedError: "invalid from flag value 0 refers current build block", |
|
| 5949 |
+ expectedError: "invalid from flag value 0: refers to current build stage", |
|
| 5950 | 5950 |
}, |
| 5951 | 5951 |
{
|
| 5952 | 5952 |
dockerfile: ` |