mirror of
https://github.com/GoogleContainerTools/kaniko
synced 2026-10-17 23:59:11 +02:00
Bump go-containerregistry to pick up ACR fix (#1898)
This commit is contained in:
+1
-1
@@ -68,7 +68,7 @@ import (
|
||||
|
||||
func main() {
|
||||
// ...
|
||||
ecrHelper := ecr.ECRHelper{ClientFactory: api.DefaultClientFactory()}
|
||||
ecrHelper := ecr.ECRHelper{ClientFactory: api.DefaultClientFactory{}}
|
||||
img, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.NewKeychainFromHelper(ecrHelper)))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
+7
-4
@@ -101,10 +101,8 @@ func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
|
||||
}
|
||||
} else {
|
||||
f, err := os.Open(filepath.Join(os.Getenv("XDG_RUNTIME_DIR"), "containers/auth.json"))
|
||||
if os.IsNotExist(err) {
|
||||
if err != nil {
|
||||
return Anonymous, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
cf, err = config.LoadFromReader(f)
|
||||
@@ -156,9 +154,14 @@ func NewKeychainFromHelper(h Helper) Keychain { return wrapper{h} }
|
||||
type wrapper struct{ h Helper }
|
||||
|
||||
func (w wrapper) Resolve(r Resource) (Authenticator, error) {
|
||||
u, p, err := w.h.Get(r.String())
|
||||
u, p, err := w.h.Get(r.RegistryStr())
|
||||
if err != nil {
|
||||
return Anonymous, nil
|
||||
}
|
||||
// If the secret being stored is an identity token, the Username should be set to <token>
|
||||
// ref: https://docs.docker.com/engine/reference/commandline/login/#credential-helper-protocol
|
||||
if u == "<token>" {
|
||||
return FromConfig(AuthConfig{Username: u, IdentityToken: p}), nil
|
||||
}
|
||||
return FromConfig(AuthConfig{Username: u, Password: p}), nil
|
||||
}
|
||||
|
||||
+16
-2
@@ -31,6 +31,7 @@ type image struct {
|
||||
ref name.Reference
|
||||
opener *imageOpener
|
||||
tarballImage v1.Image
|
||||
id *v1.Hash
|
||||
|
||||
once sync.Once
|
||||
err error
|
||||
@@ -95,10 +96,20 @@ func Image(ref name.Reference, options ...Option) (v1.Image, error) {
|
||||
ctx: o.ctx,
|
||||
}
|
||||
|
||||
return &image{
|
||||
img := &image{
|
||||
ref: ref,
|
||||
opener: i,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Eagerly fetch Image ID to ensure it actually exists.
|
||||
// https://github.com/google/go-containerregistry/issues/1186
|
||||
id, err := img.ConfigName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img.id = &id
|
||||
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (i *image) initialize() error {
|
||||
@@ -133,6 +144,9 @@ func (i *image) Size() (int64, error) {
|
||||
}
|
||||
|
||||
func (i *image) ConfigName() (v1.Hash, error) {
|
||||
if i.id != nil {
|
||||
return *i.id, nil
|
||||
}
|
||||
res, _, err := i.opener.client.ImageInspectWithRaw(i.opener.ctx, i.ref.String())
|
||||
if err != nil {
|
||||
return v1.Hash{}, err
|
||||
|
||||
+89
-12
@@ -17,15 +17,19 @@ package layout
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/logs"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/match"
|
||||
"github.com/google/go-containerregistry/pkg/v1/mutate"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
"github.com/google/go-containerregistry/pkg/v1/stream"
|
||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -221,34 +225,104 @@ func (l Path) WriteFile(name string, data []byte, perm os.FileMode) error {
|
||||
// WriteBlob copies a file to the blobs/ directory in the Path from the given ReadCloser at
|
||||
// blobs/{hash.Algorithm}/{hash.Hex}.
|
||||
func (l Path) WriteBlob(hash v1.Hash, r io.ReadCloser) error {
|
||||
return l.writeBlob(hash, -1, r, nil)
|
||||
}
|
||||
|
||||
func (l Path) writeBlob(hash v1.Hash, size int64, rc io.ReadCloser, renamer func() (v1.Hash, error)) error {
|
||||
if hash.Hex == "" && renamer == nil {
|
||||
panic("writeBlob called an invalid hash and no renamer")
|
||||
}
|
||||
|
||||
dir := l.path("blobs", hash.Algorithm)
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if blob already exists and is the correct size
|
||||
file := filepath.Join(dir, hash.Hex)
|
||||
if _, err := os.Stat(file); err == nil {
|
||||
// Blob already exists, that's fine.
|
||||
if s, err := os.Stat(file); err == nil && !s.IsDir() && (s.Size() == size || size == -1) {
|
||||
return nil
|
||||
}
|
||||
w, err := os.Create(file)
|
||||
|
||||
// If a renamer func was provided write to a temporary file
|
||||
open := func() (*os.File, error) { return os.Create(file) }
|
||||
if renamer != nil {
|
||||
open = func() (*os.File, error) { return ioutil.TempFile(dir, hash.Hex) }
|
||||
}
|
||||
w, err := open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if renamer != nil {
|
||||
// Delete temp file if an error is encountered before renaming
|
||||
defer func() {
|
||||
if err := os.Remove(w.Name()); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
logs.Warn.Printf("error removing temporary file after encountering an error while writing blob: %v", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
_, err = io.Copy(w, r)
|
||||
return err
|
||||
// Write to file and exit if not renaming
|
||||
if n, err := io.Copy(w, rc); err != nil || renamer == nil {
|
||||
return err
|
||||
} else if size != -1 && n != size {
|
||||
return fmt.Errorf("expected blob size %d, but only wrote %d", size, n)
|
||||
}
|
||||
|
||||
// Always close reader before renaming, since Close computes the digest in
|
||||
// the case of streaming layers. If Close is not called explicitly, it will
|
||||
// occur in a goroutine that is not guaranteed to succeed before renamer is
|
||||
// called. When renamer is the layer's Digest method, it can return
|
||||
// ErrNotComputed.
|
||||
if err := rc.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Always close file before renaming
|
||||
if err := w.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Rename file based on the final hash
|
||||
finalHash, err := renamer()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting final digest of layer: %w", err)
|
||||
}
|
||||
|
||||
renamePath := l.path("blobs", finalHash.Algorithm, finalHash.Hex)
|
||||
return os.Rename(w.Name(), renamePath)
|
||||
}
|
||||
|
||||
// TODO: A streaming version of WriteBlob so we don't have to know the hash
|
||||
// before we write it.
|
||||
|
||||
// TODO: For streaming layers we should write to a tmp file then Rename to the
|
||||
// final digest.
|
||||
// writeLayer writes the compressed layer to a blob. Unlike WriteBlob it will
|
||||
// write to a temporary file (suffixed with .tmp) within the layout until the
|
||||
// compressed reader is fully consumed and written to disk. Also unlike
|
||||
// WriteBlob, it will not skip writing and exit without error when a blob file
|
||||
// exists, but does not have the correct size. (The blob hash is not
|
||||
// considered, because it may be expensive to compute.)
|
||||
func (l Path) writeLayer(layer v1.Layer) error {
|
||||
d, err := layer.Digest()
|
||||
if err != nil {
|
||||
if errors.Is(err, stream.ErrNotComputed) {
|
||||
// Allow digest errors, since streams may not have calculated the hash
|
||||
// yet. Instead, use an empty value, which will be transformed into a
|
||||
// random file name with `ioutil.TempFile` and the final digest will be
|
||||
// calculated after writing to a temp file and before renaming to the
|
||||
// final path.
|
||||
d = v1.Hash{Algorithm: "sha256", Hex: ""}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s, err := layer.Size()
|
||||
if errors.Is(err, stream.ErrNotComputed) {
|
||||
// Allow size errors, since streams may not have calculated the size
|
||||
// yet. Instead, use zero as a sentinel value meaning that no size
|
||||
// comparison can be done and any sized blob file should be considered
|
||||
// valid and not overwritten.
|
||||
//
|
||||
// TODO: Provide an option to always overwrite blobs.
|
||||
s = -1
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -257,7 +331,10 @@ func (l Path) writeLayer(layer v1.Layer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.WriteBlob(d, r)
|
||||
if err := l.writeBlob(d, s, r, layer.Digest); err != nil {
|
||||
return fmt.Errorf("error writing layer: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveBlob removes a file from the blobs directory in the Path
|
||||
|
||||
+1
-8
@@ -18,7 +18,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/google/go-containerregistry/pkg/v1/partial"
|
||||
@@ -141,14 +140,8 @@ func (i *image) compute() error {
|
||||
manifest.Config.MediaType = *i.configMediaType
|
||||
}
|
||||
|
||||
// With OCI media types, this should not be set, see discussion:
|
||||
// https://github.com/opencontainers/image-spec/pull/795
|
||||
if i.mediaType != nil {
|
||||
if strings.Contains(string(*i.mediaType), types.OCIVendorPrefix) {
|
||||
manifest.MediaType = ""
|
||||
} else if strings.Contains(string(*i.mediaType), types.DockerVendorPrefix) {
|
||||
manifest.MediaType = *i.mediaType
|
||||
}
|
||||
manifest.MediaType = *i.mediaType
|
||||
}
|
||||
|
||||
if i.annotations != nil {
|
||||
|
||||
+1
-8
@@ -17,7 +17,6 @@ package mutate
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-containerregistry/pkg/logs"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
@@ -131,14 +130,8 @@ func (i *index) compute() error {
|
||||
|
||||
manifest.Manifests = manifests
|
||||
|
||||
// With OCI media types, this should not be set, see discussion:
|
||||
// https://github.com/opencontainers/image-spec/pull/795
|
||||
if i.mediaType != nil {
|
||||
if strings.Contains(string(*i.mediaType), types.OCIVendorPrefix) {
|
||||
manifest.MediaType = ""
|
||||
} else if strings.Contains(string(*i.mediaType), types.DockerVendorPrefix) {
|
||||
manifest.MediaType = *i.mediaType
|
||||
}
|
||||
manifest.MediaType = *i.mediaType
|
||||
}
|
||||
|
||||
if i.annotations != nil {
|
||||
|
||||
+52
-2
@@ -15,7 +15,9 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Platform represents the target os/arch for an image.
|
||||
@@ -28,11 +30,59 @@ type Platform struct {
|
||||
Features []string `json:"features,omitempty"`
|
||||
}
|
||||
|
||||
func (p Platform) String() string {
|
||||
if p.OS == "" {
|
||||
return ""
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString(p.OS)
|
||||
if p.Architecture != "" {
|
||||
b.WriteString("/")
|
||||
b.WriteString(p.Architecture)
|
||||
}
|
||||
if p.Variant != "" {
|
||||
b.WriteString("/")
|
||||
b.WriteString(p.Variant)
|
||||
}
|
||||
if p.OSVersion != "" {
|
||||
b.WriteString(":")
|
||||
b.WriteString(p.OSVersion)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ParsePlatform parses a string representing a Platform, if possible.
|
||||
func ParsePlatform(s string) (*Platform, error) {
|
||||
var p Platform
|
||||
parts := strings.Split(strings.TrimSpace(s), ":")
|
||||
if len(parts) == 2 {
|
||||
p.OSVersion = parts[1]
|
||||
}
|
||||
parts = strings.Split(parts[0], "/")
|
||||
if len(parts) > 0 {
|
||||
p.OS = parts[0]
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
p.Architecture = parts[1]
|
||||
}
|
||||
if len(parts) > 2 {
|
||||
p.Variant = parts[2]
|
||||
}
|
||||
if len(parts) > 3 {
|
||||
return nil, fmt.Errorf("too many slashes in platform spec: %s", s)
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
// Equals returns true if the given platform is semantically equivalent to this one.
|
||||
// The order of Features and OSFeatures is not important.
|
||||
func (p Platform) Equals(o Platform) bool {
|
||||
return p.OS == o.OS && p.Architecture == o.Architecture && p.Variant == o.Variant && p.OSVersion == o.OSVersion &&
|
||||
stringSliceEqualIgnoreOrder(p.OSFeatures, o.OSFeatures) && stringSliceEqualIgnoreOrder(p.Features, o.Features)
|
||||
return p.OS == o.OS &&
|
||||
p.Architecture == o.Architecture &&
|
||||
p.Variant == o.Variant &&
|
||||
p.OSVersion == o.OSVersion &&
|
||||
stringSliceEqualIgnoreOrder(p.OSFeatures, o.OSFeatures) &&
|
||||
stringSliceEqualIgnoreOrder(p.Features, o.Features)
|
||||
}
|
||||
|
||||
// stringSliceEqual compares 2 string slices and returns if their contents are identical.
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ type Backoff = retry.Backoff
|
||||
var defaultRetryPredicate retry.Predicate = func(err error) bool {
|
||||
// Various failure modes here, as we're often reading from and writing to
|
||||
// the network.
|
||||
if retry.IsTemporary(err) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, syscall.EPIPE) {
|
||||
if retry.IsTemporary(err) || errors.Is(err, io.ErrUnexpectedEOF) || errors.Is(err, io.EOF) || errors.Is(err, syscall.EPIPE) {
|
||||
logs.Warn.Printf("retrying %v", err)
|
||||
return true
|
||||
}
|
||||
|
||||
+36
-3
@@ -79,7 +79,7 @@ func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingRes
|
||||
schemes = append(schemes, "http")
|
||||
}
|
||||
|
||||
var errs []string
|
||||
var errs []error
|
||||
for _, scheme := range schemes {
|
||||
url := fmt.Sprintf("%s://%s/v2/", scheme, reg.Name())
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
@@ -88,7 +88,7 @@ func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingRes
|
||||
}
|
||||
resp, err := client.Do(req.WithContext(ctx))
|
||||
if err != nil {
|
||||
errs = append(errs, err.Error())
|
||||
errs = append(errs, err)
|
||||
// Potentially retry with http.
|
||||
continue
|
||||
}
|
||||
@@ -125,7 +125,7 @@ func ping(ctx context.Context, reg name.Registry, t http.RoundTripper) (*pingRes
|
||||
return nil, CheckError(resp, http.StatusOK, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
return nil, errors.New(strings.Join(errs, "; "))
|
||||
return nil, multierrs(errs)
|
||||
}
|
||||
|
||||
func pickFromMultipleChallenges(challenges []authchallenge.Challenge) authchallenge.Challenge {
|
||||
@@ -145,3 +145,36 @@ func pickFromMultipleChallenges(challenges []authchallenge.Challenge) authchalle
|
||||
|
||||
return challenges[0]
|
||||
}
|
||||
|
||||
type multierrs []error
|
||||
|
||||
func (m multierrs) Error() string {
|
||||
var b strings.Builder
|
||||
hasWritten := false
|
||||
for _, err := range m {
|
||||
if hasWritten {
|
||||
b.WriteString("; ")
|
||||
}
|
||||
hasWritten = true
|
||||
b.WriteString(err.Error())
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (m multierrs) As(target interface{}) bool {
|
||||
for _, err := range m {
|
||||
if errors.As(err, target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m multierrs) Is(target error) bool {
|
||||
for _, err := range m {
|
||||
if errors.Is(err, target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+89
-68
@@ -126,18 +126,36 @@ func (l *Layer) Compressed() (io.ReadCloser, error) {
|
||||
return newCompressedReader(l)
|
||||
}
|
||||
|
||||
// finalize sets the layer to consumed and computes all hash and size values.
|
||||
func (l *Layer) finalize(uncompressed, compressed hash.Hash, size int64) error {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
diffID, err := v1.NewHash("sha256:" + hex.EncodeToString(uncompressed.Sum(nil)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.diffID = &diffID
|
||||
|
||||
digest, err := v1.NewHash("sha256:" + hex.EncodeToString(compressed.Sum(nil)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.digest = &digest
|
||||
|
||||
l.size = size
|
||||
l.consumed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type compressedReader struct {
|
||||
closer io.Closer // original blob's Closer.
|
||||
|
||||
h, zh hash.Hash // collects digests of compressed and uncompressed stream.
|
||||
pr io.Reader
|
||||
bw *bufio.Writer
|
||||
count *countWriter
|
||||
|
||||
l *Layer // stream.Layer to update upon Close.
|
||||
pr io.Reader
|
||||
closer func() error
|
||||
}
|
||||
|
||||
func newCompressedReader(l *Layer) (*compressedReader, error) {
|
||||
// Collect digests of compressed and uncompressed stream and size of
|
||||
// compressed stream.
|
||||
h := sha256.New()
|
||||
zh := sha256.New()
|
||||
count := &countWriter{}
|
||||
@@ -158,24 +176,74 @@ func newCompressedReader(l *Layer) (*compressedReader, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
doneDigesting := make(chan struct{})
|
||||
|
||||
cr := &compressedReader{
|
||||
closer: newMultiCloser(zw, l.blob),
|
||||
pr: pr,
|
||||
bw: bw,
|
||||
h: h,
|
||||
zh: zh,
|
||||
count: count,
|
||||
l: l,
|
||||
pr: pr,
|
||||
closer: func() error {
|
||||
// Immediately close pw without error. There are three ways to get
|
||||
// here.
|
||||
//
|
||||
// 1. There was a copy error due from the underlying reader, in which
|
||||
// case the error will not be overwritten.
|
||||
// 2. Copying from the underlying reader completed successfully.
|
||||
// 3. Close has been called before the underlying reader has been
|
||||
// fully consumed. In this case pw must be closed in order to
|
||||
// keep the flush of bw from blocking indefinitely.
|
||||
//
|
||||
// NOTE: pw.Close never returns an error. The signature is only to
|
||||
// implement io.Closer.
|
||||
_ = pw.Close()
|
||||
|
||||
// Close the inner ReadCloser.
|
||||
//
|
||||
// NOTE: net/http will call close on success, so if we've already
|
||||
// closed the inner rc, it's not an error.
|
||||
if err := l.blob.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Finalize layer with its digest and size values.
|
||||
<-doneDigesting
|
||||
return l.finalize(h, zh, count.n)
|
||||
},
|
||||
}
|
||||
go func() {
|
||||
if _, err := io.Copy(io.MultiWriter(h, zw), l.blob); err != nil {
|
||||
// Copy blob into the gzip writer, which also hashes and counts the
|
||||
// size of the compressed output, and hasher of the raw contents.
|
||||
_, copyErr := io.Copy(io.MultiWriter(h, zw), l.blob)
|
||||
|
||||
// Close the gzip writer once copying is done. If this is done in the
|
||||
// Close method of compressedReader instead, then it can cause a panic
|
||||
// when the compressedReader is closed before the blob is fully
|
||||
// consumed and io.Copy in this goroutine is still blocking.
|
||||
closeErr := zw.Close()
|
||||
|
||||
// Check errors from writing and closing streams.
|
||||
if copyErr != nil {
|
||||
close(doneDigesting)
|
||||
pw.CloseWithError(copyErr)
|
||||
return
|
||||
}
|
||||
if closeErr != nil {
|
||||
close(doneDigesting)
|
||||
pw.CloseWithError(closeErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Flush the buffer once all writes are complete to the gzip writer.
|
||||
if err := bw.Flush(); err != nil {
|
||||
close(doneDigesting)
|
||||
pw.CloseWithError(err)
|
||||
return
|
||||
}
|
||||
// Now close the compressed reader, to flush the gzip stream
|
||||
// and calculate digest/diffID/size. This will cause pr to
|
||||
// return EOF which will cause readers of the Compressed stream
|
||||
// to finish reading.
|
||||
|
||||
// Notify closer that digests are done being written.
|
||||
close(doneDigesting)
|
||||
|
||||
// Close the compressed reader to calculate digest/diffID/size. This
|
||||
// will cause pr to return EOF which will cause readers of the
|
||||
// Compressed stream to finish reading.
|
||||
pw.CloseWithError(cr.Close())
|
||||
}()
|
||||
|
||||
@@ -184,36 +252,7 @@ func newCompressedReader(l *Layer) (*compressedReader, error) {
|
||||
|
||||
func (cr *compressedReader) Read(b []byte) (int, error) { return cr.pr.Read(b) }
|
||||
|
||||
func (cr *compressedReader) Close() error {
|
||||
cr.l.mu.Lock()
|
||||
defer cr.l.mu.Unlock()
|
||||
|
||||
// Close the inner ReadCloser.
|
||||
if err := cr.closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Flush the buffer.
|
||||
if err := cr.bw.Flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
diffID, err := v1.NewHash("sha256:" + hex.EncodeToString(cr.h.Sum(nil)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cr.l.diffID = &diffID
|
||||
|
||||
digest, err := v1.NewHash("sha256:" + hex.EncodeToString(cr.zh.Sum(nil)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cr.l.digest = &digest
|
||||
|
||||
cr.l.size = cr.count.n
|
||||
cr.l.consumed = true
|
||||
return nil
|
||||
}
|
||||
func (cr *compressedReader) Close() error { return cr.closer() }
|
||||
|
||||
// countWriter counts bytes written to it.
|
||||
type countWriter struct{ n int64 }
|
||||
@@ -222,21 +261,3 @@ func (c *countWriter) Write(p []byte) (int, error) {
|
||||
c.n += int64(len(p))
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// multiCloser is a Closer that collects multiple Closers and Closes them in order.
|
||||
type multiCloser []io.Closer
|
||||
|
||||
var _ io.Closer = (multiCloser)(nil)
|
||||
|
||||
func newMultiCloser(c ...io.Closer) multiCloser { return multiCloser(c) }
|
||||
|
||||
func (m multiCloser) Close() error {
|
||||
for _, c := range m {
|
||||
// NOTE: net/http will call close on success, so if we've already
|
||||
// closed the inner rc, it's not an error.
|
||||
if err := c.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+12
-1
@@ -18,6 +18,7 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -117,8 +118,18 @@ func validateLayers(img v1.Image, opt ...Option) error {
|
||||
diffids := []v1.Hash{}
|
||||
udiffids := []v1.Hash{}
|
||||
sizes := []int64{}
|
||||
for _, layer := range layers {
|
||||
for i, layer := range layers {
|
||||
cl, err := computeLayer(layer)
|
||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
// Errored while reading tar content of layer because a header or
|
||||
// content section was not the correct length. This is most likely
|
||||
// due to an incomplete download or otherwise interrupted process.
|
||||
m, err := img.Manifest()
|
||||
if err != nil {
|
||||
return fmt.Errorf("undersized layer[%d] content", i)
|
||||
}
|
||||
return fmt.Errorf("undersized layer[%d] content: Manifest.Layers[%d].Size=%d", i, i, m.Layers[i].Size)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// Copyright 2018 Google LLC All Rights Reserved.
|
||||
|
||||
Reference in New Issue
Block a user