From 37cabec01330763de7e059320d0cf3b144db1f8c Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 3 Sep 2015 22:19:10 -0400 Subject: [PATCH 1/2] graph: exported images times matching creation the image export, that is used in `docker save` previous has just had the layers times (atimes, mtimes) be when the save was done. ```bash vbatts@valse ~ (master) $ docker save busybox | tar tv drwxr-xr-x 0/0 0 2015-09-03 22:22 6ce2e90b0bc7224de3db1f0d646fe8e2c4dd37f1793928287f6074bc451a57ea/ -rw-r--r-- 0/0 3 2015-09-03 22:22 6ce2e90b0bc7224de3db1f0d646fe8e2c4dd37f1793928287f6074bc451a57ea/VERSION -rw-r--r-- 0/0 1405 2015-09-03 22:22 6ce2e90b0bc7224de3db1f0d646fe8e2c4dd37f1793928287f6074bc451a57ea/json -rw-r--r-- 0/0 2643968 2015-09-03 22:22 6ce2e90b0bc7224de3db1f0d646fe8e2c4dd37f1793928287f6074bc451a57ea/layer.tar drwxr-xr-x 0/0 0 2015-09-03 22:22 8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/ -rw-r--r-- 0/0 3 2015-09-03 22:22 8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/VERSION -rw-r--r-- 0/0 1346 2015-09-03 22:22 8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/json -rw-r--r-- 0/0 1024 2015-09-03 22:22 8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/layer.tar drwxr-xr-x 0/0 0 2015-09-03 22:22 cf2616975b4a3cba083ca99bc3f0bf25f5f528c3c52be1596b30f60b0b1c37ff/ -rw-r--r-- 0/0 3 2015-09-03 22:22 cf2616975b4a3cba083ca99bc3f0bf25f5f528c3c52be1596b30f60b0b1c37ff/VERSION -rw-r--r-- 0/0 1181 2015-09-03 22:22 cf2616975b4a3cba083ca99bc3f0bf25f5f528c3c52be1596b30f60b0b1c37ff/json -rw-r--r-- 0/0 1024 2015-09-03 22:22 cf2616975b4a3cba083ca99bc3f0bf25f5f528c3c52be1596b30f60b0b1c37ff/layer.tar -rw-r--r-- 0/0 90 2015-09-03 22:22 repositories ``` With this change, the layer's directory and artifact will have times matching the image layer's created time. The "repositories" file is set to epoch. ```bash vbatts@valse ~ (master) $ docker save busybox | tar tv drwxr-xr-x 0/0 0 2015-04-17 18:01 6ce2e90b0bc7224de3db1f0d646fe8e2c4dd37f1793928287f6074bc451a57ea/ -rw-r--r-- 0/0 3 2015-04-17 18:01 6ce2e90b0bc7224de3db1f0d646fe8e2c4dd37f1793928287f6074bc451a57ea/VERSION -rw-r--r-- 0/0 1405 2015-04-17 18:01 6ce2e90b0bc7224de3db1f0d646fe8e2c4dd37f1793928287f6074bc451a57ea/json -rw-r--r-- 0/0 2643968 2015-04-17 18:01 6ce2e90b0bc7224de3db1f0d646fe8e2c4dd37f1793928287f6074bc451a57ea/layer.tar drwxr-xr-x 0/0 0 2015-04-17 18:01 8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/ -rw-r--r-- 0/0 3 2015-04-17 18:01 8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/VERSION -rw-r--r-- 0/0 1346 2015-04-17 18:01 8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/json -rw-r--r-- 0/0 1024 2015-04-17 18:01 8c2e06607696bd4afb3d03b687e361cc43cf8ec1a4a725bc96e39f05ba97dd55/layer.tar drwxr-xr-x 0/0 0 2015-04-17 18:01 cf2616975b4a3cba083ca99bc3f0bf25f5f528c3c52be1596b30f60b0b1c37ff/ -rw-r--r-- 0/0 3 2015-04-17 18:01 cf2616975b4a3cba083ca99bc3f0bf25f5f528c3c52be1596b30f60b0b1c37ff/VERSION -rw-r--r-- 0/0 1181 2015-04-17 18:01 cf2616975b4a3cba083ca99bc3f0bf25f5f528c3c52be1596b30f60b0b1c37ff/json -rw-r--r-- 0/0 1024 2015-04-17 18:01 cf2616975b4a3cba083ca99bc3f0bf25f5f528c3c52be1596b30f60b0b1c37ff/layer.tar -rw-r--r-- 0/0 90 1969-12-31 19:00 repositories ``` Side effect of this is that the tar stream from `docker save` is now more deterministic. ```bash vbatts@valse ~ (master) $ docker save busybox | sha1sum baf03e30ef79ca4d9c5e512d3a1b873880f404ca - vbatts@valse ~ (master) $ docker save busybox | sha1sum baf03e30ef79ca4d9c5e512d3a1b873880f404ca - vbatts@valse ~ (master) $ docker save busybox | sha1sum baf03e30ef79ca4d9c5e512d3a1b873880f404ca - vbatts@valse ~ (master) $ docker save busybox | sha1sum baf03e30ef79ca4d9c5e512d3a1b873880f404ca - ``` Signed-off-by: Vincent Batts Upstream-commit: 7795b1c6979c1da3f05e995e15a966c969d6895f Component: engine --- components/engine/graph/export.go | 21 ++++++++++++++++----- components/engine/graph/service.go | 16 ---------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/components/engine/graph/export.go b/components/engine/graph/export.go index 7512127f7e..6362e54a6d 100644 --- a/components/engine/graph/export.go +++ b/components/engine/graph/export.go @@ -2,10 +2,12 @@ package graph import ( "encoding/json" + "fmt" "io" "io/ioutil" "os" "path/filepath" + "time" "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/archive" @@ -88,6 +90,9 @@ func (s *TagStore) ImageExport(names []string, outStream io.Writer) error { if err := f.Close(); err != nil { return err } + if err := os.Chtimes(filepath.Join(tempdir, "repositories"), time.Unix(0, 0), time.Unix(0, 0)); err != nil { + return err + } } else { logrus.Debugf("There were no repositories to write") } @@ -128,7 +133,11 @@ func (s *TagStore) exportImage(name, tempdir string) error { if err != nil { return err } - imageInspectRaw, err := s.lookupRaw(n) + img, err := s.LookupImage(n) + if err != nil || img == nil { + return fmt.Errorf("No such image %s", n) + } + imageInspectRaw, err := s.graph.RawJSON(img.ID) if err != nil { return err } @@ -149,11 +158,13 @@ func (s *TagStore) exportImage(name, tempdir string) error { return err } - // find parent - img, err := s.LookupImage(n) - if err != nil { - return err + for _, fname := range []string{"", "VERSION", "json", "layer.tar"} { + if err := os.Chtimes(filepath.Join(tmpImageDir, fname), img.Created, img.Created); err != nil { + return err + } } + + // try again with parent n = img.Parent } return nil diff --git a/components/engine/graph/service.go b/components/engine/graph/service.go index 1b347d0b5a..2dce6b6fb4 100644 --- a/components/engine/graph/service.go +++ b/components/engine/graph/service.go @@ -10,22 +10,6 @@ import ( "github.com/docker/docker/api/types" ) -// lookupRaw looks up an image by name in a TagStore and returns the raw JSON -// describing the image. -func (s *TagStore) lookupRaw(name string) ([]byte, error) { - image, err := s.LookupImage(name) - if err != nil || image == nil { - return nil, fmt.Errorf("No such image %s", name) - } - - imageInspectRaw, err := s.graph.RawJSON(image.ID) - if err != nil { - return nil, err - } - - return imageInspectRaw, nil -} - // Lookup looks up an image by name in a TagStore and returns it as an // ImageInspect structure. func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) { From 90b9ae3169ebff58fbc23cacb756235dd7a60509 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 1 Oct 2015 12:07:30 -0400 Subject: [PATCH 2/2] save: integration test for timestamp matching The `docker save`ed output ought to have matching timestamp to the layer creation. Signed-off-by: Vincent Batts Upstream-commit: e4478caddf78700bd42f94eb382ae1805a443618 Component: engine --- .../docker_cli_save_load_test.go | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/components/engine/integration-cli/docker_cli_save_load_test.go b/components/engine/integration-cli/docker_cli_save_load_test.go index bed9b1cd2d..bb758b6cb6 100644 --- a/components/engine/integration-cli/docker_cli_save_load_test.go +++ b/components/engine/integration-cli/docker_cli_save_load_test.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "io/ioutil" "os" @@ -9,6 +10,7 @@ import ( "reflect" "sort" "strings" + "time" "github.com/go-check/check" ) @@ -94,6 +96,24 @@ func (s *DockerSuite) TestSaveSingleTag(c *check.C) { } } +func (s *DockerSuite) TestSaveCheckTimes(c *check.C) { + repoName := "busybox:latest" + out, _ := dockerCmd(c, "inspect", repoName) + data := []struct { + ID string + Created time.Time + }{} + err := json.Unmarshal([]byte(out), &data) + c.Assert(err, check.IsNil, check.Commentf("failed to marshal from %q: err %v", repoName, err)) + c.Assert(len(data), check.Not(check.Equals), 0, check.Commentf("failed to marshal the data from %q", repoName)) + tarTvTimeFormat := "2006-01-02 15:04" + out, _, err = runCommandPipelineWithOutput( + exec.Command(dockerBinary, "save", repoName), + exec.Command("tar", "tv"), + exec.Command("grep", "-E", fmt.Sprintf("%s %s", data[0].Created.Format(tarTvTimeFormat), data[0].ID))) + c.Assert(err, check.IsNil, check.Commentf("failed to save repo with image ID and 'repositories' file: %s, %v", out, err)) +} + func (s *DockerSuite) TestSaveImageId(c *check.C) { repoName := "foobar-save-image-id-test" dockerCmd(c, "tag", "emptyfs:latest", fmt.Sprintf("%v:latest", repoName))