Skip to content
Closed
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
259 changes: 259 additions & 0 deletions image/image_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
// Copyright 2016 The Linux Foundation
//
// 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 image

import (
"archive/tar"
"bytes"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
)

const (
layoutStr = `{"imageLayoutVersion": "1.0.0"}`

configStr = `{
"created": "2015-10-31T22:22:56.015925234Z",
"author": "Alyssa P. Hacker <[email protected]>",
"architecture": "amd64",
"os": "linux",
"config": {
"User": "alice",
"Memory": 2048,
"MemorySwap": 4096,
"CpuShares": 8,
"ExposedPorts": {
"8080/tcp": {}
},
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"FOO=oci_is_a",
"BAR=well_written_spec"
],
"Entrypoint": [
"/bin/my-app-binary"
],
"Cmd": [
"--foreground",
"--config",
"/etc/my-app.d/default.cfg"
],
"Volumes": {
"/var/job-result-data": {},
"/var/log/my-app-logs": {}
},
"WorkingDir": "/home/alice"
},
"rootfs": {
"diff_ids": [
"sha256:c6f988f4874bb0add23a778f753c65efe992244e148a1d2ec2a8b664fb66bbd1",
"sha256:5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef"
],
"type": "layers"
},
"history": [
{
"created": "2015-10-31T22:22:54.690851953Z",
"created_by": "/bin/sh -c #(nop) ADD file:a3bc1e842b69636f9df5256c49c5374fb4eef1e281fe3f282c65fb853ee171c5 in /"
},
{
"created": "2015-10-31T22:22:55.613815829Z",
"created_by": "/bin/sh -c #(nop) CMD [\"sh\"]",
"empty_layer": true
}
]
}
`
)

var (
refStr = `{"digest":"<manifest_digest>","mediaType":"application/vnd.oci.image.manifest.v1+json","size":<manifest_size>}`

manifestStr = `{
"annotations": null,
"config": {
"digest": "<config_digest>",
"mediaType": "application/vnd.oci.image.config.v1+json",
"size": <config_size>
},
"layers": [
{
"digest": "<layer_digest>",
"mediaType": "application/vnd.oci.image.layer.tar+gzip",
"size": <layer_size>
}
],
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"schemaVersion": 2
}
`
)

func TestValidateLayout(t *testing.T) {
root, err := ioutil.TempDir("", "oci-test")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(root)

err = os.MkdirAll(filepath.Join(root, "blobs", "sha256"), 0700)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of duplicating this logic from manifest_test.go, I'd rather have it pulled out into a helper.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And “create image-layout fodder for testing” is likely to be useful stuff in lots of places. Can we make layer-generator, etc. helpers instead of baking it into TestUnpackLayerRemovePartialyUnpackedFile. I'm missing #159 and 87d6a53 ;).

@wking Got it. Can we place this code to a function createTarFile in *_test.go? For now this is only used by unit test, and had better not to be compiled to binary.
For that I will fix image_test.go and manifest_test.go to reuse the func createTarFile

Copy link
Contributor Author

@xiekeyang xiekeyang Sep 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wking
Maybe I didn't fully catch your point. For unit test purpose, I can improve the duplicated code.
But I'm not sure if we must add rename behavior here, as your following comment:

You can use rename no matter what the source and destination are, and
only have to pay for a copy if they are on different filesystems. In
this case, tmp1/…, {tmp1}/tmp/… and {tmp1}/blobs/sha256/… are all in
the same filesystem, so rename will be cheap in all cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Wed, Sep 14, 2016 at 12:43:24AM -0700, xiekeyang wrote:

  • err = os.MkdirAll(filepath.Join(root, "blobs", "sha256"), 0700)

And “create image-layout fodder for testing” is likely to be
useful stuff in lots of places. Can we make layer-generator,
etc. helpers instead of baking it into
TestUnpackLayerRemovePartialyUnpackedFile. I'm missing #159 and
87d6a53 ;).

@wking Got it. Can we place this code to a function createTarFile
in *_test.go? For now this is only used by unit test, and had
better not to be compiled to binary. For that I will fix
image_test.go and manifest_test.go to reuse the func createTarFile

