Skip to content

Implement daemon.Write #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/crane/append.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func doAppend(src, dst, tar, output string) {
}

if output != "" {
if err := tarball.Write(output, dstTag, image, &tarball.WriteOptions{}); err != nil {
if err := tarball.WriteToFile(output, dstTag, image, &tarball.WriteOptions{}); err != nil {
log.Fatalln(err)
}
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/crane/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func pull(_ *cobra.Command, args []string) {
log.Fatalln(err)
}

if err := tarball.Write(dst, t, i, &tarball.WriteOptions{}); err != nil {
if err := tarball.WriteToFile(dst, t, i, &tarball.WriteOptions{}); err != nil {
log.Fatalln(err)
}
}
8 changes: 7 additions & 1 deletion v1/daemon/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,23 @@ go_library(
"//name:go_default_library",
"//v1:go_default_library",
"//v1/tarball:go_default_library",
"//vendor/github.com/docker/docker/api/types:go_default_library",
"//vendor/github.com/docker/docker/client:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = ["image_test.go"],
srcs = [
"image_test.go",
"write_test.go",
],
data = ["//v1/tarball:test_image_1.tar"], # keep
embed = [":go_default_library"],
deps = [
"//name:go_default_library",
"//v1/random:go_default_library",
"//v1/tarball:go_default_library",
],
)
47 changes: 43 additions & 4 deletions v1/daemon/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,58 @@
package daemon

import (
"fmt"
"context"
"io"
"io/ioutil"

"github.com/pkg/errors"

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"

"github.com/google/go-containerregistry/name"
"github.com/google/go-containerregistry/v1"
"github.com/google/go-containerregistry/v1/tarball"
)

// API interface for testing.
type ImageLoader interface {
ImageLoad(context.Context, io.Reader, bool) (types.ImageLoadResponse, error)
}

// This is a variable so we can override in tests.
var getImageLoader = func() (ImageLoader, error) {
return client.NewEnvClient()
}

// WriteOptions are used to expose optional information to guide or
// control the image write.
type WriteOptions struct {
// TODO(dlorenc): What kinds of knobs does the daemon expose?
}

// Write saves the image into the daemon as the given reference.
func Write(ref name.Reference, img v1.Image, wo WriteOptions) error {
return fmt.Errorf("NYI: daemon.Write(%v)", ref)
// Write saves the image into the daemon as the given tag.
func Write(tag name.Tag, img v1.Image, wo WriteOptions) (string, error) {
cli, err := getImageLoader()
if err != nil {
return "", err
}

pr, pw := io.Pipe()
go func() {
pw.CloseWithError(tarball.Write(tag, img, &tarball.WriteOptions{}, pw))
}()

// write the image in docker save format first, then load it
resp, err := cli.ImageLoad(context.Background(), pr, false)
if err != nil {
return "", errors.Wrapf(err, "error loading image")
}
defer resp.Body.Close()
b, readErr := ioutil.ReadAll(resp.Body)
response := string(b)
if readErr != nil {
return response, errors.Wrapf(err, "error reading load response body")
}
return response, nil
}
59 changes: 59 additions & 0 deletions v1/daemon/write_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2018 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package daemon

import (
"context"
"io"
"io/ioutil"
"strings"
"testing"

"github.com/docker/docker/api/types"
"github.com/google/go-containerregistry/name"
"github.com/google/go-containerregistry/v1/tarball"
)

type MockImageLoader struct{}

func (m *MockImageLoader) ImageLoad(_ context.Context, _ io.Reader, _ bool) (types.ImageLoadResponse, error) {
return types.ImageLoadResponse{
Body: ioutil.NopCloser(strings.NewReader("Loaded")),
}, nil
}

func init() {
getImageLoader = func() (ImageLoader, error) {
return &MockImageLoader{}, nil
}
}

func TestWriteImage(t *testing.T) {
image, err := tarball.ImageFromPath("../tarball/test_image_1.tar", nil)
if err != nil {
t.Errorf("Error loading image: %v", err.Error())
}
tag, err := name.NewTag("test_image_2:latest", name.WeakValidation)
if err != nil {
t.Errorf(err.Error())
}
response, err := Write(tag, image, WriteOptions{})
if err != nil {
t.Errorf("Error writing image tar: %s", err.Error())
}
if !strings.Contains(response, "Loaded") {
t.Errorf("Error loading image. Response: %s", response)
}
}
30 changes: 17 additions & 13 deletions v1/tarball/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,24 @@ type WriteOptions struct {
// TODO(mattmoor): Whether to store things compressed?
}

// Write saves the image as the given tag in a tarball at the given path.
func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
// Write in the compressed format.
// This is a tarball, on-disk, with:
// One manifest.json file at the top level containing information about several images.
// One file for each layer, named after the layer's SHA.
// One file for the config blob, named after its SHA.

// WriteToFile writes in the compressed format to a tarball, on disk.
// This is just syntactic sugar wrapping tarball.Write with a new file.
func WriteToFile(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
w, err := os.Create(p)
if err != nil {
return err
}
defer w.Close()

return Write(tag, img, wo, w)
}

// Write the contents of the image to the provided reader, in the compressed format.
// The contents are written in the following format:
// One manifest.json file at the top level containing information about several images.
// One file for each layer, named after the layer's SHA.
// One file for the config blob, named after its SHA.
func Write(tag name.Tag, img v1.Image, wo *WriteOptions, w io.Writer) error {
tf := tar.NewWriter(w)
defer tf.Close()

Expand All @@ -58,7 +62,7 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
if err != nil {
return err
}
if err := writeFile(tf, cfgName.String(), bytes.NewReader(cfgBlob), int64(len(cfgBlob))); err != nil {
if err := writeTarEntry(tf, cfgName.String(), bytes.NewReader(cfgBlob), int64(len(cfgBlob))); err != nil {
return err
}

Expand Down Expand Up @@ -94,10 +98,9 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
return err
}

if err := writeFile(tf, layerFiles[i], r, blobSize); err != nil {
if err := writeTarEntry(tf, layerFiles[i], r, blobSize); err != nil {
return err
}

}

// Generate the tar descriptor and write it.
Expand All @@ -112,10 +115,11 @@ func Write(p string, tag name.Tag, img v1.Image, wo *WriteOptions) error {
if err != nil {
return err
}
return writeFile(tf, "manifest.json", bytes.NewReader(tdBytes), int64(len(tdBytes)))
return writeTarEntry(tf, "manifest.json", bytes.NewReader(tdBytes), int64(len(tdBytes)))
}

func writeFile(tf *tar.Writer, path string, r io.Reader, size int64) error {
// write a file to the provided writer with a corresponding tar header
func writeTarEntry(tf *tar.Writer, path string, r io.Reader, size int64) error {
hdr := &tar.Header{
Mode: 0644,
Typeflag: tar.TypeReg,
Expand Down
2 changes: 1 addition & 1 deletion v1/tarball/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestWrite(t *testing.T) {
if err != nil {
t.Fatalf("Error creating test tag.")
}
if err := Write(fp.Name(), tag, randImage, nil); err != nil {
if err := WriteToFile(fp.Name(), tag, randImage, nil); err != nil {
t.Fatalf("Unexpected error writing tarball: %v", err)
}

Expand Down