summaryrefslogtreecommitdiffstats
path: root/pkg/crane/crane_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/crane/crane_test.go')
-rw-r--r--pkg/crane/crane_test.go574
1 files changed, 574 insertions, 0 deletions
diff --git a/pkg/crane/crane_test.go b/pkg/crane/crane_test.go
new file mode 100644
index 0000000..9f4d124
--- /dev/null
+++ b/pkg/crane/crane_test.go
@@ -0,0 +1,574 @@
+// Copyright 2019 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 crane_test
+
+import (
+ "archive/tar"
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "path"
+ "strings"
+ "testing"
+
+ "github.com/google/go-containerregistry/internal/compare"
+ "github.com/google/go-containerregistry/pkg/authn"
+ "github.com/google/go-containerregistry/pkg/crane"
+ "github.com/google/go-containerregistry/pkg/name"
+ "github.com/google/go-containerregistry/pkg/registry"
+ v1 "github.com/google/go-containerregistry/pkg/v1"
+ "github.com/google/go-containerregistry/pkg/v1/empty"
+ "github.com/google/go-containerregistry/pkg/v1/mutate"
+ "github.com/google/go-containerregistry/pkg/v1/random"
+ "github.com/google/go-containerregistry/pkg/v1/remote"
+)
+
+// TODO(jonjohnsonjr): Test crane.Copy failures.
+func TestCraneRegistry(t *testing.T) {
+ // Set up a fake registry.
+ s := httptest.NewServer(registry.New())
+ defer s.Close()
+ u, err := url.Parse(s.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ src := fmt.Sprintf("%s/test/crane", u.Host)
+ dst := fmt.Sprintf("%s/test/crane/copy", u.Host)
+
+ // Expected values.
+ img, err := random.Image(1024, 5)
+ if err != nil {
+ t.Fatal(err)
+ }
+ digest, err := img.Digest()
+ if err != nil {
+ t.Fatal(err)
+ }
+ rawManifest, err := img.RawManifest()
+ if err != nil {
+ t.Fatal(err)
+ }
+ manifest, err := img.Manifest()
+ if err != nil {
+ t.Fatal(err)
+ }
+ config, err := img.RawConfigFile()
+ if err != nil {
+ t.Fatal(err)
+ }
+ layer, err := img.LayerByDigest(manifest.Layers[0].Digest)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Load up the registry.
+ if err := crane.Push(img, src); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test that `crane.Foo` returns expected values.
+ d, err := crane.Digest(src)
+ if err != nil {
+ t.Error(err)
+ } else if d != digest.String() {
+ t.Errorf("Digest(): %v != %v", d, digest)
+ }
+
+ m, err := crane.Manifest(src)
+ if err != nil {
+ t.Error(err)
+ } else if string(m) != string(rawManifest) {
+ t.Errorf("Manifest(): %v != %v", m, rawManifest)
+ }
+
+ c, err := crane.Config(src)
+ if err != nil {
+ t.Error(err)
+ } else if string(c) != string(config) {
+ t.Errorf("Config(): %v != %v", c, config)
+ }
+
+ // Make sure we pull what we pushed.
+ pulled, err := crane.Pull(src)
+ if err != nil {
+ t.Error(err)
+ }
+ if err := compare.Images(img, pulled); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test that the copied image is the same as the source.
+ if err := crane.Copy(src, dst); err != nil {
+ t.Fatal(err)
+ }
+
+ // Make sure what we copied is equivalent.
+ // Also, get options coverage in a dumb way.
+ copied, err := crane.Pull(dst, crane.Insecure, crane.WithTransport(http.DefaultTransport), crane.WithAuth(authn.Anonymous), crane.WithAuthFromKeychain(authn.DefaultKeychain), crane.WithUserAgent("crane/tests"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := compare.Images(pulled, copied); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := crane.Tag(dst, "crane-tag"); err != nil {
+ t.Fatal(err)
+ }
+
+ // Make sure what we tagged is equivalent.
+ tagged, err := crane.Pull(fmt.Sprintf("%s:%s", dst, "crane-tag"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := compare.Images(pulled, tagged); err != nil {
+ t.Fatal(err)
+ }
+
+ layerRef := fmt.Sprintf("%s/test/crane@%s", u.Host, manifest.Layers[0].Digest)
+ pulledLayer, err := crane.PullLayer(layerRef)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := compare.Layers(pulledLayer, layer); err != nil {
+ t.Fatal(err)
+ }
+
+ // List Tags
+ // dst variable have: latest and crane-tag
+ tags, err := crane.ListTags(dst)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(tags) != 2 {
+ t.Fatalf("wanted 2 tags, got %d", len(tags))
+ }
+
+ // create 4 tags for dst
+ for i := 1; i < 5; i++ {
+ if err := crane.Tag(dst, fmt.Sprintf("honk-tag-%d", i)); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ tags, err = crane.ListTags(dst)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(tags) != 6 {
+ t.Fatalf("wanted 6 tags, got %d", len(tags))
+ }
+
+ // Delete the non existing image
+ if err := crane.Delete(dst + ":honk-image"); err == nil {
+ t.Fatal("wanted err, got nil")
+ }
+
+ // Delete the image
+ if err := crane.Delete(src); err != nil {
+ t.Fatal(err)
+ }
+
+ // check if the image was really deleted
+ if _, err := crane.Pull(src); err == nil {
+ t.Fatal("wanted err, got nil")
+ }
+
+ // check if the copied image still exist
+ dstPulled, err := crane.Pull(dst)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := compare.Images(dstPulled, copied); err != nil {
+ t.Fatal(err)
+ }
+
+ // List Catalog
+ repos, err := crane.Catalog(u.Host)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if len(repos) != 2 {
+ t.Fatalf("wanted 2 repos, got %d", len(repos))
+ }
+
+ // Test pushing layer
+ layer, err = img.LayerByDigest(manifest.Layers[1].Digest)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := crane.Upload(layer, dst); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestCraneCopyIndex(t *testing.T) {
+ // Set up a fake registry.
+ s := httptest.NewServer(registry.New())
+ defer s.Close()
+ u, err := url.Parse(s.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ src := fmt.Sprintf("%s/test/crane", u.Host)
+ dst := fmt.Sprintf("%s/test/crane/copy", u.Host)
+
+ // Load up the registry.
+ idx, err := random.Index(1024, 3, 3)
+ if err != nil {
+ t.Fatal(err)
+ }
+ ref, err := name.ParseReference(src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := remote.WriteIndex(ref, idx); err != nil {
+ t.Fatal(err)
+ }
+
+ // Test that the copied index is the same as the source.
+ if err := crane.Copy(src, dst); err != nil {
+ t.Fatal(err)
+ }
+
+ d, err := crane.Digest(src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ cp, err := crane.Digest(dst)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if d != cp {
+ t.Errorf("Copied Digest(): %v != %v", d, cp)
+ }
+}
+
+func TestWithPlatform(t *testing.T) {
+ // Set up a fake registry with a platform-specific image.
+ s := httptest.NewServer(registry.New())
+ defer s.Close()
+ u, err := url.Parse(s.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ imgs := []mutate.IndexAddendum{}
+ for _, plat := range []string{
+ "linux/amd64",
+ "linux/arm",
+ } {
+ img, err := crane.Image(map[string][]byte{
+ "platform.txt": []byte(plat),
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ parts := strings.Split(plat, "/")
+ imgs = append(imgs, mutate.IndexAddendum{
+ Add: img,
+ Descriptor: v1.Descriptor{
+ Platform: &v1.Platform{
+ OS: parts[0],
+ Architecture: parts[1],
+ },
+ },
+ })
+ }
+
+ idx := mutate.AppendManifests(empty.Index, imgs...)
+
+ src := path.Join(u.Host, "src")
+ dst := path.Join(u.Host, "dst")
+
+ ref, err := name.ParseReference(src)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Populate registry so we can copy from it.
+ if err := remote.WriteIndex(ref, idx); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := crane.Copy(src, dst, crane.WithPlatform(imgs[1].Platform)); err != nil {
+ t.Fatal(err)
+ }
+
+ want, err := crane.Manifest(src, crane.WithPlatform(imgs[1].Platform))
+ if err != nil {
+ t.Fatal(err)
+ }
+ got, err := crane.Manifest(dst)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if string(got) != string(want) {
+ t.Errorf("Manifest(%q) != Manifest(%q): (\n\n%s\n\n!=\n\n%s\n\n)", dst, src, string(got), string(want))
+ }
+
+ arch := "real fake doors"
+
+ // Now do a fake platform, should fail
+ if _, err := crane.Manifest(src, crane.WithPlatform(&v1.Platform{
+ OS: "does-not-exist",
+ Architecture: arch,
+ })); err == nil {
+ t.Error("crane.Manifest(fake platform): got nil want err")
+ } else if !strings.Contains(err.Error(), arch) {
+ t.Errorf("crane.Manifest(fake platform): expected %q in error, got: %v", arch, err)
+ }
+}
+
+func TestCraneTarball(t *testing.T) {
+ t.Parallel()
+ // Write an image as a tarball.
+ tmp, err := os.CreateTemp("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(tmp.Name())
+
+ img, err := random.Image(1024, 5)
+ if err != nil {
+ t.Fatal(err)
+ }
+ digest, err := img.Digest()
+ if err != nil {
+ t.Fatal(err)
+ }
+ src := fmt.Sprintf("test/crane@%s", digest)
+
+ if err := crane.Save(img, src, tmp.Name()); err != nil {
+ t.Errorf("Save: %v", err)
+ }
+
+ // Make sure the image we load has a matching digest.
+ img, err = crane.Load(tmp.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ d, err := img.Digest()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if d != digest {
+ t.Errorf("digest mismatch: %v != %v", d, digest)
+ }
+}
+
+func TestCraneSaveLegacy(t *testing.T) {
+ t.Parallel()
+ // Write an image as a legacy tarball.
+ tmp, err := os.CreateTemp("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(tmp.Name())
+
+ img, err := random.Image(1024, 5)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if err := crane.SaveLegacy(img, "test/crane", tmp.Name()); err != nil {
+ t.Errorf("SaveOCI: %v", err)
+ }
+}
+
+func TestCraneSaveOCI(t *testing.T) {
+ t.Parallel()
+ // Write an image as an OCI image layout.
+ tmp := t.TempDir()
+
+ img, err := random.Image(1024, 5)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := crane.SaveOCI(img, tmp); err != nil {
+ t.Errorf("SaveLegacy: %v", err)
+ }
+}
+
+func TestCraneFilesystem(t *testing.T) {
+ t.Parallel()
+ tmp, err := os.CreateTemp("", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ img, err := random.Image(1024, 5)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ name := "/some/file"
+ content := []byte("sentinel")
+
+ tw := tar.NewWriter(tmp)
+ if err := tw.WriteHeader(&tar.Header{
+ Size: int64(len(content)),
+ Name: name,
+ }); err != nil {
+ t.Fatal(err)
+ }
+ if _, err := tw.Write(content); err != nil {
+ t.Fatal(err)
+ }
+ tw.Flush()
+ tw.Close()
+
+ img, err = crane.Append(img, tmp.Name())
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var buf bytes.Buffer
+ if err := crane.Export(img, &buf); err != nil {
+ t.Fatal(err)
+ }
+
+ tr := tar.NewReader(&buf)
+ for {
+ header, err := tr.Next()
+ if errors.Is(err, io.EOF) {
+ t.Fatalf("didn't find find")
+ } else if err != nil {
+ t.Fatal(err)
+ }
+ if header.Name == name {
+ b, err := io.ReadAll(tr)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(b) != string(content) {
+ t.Fatalf("got back wrong content: %v != %v", string(b), string(content))
+ }
+ break
+ }
+ }
+}
+
+func TestStreamingAppend(t *testing.T) {
+ // Stdin will be an uncompressed layer.
+ layer, err := crane.Layer(map[string][]byte{
+ "hello": []byte(`world`),
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ rc, err := layer.Uncompressed()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tmp, err := os.CreateTemp("", "crane-append")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.Remove(tmp.Name())
+
+ if _, err := io.Copy(tmp, rc); err != nil {
+ t.Fatal(err)
+ }
+
+ stdin := os.Stdin
+ defer func() {
+ os.Stdin = stdin
+ }()
+
+ os.Stdin = tmp
+
+ img, err := crane.Append(empty.Image, "-")
+ if err != nil {
+ t.Fatal(err)
+ }
+ ll, err := img.Layers()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if want, got := 1, len(ll); want != got {
+ t.Errorf("crane.Append(stdin) - len(layers): want %d != got %d", want, got)
+ }
+}
+
+func TestBadInputs(t *testing.T) {
+ t.Parallel()
+ invalid := "/dev/null/@@@@@@"
+
+ // Create a valid image reference that will fail with not found.
+ s := httptest.NewServer(http.NotFoundHandler())
+ u, err := url.Parse(s.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+ valid404 := fmt.Sprintf("%s/some/image", u.Host)
+
+ // e drops the first parameter so we can use the result of a function
+ // that returns two values as an expression above. This is a bit of a go quirk.
+ e := func(_ any, err error) error {
+ return err
+ }
+
+ for _, tc := range []struct {
+ desc string
+ err error
+ }{
+ {"Push(_, invalid)", crane.Push(nil, invalid)},
+ {"Upload(_, invalid)", crane.Upload(nil, invalid)},
+ {"Delete(invalid)", crane.Delete(invalid)},
+ {"Delete: 404", crane.Delete(valid404)},
+ {"Save(_, invalid)", crane.Save(nil, invalid, "")},
+ {"SaveLegacy(_, invalid)", crane.SaveLegacy(nil, invalid, "")},
+ {"SaveLegacy(_, invalid)", crane.SaveLegacy(nil, valid404, invalid)},
+ {"SaveOCI(_, invalid)", crane.SaveOCI(nil, "")},
+ {"Copy(invalid, invalid)", crane.Copy(invalid, invalid)},
+ {"Copy(404, invalid)", crane.Copy(valid404, invalid)},
+ {"Copy(404, 404)", crane.Copy(valid404, valid404)},
+ {"Tag(invalid, invalid)", crane.Tag(invalid, invalid)},
+ {"Tag(404, invalid)", crane.Tag(valid404, invalid)},
+ {"Tag(404, 404)", crane.Tag(valid404, valid404)},
+ {"Optimize(invalid, invalid)", crane.Optimize(invalid, invalid, []string{})},
+ {"Optimize(404, invalid)", crane.Optimize(valid404, invalid, []string{})},
+ {"Optimize(404, 404)", crane.Optimize(valid404, valid404, []string{})},
+ // These return multiple values, which are hard to use as expressions.
+ {"Pull(invalid)", e(crane.Pull(invalid))},
+ {"Digest(invalid)", e(crane.Digest(invalid))},
+ {"Manifest(invalid)", e(crane.Manifest(invalid))},
+ {"Config(invalid)", e(crane.Config(invalid))},
+ {"Config(404)", e(crane.Config(valid404))},
+ {"ListTags(invalid)", e(crane.ListTags(invalid))},
+ {"ListTags(404)", e(crane.ListTags(valid404))},
+ {"Append(_, invalid)", e(crane.Append(nil, invalid))},
+ {"Catalog(invalid)", e(crane.Catalog(invalid))},
+ {"Catalog(404)", e(crane.Catalog(u.Host))},
+ {"PullLayer(invalid)", e(crane.PullLayer(invalid))},
+ {"LoadTag(_, invalid)", e(crane.LoadTag("", invalid))},
+ {"LoadTag(invalid, 404)", e(crane.LoadTag(invalid, valid404))},
+ } {
+ if tc.err == nil {
+ t.Errorf("%s: expected err, got nil", tc.desc)
+ }
+ }
+}