This sounds fine to me. I'd encourage helpers for:

  • create image-layout (directory or tar file) 1
  • create dummy layer
  • PutJSON 2
  • PutRef 3

if err != nil {
t.Fatal(err)
}

err = os.MkdirAll(filepath.Join(root, "refs"), 0700)
if err != nil {
t.Fatal(err)
}

desc, err := createLayerFile(root)
if err != nil {
t.Fatal(err)
}
manifestStr = strings.Replace(manifestStr, "<layer_digest>", desc.Digest, 1)
manifestStr = strings.Replace(manifestStr, "<layer_size>", strconv.FormatInt(desc.Size, 10), 1)

desc, err = createConfigTestFile(root)
if err != nil {
t.Fatal(err)
}
manifestStr = strings.Replace(manifestStr, "<config_digest>", desc.Digest, 1)
manifestStr = strings.Replace(manifestStr, "<config_size>", strconv.FormatInt(desc.Size, 10), 1)

mft, err := createManifestFile(root, manifestStr)
if err != nil {
t.Fatal(err)
}

err = createRefFile(root, mft)
if err != nil {
t.Fatal(err)
}

err = createLayoutFile(root)
if err != nil {
t.Fatal(err)
}

err = ValidateLayout(root, []string{"latest"}, nil)
if err != nil {
t.Fatal(err)
}
}

func createLayerFile(root string) (descriptor, error) {
layerPath := filepath.Join(root, "blobs", "sha256", "test.tar")

desc, err := createTarFile(layerPath, []tarContent{
tarContent{&tar.Header{Name: "test", Size: 4, Mode: 0600}, []byte("test")},
})
if err != nil {
return descriptor{}, err
}

err = os.Rename(layerPath, filepath.Join(root, "blobs", "sha256", desc.Digest))
if err != nil {
return descriptor{}, err
}

return descriptor{Digest: "sha256:" + desc.Digest, Size: desc.Size}, nil
}

func createConfigTestFile(root string) (descriptor, error) {
oldpath := filepath.Join(root, "blobs", "sha256", "test-config")
f, err := os.Create(oldpath)
if err != nil {
return descriptor{}, err
}
defer f.Close()

_, err = io.Copy(f, bytes.NewBuffer([]byte(configStr)))
if err != nil {
return descriptor{}, err
}

// generate sha256 hash
h := sha256.New()
size, err := io.Copy(h, bytes.NewBuffer([]byte(configStr)))
if err != nil {
return descriptor{}, err
}
digest := fmt.Sprintf("%x", h.Sum(nil))

err = os.Rename(oldpath, filepath.Join(root, "blobs", "sha256", digest))
if err != nil {
return descriptor{}, err
}
return descriptor{Digest: "sha256:" + digest, Size: size}, nil
}

func createManifestFile(root, str string) (descriptor, error) {
oldpath := filepath.Join(root, "blobs", "sha256", "test-manifest")
f, err := os.Create(oldpath)
if err != nil {
return descriptor{}, err
}
defer f.Close()

_, err = io.Copy(f, bytes.NewBuffer([]byte(str)))
if err != nil {
return descriptor{}, err
}

// generate sha256 hash
h := sha256.New()
size, err := io.Copy(h, bytes.NewBuffer([]byte(str)))
if err != nil {
return descriptor{}, err
}
digest := fmt.Sprintf("%x", h.Sum(nil))

err = os.Rename(oldpath, filepath.Join(root, "blobs", "sha256", digest))
if err != nil {
return descriptor{}, err
}
return descriptor{Digest: "sha256:" + digest, Size: size}, nil
}

func createRefFile(root string, mft descriptor) error {
refpath := filepath.Join(root, "refs", "latest")
f, err := os.Create(refpath)
if err != nil {
return err
}
defer f.Close()
refStr = strings.Replace(refStr, "<manifest_digest>", mft.Digest, -1)
refStr = strings.Replace(refStr, "<manifest_size>", strconv.FormatInt(mft.Size, 10), -1)
_, err = io.Copy(f, bytes.NewBuffer([]byte(refStr)))
return err
}

func createLayoutFile(root string) error {
layoutPath := filepath.Join(root, "oci-layout")
f, err := os.Create(layoutPath)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, bytes.NewBuffer([]byte(layoutStr)))
return err
}
93 changes: 58 additions & 35 deletions image/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,27 +28,25 @@ import (
"testing"
)

