369 lines
17 KiB
Diff
369 lines
17 KiB
Diff
From a671cf961c82749cd9025e3e9cce772650134a67 Mon Sep 17 00:00:00 2001
|
|
From: Liu Hua <sdu.liu@huawei.com>
|
|
Date: Thu, 6 Jun 2019 01:13:08 -0400
|
|
Subject: [PATCH] docker Support compress when saving images
|
|
|
|
If add option `-c` when executing command `docker save`,
|
|
layer.tar in exported archive will be compressed with
|
|
algorithm gzip. Usage like this(we can use command `file`
|
|
to check format of the layer.tar):
|
|
|
|
```
|
|
$ docker save -c -o redis.tar redis
|
|
$ tar xvf redis.tar
|
|
09b9c5a923b810cc524ec5abf9dd65f796e37397005906b5d81c919bf63cb123/
|
|
09b9c5a923b810cc524ec5abf9dd65f796e37397005906b5d81c919bf63cb123/VERSION
|
|
09b9c5a923b810cc524ec5abf9dd65f796e37397005906b5d81c919bf63cb123/json
|
|
09b9c5a923b810cc524ec5abf9dd65f796e37397005906b5d81c919bf63cb123/layer.tar
|
|
7613529f37fd6709b0d850a8ebecd13c069a9cb2bbf67babea0769c68172c5c9/
|
|
7613529f37fd6709b0d850a8ebecd13c069a9cb2bbf67babea0769c68172c5c9/VERSION
|
|
7613529f37fd6709b0d850a8ebecd13c069a9cb2bbf67babea0769c68172c5c9/json
|
|
7613529f37fd6709b0d850a8ebecd13c069a9cb2bbf67babea0769c68172c5c9/layer.tar
|
|
846767a44c632a336d7204fb30e9215c84fb1ae1df81a557f78e0fc4d1842130/
|
|
846767a44c632a336d7204fb30e9215c84fb1ae1df81a557f78e0fc4d1842130/VERSION
|
|
846767a44c632a336d7204fb30e9215c84fb1ae1df81a557f78e0fc4d1842130/json
|
|
846767a44c632a336d7204fb30e9215c84fb1ae1df81a557f78e0fc4d1842130/layer.tar
|
|
98ee6000320c9e7de792f9c17ac39233abd1881bb127f8227719740981742d7e/
|
|
98ee6000320c9e7de792f9c17ac39233abd1881bb127f8227719740981742d7e/VERSION
|
|
98ee6000320c9e7de792f9c17ac39233abd1881bb127f8227719740981742d7e/json
|
|
98ee6000320c9e7de792f9c17ac39233abd1881bb127f8227719740981742d7e/layer.tar
|
|
a82ad3e3c9cd9fafcb310870d070fe293448e5a6d5681eafe54c88cb486038fc/
|
|
a82ad3e3c9cd9fafcb310870d070fe293448e5a6d5681eafe54c88cb486038fc/VERSION
|
|
a82ad3e3c9cd9fafcb310870d070fe293448e5a6d5681eafe54c88cb486038fc/json
|
|
a82ad3e3c9cd9fafcb310870d070fe293448e5a6d5681eafe54c88cb486038fc/layer.tar
|
|
b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/
|
|
b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/VERSION
|
|
b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/json
|
|
b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/layer.tar
|
|
d4f259423416b3c82b46e1caf01829ac3a99a211dfc691c9d862464768112e7f.json
|
|
manifest.json
|
|
repositories
|
|
$ file
|
|
b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/layer.tar
|
|
b047187f532984ce5e2b53a06c09f2621457939f87472673f35d15391ca58188/layer.tar:
|
|
gzip compressed data
|
|
```
|
|
|
|
Change-Id: I9069b5346e4990e90c2f4724841dc3b4f6f5579f
|
|
Signed-off-by: Fengtu Wang <wangfengtu@huawei.com>
|
|
Signed-off-by: Liu Hua <sdu.liu@huawei.com>
|
|
---
|
|
.../cli/cli/command/image/client_test.go | 2 +-
|
|
components/cli/cli/command/image/save.go | 8 +++++---
|
|
.../docker/docker/client/image_save.go | 6 +++++-
|
|
.../docker/docker/client/interface.go | 2 +-
|
|
.../engine/api/server/router/image/backend.go | 2 +-
|
|
.../api/server/router/image/image_routes.go | 4 +++-
|
|
components/engine/client/image_save.go | 6 +++++-
|
|
components/engine/client/image_save_test.go | 4 ++--
|
|
components/engine/client/interface.go | 2 +-
|
|
.../engine/daemon/images/image_exporter.go | 4 ++--
|
|
components/engine/distribution/push.go | 2 +-
|
|
components/engine/distribution/push_v2.go | 2 +-
|
|
components/engine/image/image.go | 2 +-
|
|
components/engine/image/tarexport/save.go | 18 +++++++++++++++---
|
|
.../plugin/authz/authz_plugin_test.go | 2 +-
|
|
.../engine/internal/test/daemon/daemon.go | 2 +-
|
|
16 files changed, 46 insertions(+), 22 deletions(-)
|
|
|
|
diff --git a/components/cli/cli/command/image/client_test.go b/components/cli/cli/command/image/client_test.go
|
|
index 50e46f4ec1..aa1bf13611 100644
|
|
--- a/components/cli/cli/command/image/client_test.go
|
|
+++ b/components/cli/cli/command/image/client_test.go
|
|
@@ -37,7 +37,7 @@ func (cli *fakeClient) ImageTag(_ context.Context, image, ref string) error {
|
|
return nil
|
|
}
|
|
|
|
-func (cli *fakeClient) ImageSave(_ context.Context, images []string) (io.ReadCloser, error) {
|
|
+func (cli *fakeClient) ImageSave(_ context.Context, images []string, compress bool) (io.ReadCloser, error) {
|
|
if cli.imageSaveFunc != nil {
|
|
return cli.imageSaveFunc(images)
|
|
}
|
|
diff --git a/components/cli/cli/command/image/save.go b/components/cli/cli/command/image/save.go
|
|
index ef23ca1bb1..e1b4a21482 100644
|
|
--- a/components/cli/cli/command/image/save.go
|
|
+++ b/components/cli/cli/command/image/save.go
|
|
@@ -13,8 +13,9 @@ import (
|
|
)
|
|
|
|
type saveOptions struct {
|
|
- images []string
|
|
- output string
|
|
+ images []string
|
|
+ output string
|
|
+ compress bool
|
|
}
|
|
|
|
// NewSaveCommand creates a new `docker save` command
|
|
@@ -34,6 +35,7 @@ func NewSaveCommand(dockerCli command.Cli) *cobra.Command {
|
|
flags := cmd.Flags()
|
|
|
|
flags.StringVarP(&opts.output, "output", "o", "", "Write to a file, instead of STDOUT")
|
|
+ flags.BoolVarP(&opts.compress, "compress", "c", false, "Compress layers when saving images")
|
|
|
|
return cmd
|
|
}
|
|
@@ -48,7 +50,7 @@ func RunSave(dockerCli command.Cli, opts saveOptions) error {
|
|
return errors.Wrap(err, "failed to save image")
|
|
}
|
|
|
|
- responseBody, err := dockerCli.Client().ImageSave(context.Background(), opts.images)
|
|
+ responseBody, err := dockerCli.Client().ImageSave(context.Background(), opts.images, opts.compress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
diff --git a/components/cli/vendor/github.com/docker/docker/client/image_save.go b/components/cli/vendor/github.com/docker/docker/client/image_save.go
|
|
index d1314e4b22..2bbea0f8ff 100644
|
|
--- a/components/cli/vendor/github.com/docker/docker/client/image_save.go
|
|
+++ b/components/cli/vendor/github.com/docker/docker/client/image_save.go
|
|
@@ -8,11 +8,15 @@ import (
|
|
|
|
// ImageSave retrieves one or more images from the docker host as an io.ReadCloser.
|
|
// It's up to the caller to store the images and close the stream.
|
|
-func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) {
|
|
+func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, compress bool) (io.ReadCloser, error) {
|
|
query := url.Values{
|
|
"names": imageIDs,
|
|
}
|
|
|
|
+ if compress {
|
|
+ query.Set("compress", "1")
|
|
+ }
|
|
+
|
|
resp, err := cli.get(ctx, "/images/get", query, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
diff --git a/components/cli/vendor/github.com/docker/docker/client/interface.go b/components/cli/vendor/github.com/docker/docker/client/interface.go
|
|
index b2d5d7bb72..e68f9f6264 100644
|
|
--- a/components/cli/vendor/github.com/docker/docker/client/interface.go
|
|
+++ b/components/cli/vendor/github.com/docker/docker/client/interface.go
|
|
@@ -98,7 +98,7 @@ type ImageAPIClient interface {
|
|
ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error)
|
|
ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
|
|
ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error)
|
|
- ImageSave(ctx context.Context, images []string) (io.ReadCloser, error)
|
|
+ ImageSave(ctx context.Context, images []string, compress bool) (io.ReadCloser, error)
|
|
ImageTag(ctx context.Context, image, ref string) error
|
|
ImagesPrune(ctx context.Context, pruneFilter filters.Args) (types.ImagesPruneReport, error)
|
|
}
|
|
diff --git a/components/engine/api/server/router/image/backend.go b/components/engine/api/server/router/image/backend.go
|
|
index 5837f9a9bc..84509d301a 100644
|
|
--- a/components/engine/api/server/router/image/backend.go
|
|
+++ b/components/engine/api/server/router/image/backend.go
|
|
@@ -31,7 +31,7 @@ type imageBackend interface {
|
|
type importExportBackend interface {
|
|
LoadImage(inTar io.ReadCloser, outStream io.Writer, quiet bool) error
|
|
ImportImage(src string, repository, platform string, tag string, msg string, inConfig io.ReadCloser, outStream io.Writer, changes []string) error
|
|
- ExportImage(names []string, outStream io.Writer) error
|
|
+ ExportImage(names []string, compress bool, outStream io.Writer) error
|
|
}
|
|
|
|
type registryBackend interface {
|
|
diff --git a/components/engine/api/server/router/image/image_routes.go b/components/engine/api/server/router/image/image_routes.go
|
|
index 85707c06d2..b7bb340e9a 100644
|
|
--- a/components/engine/api/server/router/image/image_routes.go
|
|
+++ b/components/engine/api/server/router/image/image_routes.go
|
|
@@ -159,7 +159,9 @@ func (s *imageRouter) getImagesGet(ctx context.Context, w http.ResponseWriter, r
|
|
names = r.Form["names"]
|
|
}
|
|
|
|
- if err := s.backend.ExportImage(names, output); err != nil {
|
|
+ compress := httputils.BoolValueOrDefault(r, "compress", false)
|
|
+
|
|
+ if err := s.backend.ExportImage(names, compress, output); err != nil {
|
|
if !output.Flushed() {
|
|
return err
|
|
}
|
|
diff --git a/components/engine/client/image_save.go b/components/engine/client/image_save.go
|
|
index d1314e4b22..2bbea0f8ff 100644
|
|
--- a/components/engine/client/image_save.go
|
|
+++ b/components/engine/client/image_save.go
|
|
@@ -8,11 +8,15 @@ import (
|
|
|
|
// ImageSave retrieves one or more images from the docker host as an io.ReadCloser.
|
|
// It's up to the caller to store the images and close the stream.
|
|
-func (cli *Client) ImageSave(ctx context.Context, imageIDs []string) (io.ReadCloser, error) {
|
|
+func (cli *Client) ImageSave(ctx context.Context, imageIDs []string, compress bool) (io.ReadCloser, error) {
|
|
query := url.Values{
|
|
"names": imageIDs,
|
|
}
|
|
|
|
+ if compress {
|
|
+ query.Set("compress", "1")
|
|
+ }
|
|
+
|
|
resp, err := cli.get(ctx, "/images/get", query, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
diff --git a/components/engine/client/image_save_test.go b/components/engine/client/image_save_test.go
|
|
index a40055e583..7325da0c80 100644
|
|
--- a/components/engine/client/image_save_test.go
|
|
+++ b/components/engine/client/image_save_test.go
|
|
@@ -15,7 +15,7 @@ func TestImageSaveError(t *testing.T) {
|
|
client := &Client{
|
|
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
|
|
}
|
|
- _, err := client.ImageSave(context.Background(), []string{"nothing"})
|
|
+ _, err := client.ImageSave(context.Background(), []string{"nothing"}, false)
|
|
if err == nil || err.Error() != "Error response from daemon: Server error" {
|
|
t.Fatalf("expected a Server error, got %v", err)
|
|
}
|
|
@@ -41,7 +41,7 @@ func TestImageSave(t *testing.T) {
|
|
}, nil
|
|
}),
|
|
}
|
|
- saveResponse, err := client.ImageSave(context.Background(), []string{"image_id1", "image_id2"})
|
|
+ saveResponse, err := client.ImageSave(context.Background(), []string{"image_id1", "image_id2"}, false)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
diff --git a/components/engine/client/interface.go b/components/engine/client/interface.go
|
|
index d190f8e58d..5a7b9eb5b8 100644
|
|
--- a/components/engine/client/interface.go
|
|
+++ b/components/engine/client/interface.go
|
|
@@ -98,7 +98,7 @@ type ImageAPIClient interface {
|
|
ImagePush(ctx context.Context, ref string, options types.ImagePushOptions) (io.ReadCloser, error)
|
|
ImageRemove(ctx context.Context, image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
|
|
ImageSearch(ctx context.Context, term string, options types.ImageSearchOptions) ([]registry.SearchResult, error)
|
|
- ImageSave(ctx context.Context, images []string) (io.ReadCloser, error)
|
|
+ ImageSave(ctx context.Context, images []string, compress bool) (io.ReadCloser, error)
|
|
ImageTag(ctx context.Context, image, ref string) error
|
|
ImagesPrune(ctx context.Context, pruneFilter filters.Args) (types.ImagesPruneReport, error)
|
|
}
|
|
diff --git a/components/engine/daemon/images/image_exporter.go b/components/engine/daemon/images/image_exporter.go
|
|
index 58105dcb71..dbe5811f30 100644
|
|
--- a/components/engine/daemon/images/image_exporter.go
|
|
+++ b/components/engine/daemon/images/image_exporter.go
|
|
@@ -11,9 +11,9 @@ import (
|
|
// stream. All images with the given tag and all versions containing
|
|
// the same tag are exported. names is the set of tags to export, and
|
|
// outStream is the writer which the images are written to.
|
|
-func (i *ImageService) ExportImage(names []string, outStream io.Writer) error {
|
|
+func (i *ImageService) ExportImage(names []string, compress bool, outStream io.Writer) error {
|
|
imageExporter := tarexport.NewTarExporter(i.imageStore, i.layerStores, i.referenceStore, i)
|
|
- return imageExporter.Save(names, outStream)
|
|
+ return imageExporter.Save(names, compress, outStream)
|
|
}
|
|
|
|
// LoadImage uploads a set of images into the repository. This is the
|
|
diff --git a/components/engine/distribution/push.go b/components/engine/distribution/push.go
|
|
index eb3bc55974..ca49f22926 100644
|
|
--- a/components/engine/distribution/push.go
|
|
+++ b/components/engine/distribution/push.go
|
|
@@ -158,7 +158,7 @@ func Push(ctx context.Context, ref reference.Named, imagePushConfig *ImagePushCo
|
|
// is finished. This allows the caller to make sure the goroutine finishes
|
|
// before it releases any resources connected with the reader that was
|
|
// passed in.
|
|
-func compress(in io.Reader) (io.ReadCloser, chan struct{}) {
|
|
+func Compress(in io.Reader) (io.ReadCloser, chan struct{}) {
|
|
compressionDone := make(chan struct{})
|
|
|
|
pipeReader, pipeWriter := io.Pipe()
|
|
diff --git a/components/engine/distribution/push_v2.go b/components/engine/distribution/push_v2.go
|
|
index 9dc3e7a2a6..f5922d65bf 100644
|
|
--- a/components/engine/distribution/push_v2.go
|
|
+++ b/components/engine/distribution/push_v2.go
|
|
@@ -447,7 +447,7 @@ func (pd *v2PushDescriptor) uploadUsingSession(
|
|
|
|
switch m := pd.layer.MediaType(); m {
|
|
case schema2.MediaTypeUncompressedLayer:
|
|
- compressedReader, compressionDone := compress(reader)
|
|
+ compressedReader, compressionDone := Compress(reader)
|
|
defer func(closer io.Closer) {
|
|
closer.Close()
|
|
<-compressionDone
|
|
diff --git a/components/engine/image/image.go b/components/engine/image/image.go
|
|
index 7e0646f072..bb6046b5ec 100644
|
|
--- a/components/engine/image/image.go
|
|
+++ b/components/engine/image/image.go
|
|
@@ -212,7 +212,7 @@ func NewHistory(author, comment, createdBy string, isEmptyLayer bool) History {
|
|
type Exporter interface {
|
|
Load(io.ReadCloser, io.Writer, bool) error
|
|
// TODO: Load(net.Context, io.ReadCloser, <- chan StatusMessage) error
|
|
- Save([]string, io.Writer) error
|
|
+ Save([]string, bool, io.Writer) error
|
|
}
|
|
|
|
// NewFromJSON creates an Image configuration from json.
|
|
diff --git a/components/engine/image/tarexport/save.go b/components/engine/image/tarexport/save.go
|
|
index 4e734b3503..0683f1704f 100644
|
|
--- a/components/engine/image/tarexport/save.go
|
|
+++ b/components/engine/image/tarexport/save.go
|
|
@@ -12,6 +12,7 @@ import (
|
|
"time"
|
|
|
|
"github.com/docker/distribution"
|
|
+ dd "github.com/docker/docker/distribution"
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/docker/docker/image"
|
|
"github.com/docker/docker/image/v1"
|
|
@@ -35,9 +36,10 @@ type saveSession struct {
|
|
images map[image.ID]*imageDescriptor
|
|
savedLayers map[string]struct{}
|
|
diffIDPaths map[layer.DiffID]string // cache every diffID blob to avoid duplicates
|
|
+ compress bool
|
|
}
|
|
|
|
-func (l *tarexporter) Save(names []string, outStream io.Writer) error {
|
|
+func (l *tarexporter) Save(names []string, compress bool, outStream io.Writer) error {
|
|
images, err := l.parseNames(names)
|
|
if err != nil {
|
|
return err
|
|
@@ -45,7 +47,7 @@ func (l *tarexporter) Save(names []string, outStream io.Writer) error {
|
|
|
|
// Release all the image top layer references
|
|
defer l.releaseLayerReferences(images)
|
|
- return (&saveSession{tarexporter: l, images: images}).save(outStream)
|
|
+ return (&saveSession{tarexporter: l, images: images, compress: compress}).save(outStream)
|
|
}
|
|
|
|
// parseNames will parse the image names to a map which contains image.ID to *imageDescriptor.
|
|
@@ -408,7 +410,17 @@ func (s *saveSession) saveLayer(id layer.ChainID, legacyImg image.V1Image, creat
|
|
}
|
|
defer arch.Close()
|
|
|
|
- if _, err := io.Copy(tarFile, arch); err != nil {
|
|
+ reader := arch
|
|
+ var compressionDone chan struct{}
|
|
+ if s.compress {
|
|
+ reader, compressionDone = dd.Compress(arch)
|
|
+ defer func(closer io.Closer) {
|
|
+ closer.Close()
|
|
+ <-compressionDone
|
|
+ }(reader)
|
|
+ }
|
|
+
|
|
+ if _, err := io.Copy(tarFile, reader); err != nil {
|
|
return distribution.Descriptor{}, err
|
|
}
|
|
|
|
diff --git a/components/engine/integration/plugin/authz/authz_plugin_test.go b/components/engine/integration/plugin/authz/authz_plugin_test.go
|
|
index 105affc1af..c740fc1dbd 100644
|
|
--- a/components/engine/integration/plugin/authz/authz_plugin_test.go
|
|
+++ b/components/engine/integration/plugin/authz/authz_plugin_test.go
|
|
@@ -435,7 +435,7 @@ func TestAuthzPluginEnsureContainerCopyToFrom(t *testing.T) {
|
|
|
|
func imageSave(client client.APIClient, path, image string) error {
|
|
ctx := context.Background()
|
|
- responseReader, err := client.ImageSave(ctx, []string{image})
|
|
+ responseReader, err := client.ImageSave(ctx, []string{image}, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
diff --git a/components/engine/internal/test/daemon/daemon.go b/components/engine/internal/test/daemon/daemon.go
|
|
index 4f56dff9bb..f16a43de45 100644
|
|
--- a/components/engine/internal/test/daemon/daemon.go
|
|
+++ b/components/engine/internal/test/daemon/daemon.go
|
|
@@ -557,7 +557,7 @@ func (d *Daemon) LoadBusybox(t assert.TestingT) {
|
|
defer clientHost.Close()
|
|
|
|
ctx := context.Background()
|
|
- reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"})
|
|
+ reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"}, false)
|
|
assert.NilError(t, err, "failed to download busybox")
|
|
defer reader.Close()
|
|
|
|
--
|
|
2.17.1
|
|
|