Skip to content

Commit

Permalink
Towards eliminating v1util (#872)
Browse files Browse the repository at this point in the history
* Move logic out of v1util into other packages, leave aliases for back-compat.

* Eliminate our own reliance on v1util

* Add deprecation notices to trigger golangci-lint

* Fix linting issues

* Use 'Deprecated:'

* Add TODO(issue) for removing deprecated things

* Move verification to internal package

* Eliminate stutter

* Fix victor linter
  • Loading branch information
mattmoor committed Dec 17, 2020
1 parent 8b5370a commit 481434c
Show file tree
Hide file tree
Showing 17 changed files with 241 additions and 172 deletions.
12 changes: 6 additions & 6 deletions pkg/v1/hash_test.go
Expand Up @@ -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)
Expand All @@ -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)
}
}
}
Expand All @@ -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)
Expand All @@ -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{
Expand Down
20 changes: 10 additions & 10 deletions pkg/v1/v1util/and_closer.go → 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.
Expand All @@ -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()
}
@@ -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.
Expand All @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package v1util
package and

import (
"bytes"
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
96 changes: 96 additions & 0 deletions 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
}
38 changes: 19 additions & 19 deletions pkg/v1/v1util/zip_test.go → 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.
Expand All @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package v1util
package gzip

import (
"bytes"
Expand All @@ -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
Expand All @@ -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)
}
}
}
Expand All @@ -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)
}
}
62 changes: 62 additions & 0 deletions 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
}

0 comments on commit 481434c

Please sign in to comment.