type tarContent struct {
header *tar.Header
b []byte
}

func TestUnpackLayerDuplicateEntries(t *testing.T) {
tmp1, err := ioutil.TempDir("", "test-dup")
if err != nil {
t.Fatal(err)
}
tarfile := filepath.Join(tmp1, "test.tar")
f, err := os.Create(tarfile)

_, err = createTarFile(tarfile, []tarContent{
tarContent{&tar.Header{Name: "test", Size: 4, Mode: 0600}, []byte("test")},
tarContent{&tar.Header{Name: "test", Size: 5, Mode: 0600}, []byte("test1")},
})
if err != nil {
t.Fatal(err)
}
defer f.Close()
defer os.RemoveAll(tmp1)
gw := gzip.NewWriter(f)
tw := tar.NewWriter(gw)

tw.WriteHeader(&tar.Header{Name: "test", Size: 4, Mode: 0600})
io.Copy(tw, bytes.NewReader([]byte("test")))
tw.WriteHeader(&tar.Header{Name: "test", Size: 5, Mode: 0600})
io.Copy(tw, bytes.NewReader([]byte("test1")))
tw.Close()
gw.Close()

r, err := os.Open(tarfile)
if err != nil {
Expand Down Expand Up @@ -76,40 +74,23 @@ func TestUnpackLayer(t *testing.T) {
t.Fatal(err)
}
tarfile := filepath.Join(tmp1, "blobs", "sha256", "test.tar")
f, err := os.Create(tarfile)
if err != nil {
t.Fatal(err)
}

gw := gzip.NewWriter(f)
tw := tar.NewWriter(gw)

tw.WriteHeader(&tar.Header{Name: "test", Size: 4, Mode: 0600})
io.Copy(tw, bytes.NewReader([]byte("test")))
tw.Close()
gw.Close()
f.Close()

// generate sha256 hash
h := sha256.New()
file, err := os.Open(tarfile)
desc, err := createTarFile(tarfile, []tarContent{
tarContent{&tar.Header{Name: "test", Size: 4, Mode: 0600}, []byte("test")},
})
if err != nil {
t.Fatal(err)
}
defer file.Close()
_, err = io.Copy(h, file)
if err != nil {
t.Fatal(err)
}
err = os.Rename(tarfile, filepath.Join(tmp1, "blobs", "sha256", fmt.Sprintf("%x", h.Sum(nil))))

err = os.Rename(tarfile, filepath.Join(tmp1, "blobs", "sha256", desc.Digest))
if err != nil {
t.Fatal(err)
}

testManifest := manifest{
Layers: []descriptor{descriptor{
MediaType: "application/vnd.oci.image.layer.tar+gzip",
Digest: fmt.Sprintf("sha256:%s", fmt.Sprintf("%x", h.Sum(nil))),
Digest: fmt.Sprintf("sha256:%s", desc.Digest),
}},
}
err = testManifest.unpack(newPathWalker(tmp1), filepath.Join(tmp1, "rootfs"))
Expand All @@ -122,3 +103,45 @@ func TestUnpackLayer(t *testing.T) {
t.Fatal(err)
}
}

func createTarFile(name string, list []tarContent) (descriptor, error) {
f, err := os.Create(name)
if err != nil {
return descriptor{}, err
}
gw := gzip.NewWriter(f)
tw := tar.NewWriter(gw)

for _, content := range list {
if err = tw.WriteHeader(content.header); err != nil {
tw.Close()
gw.Close()
f.Close()
return descriptor{}, err
}
if _, err = io.Copy(tw, bytes.NewReader(content.b)); err != nil {
tw.Close()
gw.Close()
f.Close()
return descriptor{}, err
}
}
tw.Close()
gw.Close()
f.Close()

// generate sha256 hash
h := sha256.New()
file, err := os.Open(name)
if err != nil {
return descriptor{}, err
}
defer file.Close()

size, err := io.Copy(h, file)
if err != nil {
return descriptor{}, err
}

return descriptor{Digest: fmt.Sprintf("%x", h.Sum(nil)), Size: size}, nil
}