Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
| ... | ... |
@@ -7,6 +7,18 @@ import ( |
| 7 | 7 |
"github.com/docker/docker/pkg/streamformatter" |
| 8 | 8 |
) |
| 9 | 9 |
|
| 10 |
+// PullOption defines different modes for accessing images |
|
| 11 |
+type PullOption int |
|
| 12 |
+ |
|
| 13 |
+const ( |
|
| 14 |
+ // PullOptionNoPull only returns local images |
|
| 15 |
+ PullOptionNoPull PullOption = iota |
|
| 16 |
+ // PullOptionForcePull always tries to pull a ref from the registry first |
|
| 17 |
+ PullOptionForcePull |
|
| 18 |
+ // PullOptionPreferLocal uses local image if it exists, otherwise pulls |
|
| 19 |
+ PullOptionPreferLocal |
|
| 20 |
+) |
|
| 21 |
+ |
|
| 10 | 22 |
// ProgressWriter is a data object to transport progress streams to the client |
| 11 | 23 |
type ProgressWriter struct {
|
| 12 | 24 |
Output io.Writer |
| ... | ... |
@@ -25,7 +37,7 @@ type BuildConfig struct {
|
| 25 | 25 |
|
| 26 | 26 |
// GetImageAndLayerOptions are the options supported by GetImageAndReleasableLayer |
| 27 | 27 |
type GetImageAndLayerOptions struct {
|
| 28 |
- ForcePull bool |
|
| 28 |
+ PullOption PullOption |
|
| 29 | 29 |
AuthConfig map[string]types.AuthConfig |
| 30 | 30 |
Output io.Writer |
| 31 | 31 |
} |
| ... | ... |
@@ -196,6 +196,7 @@ func (b *Builder) getImageMount(fromFlag *Flag) (*imageMount, error) {
|
| 196 | 196 |
return nil, nil |
| 197 | 197 |
} |
| 198 | 198 |
|
| 199 |
+ var localOnly bool |
|
| 199 | 200 |
imageRefOrID := fromFlag.Value |
| 200 | 201 |
stage, err := b.buildStages.get(fromFlag.Value) |
| 201 | 202 |
if err != nil {
|
| ... | ... |
@@ -203,8 +204,9 @@ func (b *Builder) getImageMount(fromFlag *Flag) (*imageMount, error) {
|
| 203 | 203 |
} |
| 204 | 204 |
if stage != nil {
|
| 205 | 205 |
imageRefOrID = stage.ImageID() |
| 206 |
+ localOnly = true |
|
| 206 | 207 |
} |
| 207 |
- return b.imageSources.Get(imageRefOrID) |
|
| 208 |
+ return b.imageSources.Get(imageRefOrID, localOnly) |
|
| 208 | 209 |
} |
| 209 | 210 |
|
| 210 | 211 |
// FROM imagename[:tag | @digest] [AS build-stage-name] |
| ... | ... |
@@ -266,8 +268,10 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err |
| 266 | 266 |
return nil, err |
| 267 | 267 |
} |
| 268 | 268 |
|
| 269 |
+ var localOnly bool |
|
| 269 | 270 |
if stage, ok := b.buildStages.getByName(name); ok {
|
| 270 | 271 |
name = stage.ImageID() |
| 272 |
+ localOnly = true |
|
| 271 | 273 |
} |
| 272 | 274 |
|
| 273 | 275 |
// Windows cannot support a container with no base image. |
| ... | ... |
@@ -277,7 +281,7 @@ func (b *Builder) getFromImage(shlex *ShellLex, name string) (builder.Image, err |
| 277 | 277 |
} |
| 278 | 278 |
return scratchImage, nil |
| 279 | 279 |
} |
| 280 |
- imageMount, err := b.imageSources.Get(name) |
|
| 280 |
+ imageMount, err := b.imageSources.Get(name, localOnly) |
|
| 281 | 281 |
if err != nil {
|
| 282 | 282 |
return nil, err |
| 283 | 283 |
} |
| ... | ... |
@@ -86,21 +86,29 @@ func (s *buildStages) update(imageID string) {
|
| 86 | 86 |
s.sequence[len(s.sequence)-1].update(imageID) |
| 87 | 87 |
} |
| 88 | 88 |
|
| 89 |
-type getAndMountFunc func(string) (builder.Image, builder.ReleaseableLayer, error) |
|
| 89 |
+type getAndMountFunc func(string, bool) (builder.Image, builder.ReleaseableLayer, error) |
|
| 90 | 90 |
|
| 91 | 91 |
// imageSources mounts images and provides a cache for mounted images. It tracks |
| 92 | 92 |
// all images so they can be unmounted at the end of the build. |
| 93 | 93 |
type imageSources struct {
|
| 94 | 94 |
byImageID map[string]*imageMount |
| 95 |
- withoutID []*imageMount |
|
| 95 |
+ mounts []*imageMount |
|
| 96 | 96 |
getImage getAndMountFunc |
| 97 | 97 |
cache pathCache // TODO: remove |
| 98 | 98 |
} |
| 99 | 99 |
|
| 100 | 100 |
func newImageSources(ctx context.Context, options builderOptions) *imageSources {
|
| 101 |
- getAndMount := func(idOrRef string) (builder.Image, builder.ReleaseableLayer, error) {
|
|
| 101 |
+ getAndMount := func(idOrRef string, localOnly bool) (builder.Image, builder.ReleaseableLayer, error) {
|
|
| 102 |
+ pullOption := backend.PullOptionNoPull |
|
| 103 |
+ if !localOnly {
|
|
| 104 |
+ if options.Options.PullParent {
|
|
| 105 |
+ pullOption = backend.PullOptionForcePull |
|
| 106 |
+ } else {
|
|
| 107 |
+ pullOption = backend.PullOptionPreferLocal |
|
| 108 |
+ } |
|
| 109 |
+ } |
|
| 102 | 110 |
return options.Backend.GetImageAndReleasableLayer(ctx, idOrRef, backend.GetImageAndLayerOptions{
|
| 103 |
- ForcePull: options.Options.PullParent, |
|
| 111 |
+ PullOption: pullOption, |
|
| 104 | 112 |
AuthConfig: options.Options.AuthConfigs, |
| 105 | 113 |
Output: options.ProgressWriter.Output, |
| 106 | 114 |
}) |
| ... | ... |
@@ -112,12 +120,12 @@ func newImageSources(ctx context.Context, options builderOptions) *imageSources |
| 112 | 112 |
} |
| 113 | 113 |
} |
| 114 | 114 |
|
| 115 |
-func (m *imageSources) Get(idOrRef string) (*imageMount, error) {
|
|
| 115 |
+func (m *imageSources) Get(idOrRef string, localOnly bool) (*imageMount, error) {
|
|
| 116 | 116 |
if im, ok := m.byImageID[idOrRef]; ok {
|
| 117 | 117 |
return im, nil |
| 118 | 118 |
} |
| 119 | 119 |
|
| 120 |
- image, layer, err := m.getImage(idOrRef) |
|
| 120 |
+ image, layer, err := m.getImage(idOrRef, localOnly) |
|
| 121 | 121 |
if err != nil {
|
| 122 | 122 |
return nil, err |
| 123 | 123 |
} |
| ... | ... |
@@ -127,13 +135,7 @@ func (m *imageSources) Get(idOrRef string) (*imageMount, error) {
|
| 127 | 127 |
} |
| 128 | 128 |
|
| 129 | 129 |
func (m *imageSources) Unmount() (retErr error) {
|
| 130 |
- for _, im := range m.byImageID {
|
|
| 131 |
- if err := im.unmount(); err != nil {
|
|
| 132 |
- logrus.Error(err) |
|
| 133 |
- retErr = err |
|
| 134 |
- } |
|
| 135 |
- } |
|
| 136 |
- for _, im := range m.withoutID {
|
|
| 130 |
+ for _, im := range m.mounts {
|
|
| 137 | 131 |
if err := im.unmount(); err != nil {
|
| 138 | 132 |
logrus.Error(err) |
| 139 | 133 |
retErr = err |
| ... | ... |
@@ -146,10 +148,10 @@ func (m *imageSources) Add(im *imageMount) {
|
| 146 | 146 |
switch im.image {
|
| 147 | 147 |
case nil: |
| 148 | 148 |
im.image = &dockerimage.Image{}
|
| 149 |
- m.withoutID = append(m.withoutID, im) |
|
| 150 | 149 |
default: |
| 151 | 150 |
m.byImageID[im.image.ImageID()] = im |
| 152 | 151 |
} |
| 152 |
+ m.mounts = append(m.mounts, im) |
|
| 153 | 153 |
} |
| 154 | 154 |
|
| 155 | 155 |
// imageMount is a reference to an image that can be used as a builder.Source |
| ... | ... |
@@ -116,7 +116,7 @@ func (b *Builder) performCopy(state *dispatchState, inst copyInstruction) error |
| 116 | 116 |
return err |
| 117 | 117 |
} |
| 118 | 118 |
|
| 119 |
- imageMount, err := b.imageSources.Get(state.imageID) |
|
| 119 |
+ imageMount, err := b.imageSources.Get(state.imageID, true) |
|
| 120 | 120 |
if err != nil {
|
| 121 | 121 |
return errors.Wrapf(err, "failed to get destination image %q", state.imageID) |
| 122 | 122 |
} |
| ... | ... |
@@ -18,6 +18,7 @@ import ( |
| 18 | 18 |
) |
| 19 | 19 |
|
| 20 | 20 |
type releaseableLayer struct {
|
| 21 |
+ released bool |
|
| 21 | 22 |
layerStore layer.Store |
| 22 | 23 |
roLayer layer.Layer |
| 23 | 24 |
rwLayer layer.RWLayer |
| ... | ... |
@@ -70,6 +71,10 @@ func (rl *releaseableLayer) DiffID() layer.DiffID {
|
| 70 | 70 |
} |
| 71 | 71 |
|
| 72 | 72 |
func (rl *releaseableLayer) Release() error {
|
| 73 |
+ if rl.released {
|
|
| 74 |
+ return nil |
|
| 75 |
+ } |
|
| 76 |
+ rl.released = true |
|
| 73 | 77 |
rl.releaseRWLayer() |
| 74 | 78 |
return rl.releaseROLayer() |
| 75 | 79 |
} |
| ... | ... |
@@ -143,8 +148,11 @@ func (daemon *Daemon) GetImageAndReleasableLayer(ctx context.Context, refOrID st |
| 143 | 143 |
return nil, layer, err |
| 144 | 144 |
} |
| 145 | 145 |
|
| 146 |
- if !opts.ForcePull {
|
|
| 147 |
- image, _ := daemon.GetImage(refOrID) |
|
| 146 |
+ if opts.PullOption != backend.PullOptionForcePull {
|
|
| 147 |
+ image, err := daemon.GetImage(refOrID) |
|
| 148 |
+ if err != nil && opts.PullOption == backend.PullOptionNoPull {
|
|
| 149 |
+ return nil, nil, err |
|
| 150 |
+ } |
|
| 148 | 151 |
// TODO: shouldn't we error out if error is different from "not found" ? |
| 149 | 152 |
if image != nil {
|
| 150 | 153 |
layer, err := newReleasableLayerForImage(image, daemon.layerStore) |
| ... | ... |
@@ -4,11 +4,14 @@ import ( |
| 4 | 4 |
"archive/tar" |
| 5 | 5 |
"bytes" |
| 6 | 6 |
"encoding/json" |
| 7 |
+ "fmt" |
|
| 8 |
+ "io" |
|
| 7 | 9 |
"io/ioutil" |
| 8 | 10 |
"net/http" |
| 9 | 11 |
"regexp" |
| 10 | 12 |
"strings" |
| 11 | 13 |
|
| 14 |
+ "github.com/docker/docker/api/types" |
|
| 12 | 15 |
"github.com/docker/docker/integration-cli/checker" |
| 13 | 16 |
"github.com/docker/docker/integration-cli/cli/build/fakecontext" |
| 14 | 17 |
"github.com/docker/docker/integration-cli/cli/build/fakegit" |
| ... | ... |
@@ -322,6 +325,44 @@ func (s *DockerSuite) TestBuildOnBuildCache(c *check.C) {
|
| 322 | 322 |
assert.Equal(c, parentID, image.Parent) |
| 323 | 323 |
} |
| 324 | 324 |
|
| 325 |
+func (s *DockerRegistrySuite) TestBuildCopyFromForcePull(c *check.C) {
|
|
| 326 |
+ client, err := request.NewClient() |
|
| 327 |
+ require.NoError(c, err) |
|
| 328 |
+ |
|
| 329 |
+ repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
|
|
| 330 |
+ // tag the image to upload it to the private registry |
|
| 331 |
+ err = client.ImageTag(context.TODO(), "busybox", repoName) |
|
| 332 |
+ assert.Nil(c, err) |
|
| 333 |
+ // push the image to the registry |
|
| 334 |
+ rc, err := client.ImagePush(context.TODO(), repoName, types.ImagePushOptions{RegistryAuth: "{}"})
|
|
| 335 |
+ assert.Nil(c, err) |
|
| 336 |
+ _, err = io.Copy(ioutil.Discard, rc) |
|
| 337 |
+ assert.Nil(c, err) |
|
| 338 |
+ |
|
| 339 |
+ dockerfile := fmt.Sprintf(` |
|
| 340 |
+ FROM %s AS foo |
|
| 341 |
+ RUN touch abc |
|
| 342 |
+ FROM %s |
|
| 343 |
+ COPY --from=foo /abc / |
|
| 344 |
+ `, repoName, repoName) |
|
| 345 |
+ |
|
| 346 |
+ ctx := fakecontext.New(c, "", |
|
| 347 |
+ fakecontext.WithDockerfile(dockerfile), |
|
| 348 |
+ ) |
|
| 349 |
+ defer ctx.Close() |
|
| 350 |
+ |
|
| 351 |
+ res, body, err := request.Post( |
|
| 352 |
+ "/build?pull=1", |
|
| 353 |
+ request.RawContent(ctx.AsTarReader(c)), |
|
| 354 |
+ request.ContentType("application/x-tar"))
|
|
| 355 |
+ require.NoError(c, err) |
|
| 356 |
+ assert.Equal(c, http.StatusOK, res.StatusCode) |
|
| 357 |
+ |
|
| 358 |
+ out, err := testutil.ReadBody(body) |
|
| 359 |
+ require.NoError(c, err) |
|
| 360 |
+ assert.Contains(c, string(out), "Successfully built") |
|
| 361 |
+} |
|
| 362 |
+ |
|
| 325 | 363 |
type buildLine struct {
|
| 326 | 364 |
Stream string |
| 327 | 365 |
Aux struct {
|