diff --git a/pkg/v1/hash_test.go b/pkg/v1/hash_test.go index 21d9e68e5..df1be7734 100644 --- a/pkg/v1/hash_test.go +++ b/pkg/v1/hash_test.go @@ -30,14 +30,14 @@ func TestGoodHashes(t *testing.T) { for _, s := range good { h, err := NewHash(s) if err != nil { - t.Errorf("Unexpected error parsing hash: %v", err) + t.Error("Unexpected error parsing hash:", err) } if got, want := h.String(), s; got != want { t.Errorf("String(); got %q, want %q", got, want) } bytes, err := json.Marshal(h) if err != nil { - t.Errorf("Unexpected error json.Marshaling hash: %v", err) + t.Error("Unexpected error json.Marshaling hash:", err) } if got, want := string(bytes), strconv.Quote(h.String()); got != want { t.Errorf("json.Marshal(); got %q, want %q", got, want) @@ -62,7 +62,7 @@ func TestBadHashes(t *testing.T) { for _, s := range bad { h, err := NewHash(s) if err == nil { - t.Errorf("Expected error, got: %v", h) + t.Error("Expected error, got:", h) } } } @@ -71,7 +71,7 @@ func TestSHA256(t *testing.T) { input := "asdf" h, n, err := SHA256(strings.NewReader(input)) if err != nil { - t.Errorf("SHA256(asdf) = %v", err) + t.Error("SHA256(asdf) =", err) } if got, want := h.Algorithm, "sha256"; got != want { t.Errorf("Algorithm; got %v, want %v", got, want) @@ -90,10 +90,10 @@ func TestTextMarshalling(t *testing.T) { foo := make(map[Hash]string) b, err := json.Marshal(foo) if err != nil { - t.Fatalf("could not marshal: %v", err) + t.Fatal("could not marshal:", err) } if err := json.Unmarshal(b, &foo); err != nil { - t.Errorf("could not unmarshal: %v", err) + t.Error("could not unmarshal:", err) } h := &Hash{ diff --git a/pkg/v1/v1util/and_closer.go b/pkg/v1/internal/and/and_closer.go similarity index 65% rename from pkg/v1/v1util/and_closer.go rename to pkg/v1/internal/and/and_closer.go index 0925f13d5..52908e5cf 100644 --- a/pkg/v1/v1util/and_closer.go +++ b/pkg/v1/internal/and/and_closer.go @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC All Rights Reserved. +// Copyright 2020 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. @@ -12,36 +12,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v1util +package and import ( "io" ) -// readAndCloser implements io.ReadCloser by reading from a particular io.Reader +// ReadCloser implements io.ReadCloser by reading from a particular io.Reader // and then calling the provided "Close()" method. -type readAndCloser struct { +type ReadCloser struct { io.Reader CloseFunc func() error } -var _ io.ReadCloser = (*readAndCloser)(nil) +var _ io.ReadCloser = (*ReadCloser)(nil) // Close implements io.ReadCloser -func (rac *readAndCloser) Close() error { +func (rac *ReadCloser) Close() error { return rac.CloseFunc() } -// writeAndCloser implements io.WriteCloser by reading from a particular io.Writer +// WriteCloser implements io.WriteCloser by reading from a particular io.Writer // and then calling the provided "Close()" method. -type writeAndCloser struct { +type WriteCloser struct { io.Writer CloseFunc func() error } -var _ io.WriteCloser = (*writeAndCloser)(nil) +var _ io.WriteCloser = (*WriteCloser)(nil) // Close implements io.WriteCloser -func (wac *writeAndCloser) Close() error { +func (wac *WriteCloser) Close() error { return wac.CloseFunc() } diff --git a/pkg/v1/v1util/and_closer_test.go b/pkg/v1/internal/and/and_closer_test.go similarity index 94% rename from pkg/v1/v1util/and_closer_test.go rename to pkg/v1/internal/and/and_closer_test.go index 7e42f95bb..fb1fa5a32 100644 --- a/pkg/v1/v1util/and_closer_test.go +++ b/pkg/v1/internal/and/and_closer_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC All Rights Reserved. +// Copyright 2020 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. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v1util +package and import ( "bytes" @@ -25,7 +25,7 @@ func TestRead(t *testing.T) { r := bytes.NewBufferString(want) called := false - rac := &readAndCloser{ + rac := &ReadCloser{ Reader: r, CloseFunc: func() error { called = true @@ -56,7 +56,7 @@ func TestWrite(t *testing.T) { w := bytes.NewBuffer([]byte{}) called := false - wac := &writeAndCloser{ + wac := &WriteCloser{ Writer: w, CloseFunc: func() error { called = true diff --git a/pkg/v1/internal/gzip/zip.go b/pkg/v1/internal/gzip/zip.go new file mode 100644 index 000000000..71b43e35d --- /dev/null +++ b/pkg/v1/internal/gzip/zip.go @@ -0,0 +1,96 @@ +// Copyright 2020 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 gzip + +import ( + "bytes" + "compress/gzip" + "io" + + "github.com/google/go-containerregistry/pkg/v1/internal/and" +) + +var gzipMagicHeader = []byte{'\x1f', '\x8b'} + +// ReadCloser reads uncompressed input data from the io.ReadCloser and +// returns an io.ReadCloser from which compressed data may be read. +// This uses gzip.BestSpeed for the compression level. +func ReadCloser(r io.ReadCloser) io.ReadCloser { + return ReadCloserLevel(r, gzip.BestSpeed) +} + +// ReadCloserLevel reads uncompressed input data from the io.ReadCloser and +// returns an io.ReadCloser from which compressed data may be read. +// Refer to compress/gzip for the level: +// https://golang.org/pkg/compress/gzip/#pkg-constants +func ReadCloserLevel(r io.ReadCloser, level int) io.ReadCloser { + pr, pw := io.Pipe() + + // Returns err so we can pw.CloseWithError(err) + go func() error { + // TODO(go1.14): Just defer {pw,gw,r}.Close like you'd expect. + // Context: https://golang.org/issue/24283 + gw, err := gzip.NewWriterLevel(pw, level) + if err != nil { + return pw.CloseWithError(err) + } + + if _, err := io.Copy(gw, r); err != nil { + defer r.Close() + defer gw.Close() + return pw.CloseWithError(err) + } + defer pw.Close() + defer r.Close() + defer gw.Close() + + return nil + }() + + return pr +} + +// UnzipReadCloser reads compressed input data from the io.ReadCloser and +// returns an io.ReadCloser from which uncompessed data may be read. +func UnzipReadCloser(r io.ReadCloser) (io.ReadCloser, error) { + gr, err := gzip.NewReader(r) + if err != nil { + return nil, err + } + return &and.ReadCloser{ + Reader: gr, + CloseFunc: func() error { + // If the unzip fails, then this seems to return the same + // error as the read. We don't want this to interfere with + // us closing the main ReadCloser, since this could leave + // an open file descriptor (fails on Windows). + gr.Close() + return r.Close() + }, + }, nil +} + +// Is detects whether the input stream is compressed. +func Is(r io.Reader) (bool, error) { + magicHeader := make([]byte, 2) + n, err := r.Read(magicHeader) + if n == 0 && err == io.EOF { + return false, nil + } + if err != nil { + return false, err + } + return bytes.Equal(magicHeader, gzipMagicHeader), nil +} diff --git a/pkg/v1/v1util/zip_test.go b/pkg/v1/internal/gzip/zip_test.go similarity index 63% rename from pkg/v1/v1util/zip_test.go rename to pkg/v1/internal/gzip/zip_test.go index 8467705a9..8360c3f74 100644 --- a/pkg/v1/v1util/zip_test.go +++ b/pkg/v1/internal/gzip/zip_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC All Rights Reserved. +// Copyright 2020 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. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v1util +package gzip import ( "bytes" @@ -25,25 +25,25 @@ import ( func TestReader(t *testing.T) { want := "This is the input string." buf := bytes.NewBufferString(want) - zipped := GzipReadCloser(ioutil.NopCloser(buf)) - unzipped, err := GunzipReadCloser(zipped) + zipped := ReadCloser(ioutil.NopCloser(buf)) + unzipped, err := UnzipReadCloser(zipped) if err != nil { - t.Errorf("GunzipReadCloser() = %v", err) + t.Error("UnzipReadCloser() =", err) } b, err := ioutil.ReadAll(unzipped) if err != nil { - t.Errorf("ReadAll() = %v", err) + t.Error("ReadAll() =", err) } if got := string(b); got != want { t.Errorf("ReadAll(); got %q, want %q", got, want) } if err := unzipped.Close(); err != nil { - t.Errorf("Close() = %v", err) + t.Error("Close() =", err) } } -func TestIsGzipped(t *testing.T) { +func TestIs(t *testing.T) { tests := []struct { in []byte out bool @@ -55,12 +55,12 @@ func TestIsGzipped(t *testing.T) { } for _, test := range tests { reader := bytes.NewReader(test.in) - got, err := IsGzipped(reader) + got, err := Is(reader) if got != test.out { - t.Errorf("IsGzipped; n: got %v, wanted %v\n", got, test.out) + t.Errorf("Is; n: got %v, wanted %v\n", got, test.out) } if err != test.err { - t.Errorf("IsGzipped; err: got %v, wanted %v\n", err, test.err) + t.Errorf("Is; err: got %v, wanted %v\n", err, test.err) } } } @@ -77,22 +77,22 @@ func (f failReader) Read(_ []byte) (int, error) { func TestReadErrors(t *testing.T) { fr := failReader{} - if _, err := IsGzipped(fr); err != errRead { - t.Errorf("IsGzipped: expected errRead, got %v", err) + if _, err := Is(fr); err != errRead { + t.Error("Is: expected errRead, got", err) } frc := ioutil.NopCloser(fr) - if _, err := GunzipReadCloser(frc); err != errRead { - t.Errorf("GunzipReadCloser: expected errRead, got %v", err) + if _, err := UnzipReadCloser(frc); err != errRead { + t.Error("UnzipReadCloser: expected errRead, got", err) } - zr := GzipReadCloser(ioutil.NopCloser(fr)) + zr := ReadCloser(ioutil.NopCloser(fr)) if _, err := zr.Read(nil); err != errRead { - t.Errorf("GzipReadCloser: expected errRead, got %v", err) + t.Error("ReadCloser: expected errRead, got", err) } - zr = GzipReadCloserLevel(ioutil.NopCloser(strings.NewReader("zip me")), -10) + zr = ReadCloserLevel(ioutil.NopCloser(strings.NewReader("zip me")), -10) if _, err := zr.Read(nil); err == nil { - t.Errorf("Expected invalid level error, got: %v", err) + t.Error("Expected invalid level error, got:", err) } } diff --git a/pkg/v1/internal/verify/verify.go b/pkg/v1/internal/verify/verify.go new file mode 100644 index 000000000..366b6eef4 --- /dev/null +++ b/pkg/v1/internal/verify/verify.go @@ -0,0 +1,62 @@ +// Copyright 2020 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 verify + +import ( + "encoding/hex" + "fmt" + "hash" + "io" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/internal/and" +) + +type verifyReader struct { + inner io.Reader + hasher hash.Hash + expected v1.Hash +} + +// Read implements io.Reader +func (vc *verifyReader) Read(b []byte) (int, error) { + n, err := vc.inner.Read(b) + if err == io.EOF { + got := hex.EncodeToString(vc.hasher.Sum(make([]byte, 0, vc.hasher.Size()))) + if want := vc.expected.Hex; got != want { + return n, fmt.Errorf("error verifying %s checksum; got %q, want %q", + vc.expected.Algorithm, got, want) + } + } + return n, err +} + +// ReadCloser wraps the given io.ReadCloser to verify that its contents match +// the provided v1.Hash before io.EOF is returned. +func ReadCloser(r io.ReadCloser, h v1.Hash) (io.ReadCloser, error) { + w, err := v1.Hasher(h.Algorithm) + if err != nil { + return nil, err + } + r2 := io.TeeReader(r, w) + return &and.ReadCloser{ + Reader: &verifyReader{ + inner: r2, + hasher: w, + expected: h, + }, + CloseFunc: r.Close, + }, nil +} diff --git a/pkg/v1/v1util/verify_test.go b/pkg/v1/internal/verify/verify_test.go similarity index 72% rename from pkg/v1/v1util/verify_test.go rename to pkg/v1/internal/verify/verify_test.go index 143a51add..155fc4cec 100644 --- a/pkg/v1/v1util/verify_test.go +++ b/pkg/v1/internal/verify/verify_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC All Rights Reserved. +// Copyright 2020 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. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package v1util +package verify import ( "bytes" @@ -26,7 +26,7 @@ import ( func mustHash(s string, t *testing.T) v1.Hash { h, _, err := v1.SHA256(strings.NewReader(s)) if err != nil { - t.Fatalf("SHA256(%s) = %v", s, err) + t.Fatalf("v1.SHA256(%s) = %v", s, err) } return h } @@ -35,9 +35,9 @@ func TestVerificationFailure(t *testing.T) { want := "This is the input string." buf := bytes.NewBufferString(want) - verified, err := VerifyReadCloser(ioutil.NopCloser(buf), mustHash("not the same", t)) + verified, err := ReadCloser(ioutil.NopCloser(buf), mustHash("not the same", t)) if err != nil { - t.Fatalf("VerifyReadCloser() = %v", err) + t.Fatal("ReadCloser() =", err) } if b, err := ioutil.ReadAll(verified); err == nil { t.Errorf("ReadAll() = %q; want verification error", string(b)) @@ -48,12 +48,12 @@ func TestVerification(t *testing.T) { want := "This is the input string." buf := bytes.NewBufferString(want) - verified, err := VerifyReadCloser(ioutil.NopCloser(buf), mustHash(want, t)) + verified, err := ReadCloser(ioutil.NopCloser(buf), mustHash(want, t)) if err != nil { - t.Fatalf("VerifyReadCloser() = %v", err) + t.Fatal("ReadCloser() =", err) } if _, err := ioutil.ReadAll(verified); err != nil { - t.Errorf("ReadAll() = %v", err) + t.Error("ReadAll() =", err) } } @@ -62,8 +62,8 @@ func TestBadHash(t *testing.T) { Algorithm: "fake256", Hex: "whatever", } - _, err := VerifyReadCloser(ioutil.NopCloser(strings.NewReader("hi")), h) + _, err := ReadCloser(ioutil.NopCloser(strings.NewReader("hi")), h) if err == nil { - t.Errorf("VerifyReadCloser() = %v, wanted err", err) + t.Errorf("ReadCloser() = %v, wanted err", err) } } diff --git a/pkg/v1/mutate/mutate.go b/pkg/v1/mutate/mutate.go index aa139b753..798f91467 100644 --- a/pkg/v1/mutate/mutate.go +++ b/pkg/v1/mutate/mutate.go @@ -26,9 +26,9 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/internal/gzip" "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/google/go-containerregistry/pkg/v1/v1util" ) const whiteoutPrefix = ".wh." @@ -335,7 +335,7 @@ func layerTime(layer v1.Layer, t time.Time) (v1.Layer, error) { b := w.Bytes() // gzip the contents, then create the layer opener := func() (io.ReadCloser, error) { - return v1util.GzipReadCloser(ioutil.NopCloser(bytes.NewReader(b))), nil + return gzip.ReadCloser(ioutil.NopCloser(bytes.NewReader(b))), nil } layer, err = tarball.LayerFromOpener(opener) if err != nil { diff --git a/pkg/v1/partial/compressed.go b/pkg/v1/partial/compressed.go index 1f74919c2..0a2295579 100644 --- a/pkg/v1/partial/compressed.go +++ b/pkg/v1/partial/compressed.go @@ -18,8 +18,8 @@ import ( "io" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/internal/gzip" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/google/go-containerregistry/pkg/v1/v1util" ) // CompressedLayer represents the bare minimum interface a natively @@ -49,7 +49,7 @@ func (cle *compressedLayerExtender) Uncompressed() (io.ReadCloser, error) { if err != nil { return nil, err } - return v1util.GunzipReadCloser(r) + return gzip.UnzipReadCloser(r) } // DiffID implements v1.Layer diff --git a/pkg/v1/partial/uncompressed.go b/pkg/v1/partial/uncompressed.go index 7f2062ca8..69cea61c0 100644 --- a/pkg/v1/partial/uncompressed.go +++ b/pkg/v1/partial/uncompressed.go @@ -20,8 +20,8 @@ import ( "sync" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/internal/gzip" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/google/go-containerregistry/pkg/v1/v1util" ) // UncompressedLayer represents the bare minimum interface a natively @@ -54,7 +54,7 @@ func (ule *uncompressedLayerExtender) Compressed() (io.ReadCloser, error) { if err != nil { return nil, err } - return v1util.GzipReadCloser(u), nil + return gzip.ReadCloser(u), nil } // Digest implements v1.Layer diff --git a/pkg/v1/remote/descriptor.go b/pkg/v1/remote/descriptor.go index b77cb9a89..9c248cfe2 100644 --- a/pkg/v1/remote/descriptor.go +++ b/pkg/v1/remote/descriptor.go @@ -28,10 +28,10 @@ import ( "github.com/google/go-containerregistry/pkg/logs" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/internal/verify" "github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/google/go-containerregistry/pkg/v1/v1util" ) // ErrSchema1 indicates that we received a schema1 manifest from the registry. @@ -368,7 +368,7 @@ func (f *fetcher) fetchBlob(ctx context.Context, h v1.Hash) (io.ReadCloser, erro return nil, err } - return v1util.VerifyReadCloser(resp.Body, h) + return verify.ReadCloser(resp.Body, h) } func (f *fetcher) headBlob(h v1.Hash) (*http.Response, error) { diff --git a/pkg/v1/remote/image.go b/pkg/v1/remote/image.go index 35fee7762..e04961e90 100644 --- a/pkg/v1/remote/image.go +++ b/pkg/v1/remote/image.go @@ -24,10 +24,10 @@ import ( "github.com/google/go-containerregistry/pkg/internal/redact" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/internal/verify" "github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/google/go-containerregistry/pkg/v1/v1util" ) var acceptableImageMediaTypes = []types.MediaType{ @@ -177,7 +177,7 @@ func (rl *remoteImageLayer) Compressed() (io.ReadCloser, error) { continue } - return v1util.VerifyReadCloser(resp.Body, rl.digest) + return verify.ReadCloser(resp.Body, rl.digest) } return nil, lastErr diff --git a/pkg/v1/tarball/image.go b/pkg/v1/tarball/image.go index 905da0fb7..08f7a8124 100644 --- a/pkg/v1/tarball/image.go +++ b/pkg/v1/tarball/image.go @@ -27,9 +27,9 @@ import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/internal/gzip" "github.com/google/go-containerregistry/pkg/v1/partial" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/google/go-containerregistry/pkg/v1/v1util" ) type image struct { @@ -148,7 +148,7 @@ func (i *image) areLayersCompressed() (bool, error) { return false, err } defer blob.Close() - return v1util.IsGzipped(blob) + return gzip.Is(blob) } func (i *image) loadTarDescriptorAndConfig() error { diff --git a/pkg/v1/tarball/layer.go b/pkg/v1/tarball/layer.go index 659e6ae98..a06be349a 100644 --- a/pkg/v1/tarball/layer.go +++ b/pkg/v1/tarball/layer.go @@ -23,8 +23,8 @@ import ( "sync" v1 "github.com/google/go-containerregistry/pkg/v1" + ggzip "github.com/google/go-containerregistry/pkg/v1/internal/gzip" "github.com/google/go-containerregistry/pkg/v1/types" - "github.com/google/go-containerregistry/pkg/v1/v1util" ) type layer struct { @@ -123,7 +123,7 @@ func LayerFromOpener(opener Opener, opts ...LayerOption) (v1.Layer, error) { } defer rc.Close() - compressed, err := v1util.IsGzipped(rc) + compressed, err := ggzip.Is(rc) if err != nil { return nil, err } @@ -139,7 +139,7 @@ func LayerFromOpener(opener Opener, opts ...LayerOption) (v1.Layer, error) { if err != nil { return nil, err } - return v1util.GunzipReadCloser(urc) + return ggzip.UnzipReadCloser(urc) } } else { layer.uncompressedopener = opener @@ -148,7 +148,7 @@ func LayerFromOpener(opener Opener, opts ...LayerOption) (v1.Layer, error) { if err != nil { return nil, err } - return v1util.GzipReadCloserLevel(crc, layer.compression), nil + return ggzip.ReadCloserLevel(crc, layer.compression), nil } } diff --git a/pkg/v1/tarball/layer_test.go b/pkg/v1/tarball/layer_test.go index 00dee3d5b..aaf3d679b 100644 --- a/pkg/v1/tarball/layer_test.go +++ b/pkg/v1/tarball/layer_test.go @@ -165,7 +165,7 @@ func TestLayerFromReader(t *testing.T) { // Compression settings matter in order for the digest, size, // compressed assertions to pass // -// Since our v1util.GzipReadCloser uses gzip.BestSpeed +// Since our gzip.GzipReadCloser uses gzip.BestSpeed // we need our fixture to use the same - bazel's pkg_tar doesn't // seem to let you control compression settings func setupFixtures(t *testing.T) { diff --git a/pkg/v1/v1util/verify.go b/pkg/v1/v1util/verify.go index c9699770c..fdfea7cd6 100644 --- a/pkg/v1/v1util/verify.go +++ b/pkg/v1/v1util/verify.go @@ -14,48 +14,10 @@ package v1util -import ( - "encoding/hex" - "fmt" - "hash" - "io" - - v1 "github.com/google/go-containerregistry/pkg/v1" -) - -type verifyReader struct { - inner io.Reader - hasher hash.Hash - expected v1.Hash -} - -// Read implements io.Reader -func (vc *verifyReader) Read(b []byte) (int, error) { - n, err := vc.inner.Read(b) - if err == io.EOF { - got := hex.EncodeToString(vc.hasher.Sum(make([]byte, 0, vc.hasher.Size()))) - if want := vc.expected.Hex; got != want { - return n, fmt.Errorf("error verifying %s checksum; got %q, want %q", - vc.expected.Algorithm, got, want) - } - } - return n, err -} +import "github.com/google/go-containerregistry/pkg/v1/internal/verify" // VerifyReadCloser wraps the given io.ReadCloser to verify that its contents match // the provided v1.Hash before io.EOF is returned. -func VerifyReadCloser(r io.ReadCloser, h v1.Hash) (io.ReadCloser, error) { - w, err := v1.Hasher(h.Algorithm) - if err != nil { - return nil, err - } - r2 := io.TeeReader(r, w) - return &readAndCloser{ - Reader: &verifyReader{ - inner: r2, - hasher: w, - expected: h, - }, - CloseFunc: r.Close, - }, nil -} +// TODO(#873): Remove this package. +// Deprecated: please use v1.VerifyReadCloser +var VerifyReadCloser = verify.ReadCloser diff --git a/pkg/v1/v1util/zip.go b/pkg/v1/v1util/zip.go index 7f06254b6..e0dd734e0 100644 --- a/pkg/v1/v1util/zip.go +++ b/pkg/v1/v1util/zip.go @@ -14,81 +14,30 @@ package v1util -import ( - "bytes" - "compress/gzip" - "io" -) - -var gzipMagicHeader = []byte{'\x1f', '\x8b'} +import "github.com/google/go-containerregistry/pkg/v1/internal/gzip" // GzipReadCloser reads uncompressed input data from the io.ReadCloser and // returns an io.ReadCloser from which compressed data may be read. // This uses gzip.BestSpeed for the compression level. -func GzipReadCloser(r io.ReadCloser) io.ReadCloser { - return GzipReadCloserLevel(r, gzip.BestSpeed) -} +// TODO(#873): Remove this package. +// Deprecated: please move off of this API +var GzipReadCloser = gzip.ReadCloser // GzipReadCloserLevel reads uncompressed input data from the io.ReadCloser and // returns an io.ReadCloser from which compressed data may be read. // Refer to compress/gzip for the level: // https://golang.org/pkg/compress/gzip/#pkg-constants -func GzipReadCloserLevel(r io.ReadCloser, level int) io.ReadCloser { - pr, pw := io.Pipe() - - // Returns err so we can pw.CloseWithError(err) - go func() error { - // TODO(go1.14): Just defer {pw,gw,r}.Close like you'd expect. - // Context: https://golang.org/issue/24283 - gw, err := gzip.NewWriterLevel(pw, level) - if err != nil { - return pw.CloseWithError(err) - } - - if _, err := io.Copy(gw, r); err != nil { - defer r.Close() - defer gw.Close() - return pw.CloseWithError(err) - } - defer pw.Close() - defer r.Close() - defer gw.Close() - - return nil - }() - - return pr -} +// TODO(#873): Remove this package. +// Deprecated: please move off of this API +var GzipReadCloserLevel = gzip.ReadCloserLevel // GunzipReadCloser reads compressed input data from the io.ReadCloser and // returns an io.ReadCloser from which uncompessed data may be read. -func GunzipReadCloser(r io.ReadCloser) (io.ReadCloser, error) { - gr, err := gzip.NewReader(r) - if err != nil { - return nil, err - } - return &readAndCloser{ - Reader: gr, - CloseFunc: func() error { - // If the unzip fails, then this seems to return the same - // error as the read. We don't want this to interfere with - // us closing the main ReadCloser, since this could leave - // an open file descriptor (fails on Windows). - gr.Close() - return r.Close() - }, - }, nil -} +// TODO(#873): Remove this package. +// Deprecated: please move off of this API +var GunzipReadCloser = gzip.UnzipReadCloser // IsGzipped detects whether the input stream is compressed. -func IsGzipped(r io.Reader) (bool, error) { - magicHeader := make([]byte, 2) - n, err := r.Read(magicHeader) - if n == 0 && err == io.EOF { - return false, nil - } - if err != nil { - return false, err - } - return bytes.Equal(magicHeader, gzipMagicHeader), nil -} +// TODO(#873): Remove this package. +// Deprecated: please move off of this API +var IsGzipped = gzip.Is