Hello community,
here is the log from the commit of package libcontainers-common for openSUSE:Factory checked in at 2019-04-04 12:02:23
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/libcontainers-common (Old)
and /work/SRC/openSUSE:Factory/.libcontainers-common.new.3908 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libcontainers-common"
Thu Apr 4 12:02:23 2019 rev:13 rq:690415 version:20190401
Changes:
--------
--- /work/SRC/openSUSE:Factory/libcontainers-common/libcontainers-common.changes 2019-03-01 20:26:34.622052549 +0100
+++ /work/SRC/openSUSE:Factory/.libcontainers-common.new.3908/libcontainers-common.changes 2019-04-04 12:02:28.189365108 +0200
@@ -1,0 +2,13 @@
+Mon Apr 1 14:24:17 UTC 2019 - Richard Brown
+
+- Update to libpod v1.2.0
+ * Rootless Podman can now be used with a single UID and GID, without requiring a full 65536 UIDs/GIDs to be allocated in /etc/subuid and /etc/subgid
+ * Move pkg/util default storage functions from libpod to containers/storage
+- Update to image v1.5
+ * Minor behind the scene bugfixes, no user facing changes
+- Update to storage v1.12.1
+ * Move pkg/util default storage functions from libpod to containers/storage
+ * containers/storage no longer depends on containers/image
+- Version 20190401
+
+-------------------------------------------------------------------
Old:
----
image-1.3.tar.xz
libpod-1.1.0.tar.xz
storage-1.10.tar.xz
New:
----
image-1.5.tar.xz
libpod-1.2.0.tar.xz
storage-1.12.1.tar.xz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ libcontainers-common.spec ++++++
--- /var/tmp/diff_new_pack.kuixG5/_old 2019-04-04 12:02:32.885368870 +0200
+++ /var/tmp/diff_new_pack.kuixG5/_new 2019-04-04 12:02:32.889368873 +0200
@@ -16,16 +16,16 @@
#
# libpodver - version from containers/libpod
-%define libpodver 1.1.0
+%define libpodver 1.2.0
# storagever - version from containers/storage
-%define storagever 1.10
+%define storagever 1.12.1
# imagever - version from containers/image
-%define imagever 1.3
+%define imagever 1.5
Name: libcontainers-common
-Version: 20190219
+Version: 20190401
Release: 0
Summary: Configuration files common to github.com/containers
License: Apache-2.0
++++++ _service ++++++
--- /var/tmp/diff_new_pack.kuixG5/_old 2019-04-04 12:02:32.929368905 +0200
+++ /var/tmp/diff_new_pack.kuixG5/_new 2019-04-04 12:02:32.933368908 +0200
@@ -4,24 +4,24 @@
<param name="url">https://github.com/containers/storage.git</param>
<param name="scm">git</param>
<param name="filename">storage</param>
-<param name="versionformat">1.10</param>
-<param name="revision">v1.10</param>
+<param name="versionformat">1.12.1</param>
+<param name="revision">v1.12.1</param>
</service>
<service name="tar_scm" mode="disabled">
<param name="url">https://github.com/containers/image.git</param>
<param name="scm">git</param>
<param name="filename">image</param>
-<param name="versionformat">1.3</param>
-<param name="revision">v1.3</param>
+<param name="versionformat">1.5</param>
+<param name="revision">v1.5</param>
</service>
<service name="tar_scm" mode="disabled">
<param name="url">https://github.com/containers/libpod.git</param>
<param name="scm">git</param>
<param name="filename">libpod</param>
-<param name="versionformat">1.1.0</param>
-<param name="revision">v1.1.0</param>
+<param name="versionformat">1.2.0</param>
+<param name="revision">v1.2.0</param>
</service>
<service name="recompress" mode="disabled">
++++++ image-1.3.tar.xz -> image-1.5.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/.travis.yml new/image-1.5/.travis.yml
--- old/image-1.3/.travis.yml 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/.travis.yml 2019-02-26 15:39:32.000000000 +0100
@@ -21,7 +21,7 @@
-e TRAVIS=$TRAVIS -e TRAVIS_COMMIT_RANGE=$TRAVIS_COMMIT_RANGE
-e TRAVIS_PULL_REQUEST=$TRAVIS_PULL_REQUEST -e TRAVIS_REPO_SLUG=$TRAVIS_REPO_SLUG
-e TRAVIS_BRANCH=$TRAVIS_BRANCH -e TRAVIS_COMMIT=$TRAVIS_COMMIT
- -e GOPATH=/gopath -e TRASH_CACHE=/gopath/.trashcache -e HOME=/gopath
+ -e GOPATH=/gopath -e TRASH_CACHE=/gopath/.trashcache
-v /etc/passwd:/etc/passwd -v /etc/sudoers:/etc/sudoers -v /etc/sudoers.d:/etc/sudoers.d
-v /var/run:/var/run:z -v $HOME/gopath:/gopath:Z
-w /gopath/src/github.com/containers/image image-test bash -c "PATH=$PATH:/gopath/bin make cross tools .gitvalidation validate test test-skopeo SUDO=sudo BUILDTAGS=\"$BUILDTAGS\""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/MAINTAINERS new/image-1.5/MAINTAINERS
--- old/image-1.3/MAINTAINERS 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/MAINTAINERS 2019-02-26 15:39:32.000000000 +0100
@@ -3,3 +3,4 @@
Miloslav Trmac (@mtrmac)
Dan Walsh (@dwalsh)
Nalin Dahyabhai (@nalind)
+Valentin Rothberg (@vrothberg)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/copy/copy.go new/image-1.5/copy/copy.go
--- old/image-1.3/copy/copy.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/copy/copy.go 2019-02-26 15:39:32.000000000 +0100
@@ -6,24 +6,29 @@
"fmt"
"io"
"io/ioutil"
+ "os"
"reflect"
"runtime"
"strings"
"sync"
"time"
+ "github.com/containers/image/docker/reference"
"github.com/containers/image/image"
+ "github.com/containers/image/manifest"
"github.com/containers/image/pkg/blobinfocache"
"github.com/containers/image/pkg/compression"
"github.com/containers/image/signature"
"github.com/containers/image/transports"
"github.com/containers/image/types"
"github.com/klauspost/pgzip"
- "github.com/opencontainers/go-digest"
+ digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
+ "github.com/vbauerster/mpb"
+ "github.com/vbauerster/mpb/decor"
+ "golang.org/x/crypto/ssh/terminal"
"golang.org/x/sync/semaphore"
- pb "gopkg.in/cheggaaa/pb.v1"
)
type digestingReader struct {
@@ -84,6 +89,7 @@
dest types.ImageDestination
rawSource types.ImageSource
reportWriter io.Writer
+ progressOutput io.Writer
progressInterval time.Duration
progress chan types.ProgressProperties
blobInfoCache types.BlobInfoCache
@@ -152,11 +158,19 @@
}
}()
+ // If reportWriter is not a TTY (e.g., when piping to a file), do not
+ // print the progress bars to avoid long and hard to parse output.
+ // createProgressBar() will print a single line instead.
+ progressOutput := reportWriter
+ if !isTTY(reportWriter) {
+ progressOutput = ioutil.Discard
+ }
copyInParallel := dest.HasThreadSafePutBlob() && rawSource.HasThreadSafeGetBlob()
c := &copier{
dest: dest,
rawSource: rawSource,
reportWriter: reportWriter,
+ progressOutput: progressOutput,
progressInterval: options.ProgressInterval,
progress: options.Progress,
copyInParallel: copyInParallel,
@@ -201,7 +215,7 @@
// Image copies a single (on-manifest-list) image unparsedImage, using policyContext to validate
// source image admissibility.
-func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedImage *image.UnparsedImage) (manifest []byte, retErr error) {
+func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedImage *image.UnparsedImage) (manifestBytes []byte, retErr error) {
// The caller is handling manifest lists; this could happen only if a manifest list contains a manifest list.
// Make sure we fail cleanly in such cases.
multiImage, err := isMultiImage(ctx, unparsedImage)
@@ -224,6 +238,26 @@
return nil, errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference()))
}
+ // If the destination is a digested reference, make a note of that, determine what digest value we're
+ // expecting, and check that the source manifest matches it.
+ destIsDigestedReference := false
+ if named := c.dest.Reference().DockerReference(); named != nil {
+ if digested, ok := named.(reference.Digested); ok {
+ destIsDigestedReference = true
+ sourceManifest, _, err := src.Manifest(ctx)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Error reading manifest from source image")
+ }
+ matches, err := manifest.MatchesDigest(sourceManifest, digested.Digest())
+ if err != nil {
+ return nil, errors.Wrapf(err, "Error computing digest of source image's manifest")
+ }
+ if !matches {
+ return nil, errors.New("Digest of source image's manifest would not match destination reference")
+ }
+ }
+ }
+
if err := checkImageDestinationForCurrentRuntimeOS(ctx, options.DestinationCtx, src, c.dest); err != nil {
return nil, err
}
@@ -251,15 +285,15 @@
manifestUpdates: &types.ManifestUpdateOptions{InformationOnly: types.ManifestUpdateInformation{Destination: c.dest}},
src: src,
// diffIDsAreNeeded is computed later
- canModifyManifest: len(sigs) == 0,
- // Ensure _this_ copy sees exactly the intended data when either processing a signed image or signing it.
- // This may be too conservative, but for now, better safe than sorry, _especially_ on the SignBy path:
- // The signature makes the content non-repudiable, so it very much matters that the signature is made over exactly what the user intended.
- // We do intend the RecordDigestUncompressedPair calls to only work with reliable data, but at least there’s a risk
- // that the compressed version coming from a third party may be designed to attack some other decompressor implementation,
- // and we would reuse and sign it.
- canSubstituteBlobs: len(sigs) == 0 && options.SignBy == "",
+ canModifyManifest: len(sigs) == 0 && !destIsDigestedReference,
}
+ // Ensure _this_ copy sees exactly the intended data when either processing a signed image or signing it.
+ // This may be too conservative, but for now, better safe than sorry, _especially_ on the SignBy path:
+ // The signature makes the content non-repudiable, so it very much matters that the signature is made over exactly what the user intended.
+ // We do intend the RecordDigestUncompressedPair calls to only work with reliable data, but at least there’s a risk
+ // that the compressed version coming from a third party may be designed to attack some other decompressor implementation,
+ // and we would reuse and sign it.
+ ic.canSubstituteBlobs = ic.canModifyManifest && options.SignBy == ""
if err := ic.updateEmbeddedDockerReference(); err != nil {
return nil, err
@@ -283,7 +317,7 @@
// and at least with the OpenShift registry "acceptschema2" option, there is no way to detect the support
// without actually trying to upload something and getting a types.ManifestTypeRejectedError.
// So, try the preferred manifest MIME type. If the process succeeds, fine…
- manifest, err = ic.copyUpdatedConfigAndManifest(ctx)
+ manifestBytes, err = ic.copyUpdatedConfigAndManifest(ctx)
if err != nil {
logrus.Debugf("Writing manifest using preferred type %s failed: %v", preferredManifestMIMEType, err)
// … if it fails, _and_ the failure is because the manifest is rejected, we may have other options.
@@ -314,7 +348,7 @@
}
// We have successfully uploaded a manifest.
- manifest = attemptedManifest
+ manifestBytes = attemptedManifest
errs = nil // Mark this as a success so that we don't abort below.
break
}
@@ -324,7 +358,7 @@
}
if options.SignBy != "" {
- newSig, err := c.createSignature(manifest, options.SignBy)
+ newSig, err := c.createSignature(manifestBytes, options.SignBy)
if err != nil {
return nil, err
}
@@ -336,7 +370,7 @@
return nil, errors.Wrap(err, "Error writing signatures")
}
- return manifest, nil
+ return manifestBytes, nil
}
// Printf writes a formatted string to c.reportWriter.
@@ -389,20 +423,12 @@
return nil
}
-// shortDigest returns the first 12 characters of the digest.
-func shortDigest(d digest.Digest) string {
- return d.Encoded()[:12]
-}
-
-// createProgressBar creates a pb.ProgressBar.
-func createProgressBar(srcInfo types.BlobInfo, kind string, writer io.Writer) *pb.ProgressBar {
- bar := pb.New(int(srcInfo.Size)).SetUnits(pb.U_BYTES)
- bar.SetMaxWidth(80)
- bar.ShowTimeLeft = false
- bar.ShowPercent = false
- bar.Prefix(fmt.Sprintf("Copying %s %s:", kind, shortDigest(srcInfo.Digest)))
- bar.Output = writer
- return bar
+// isTTY returns true if the io.Writer is a file and a tty.
+func isTTY(w io.Writer) bool {
+ if f, ok := w.(*os.File); ok {
+ return terminal.IsTerminal(int(f.Fd()))
+ }
+ return false
}
// copyLayers copies layers from ic.src/ic.c.rawSource to dest, using and updating ic.manifestUpdates if necessary and ic.canModifyManifest.
@@ -431,6 +457,7 @@
// copyGroup is used to determine if all layers are copied
copyGroup := sync.WaitGroup{}
copyGroup.Add(numLayers)
+
// copySemaphore is used to limit the number of parallel downloads to
// avoid malicious images causing troubles and to be nice to servers.
var copySemaphore *semaphore.Weighted
@@ -441,8 +468,7 @@
}
data := make([]copyLayerData, numLayers)
- copyLayerHelper := func(index int, srcLayer types.BlobInfo, bar *pb.ProgressBar) {
- defer bar.Finish()
+ copyLayerHelper := func(index int, srcLayer types.BlobInfo, bar *mpb.Bar) {
defer copySemaphore.Release(1)
defer copyGroup.Done()
cld := copyLayerData{}
@@ -452,44 +478,37 @@
// does not support them.
if ic.diffIDsAreNeeded {
cld.err = errors.New("getting DiffID for foreign layers is unimplemented")
- bar.Prefix(fmt.Sprintf("Skipping blob %s (DiffID foreign layer unimplemented):", shortDigest(srcLayer.Digest)))
- bar.Finish()
} else {
cld.destInfo = srcLayer
- logrus.Debugf("Skipping foreign layer %q copy to %s\n", cld.destInfo.Digest, ic.c.dest.Reference().Transport().Name())
- bar.Prefix(fmt.Sprintf("Skipping blob %s (foreign layer):", shortDigest(srcLayer.Digest)))
- bar.Add64(bar.Total)
- bar.Finish()
+ logrus.Debugf("Skipping foreign layer %q copy to %s", cld.destInfo.Digest, ic.c.dest.Reference().Transport().Name())
}
} else {
cld.destInfo, cld.diffID, cld.err = ic.copyLayer(ctx, srcLayer, bar)
}
data[index] = cld
+ bar.SetTotal(srcLayer.Size, true)
}
- progressBars := make([]*pb.ProgressBar, numLayers)
- for i, srcInfo := range srcInfos {
- bar := createProgressBar(srcInfo, "blob", nil)
- progressBars[i] = bar
- }
+ func() { // A scope for defer
+ progressPool, progressCleanup := ic.c.newProgressPool(ctx)
+ defer progressCleanup()
- progressPool := pb.NewPool(progressBars...)
- progressPool.Output = ic.c.reportWriter
- if err := progressPool.Start(); err != nil {
- return errors.Wrapf(err, "error creating progress-bar pool")
- }
+ progressBars := make([]*mpb.Bar, numLayers)
+ for i, srcInfo := range srcInfos {
+ progressBars[i] = ic.c.createProgressBar(progressPool, srcInfo, "blob")
+ }
- for i, srcLayer := range srcInfos {
- copySemaphore.Acquire(ctx, 1)
- go copyLayerHelper(i, srcLayer, progressBars[i])
- }
+ for i, srcLayer := range srcInfos {
+ copySemaphore.Acquire(ctx, 1)
+ go copyLayerHelper(i, srcLayer, progressBars[i])
+ }
+
+ // Wait for all layers to be copied
+ copyGroup.Wait()
+ }()
destInfos := make([]types.BlobInfo, numLayers)
diffIDs := make([]digest.Digest, numLayers)
-
- copyGroup.Wait()
- progressPool.Stop()
-
for i, cld := range data {
if cld.err != nil {
return cld.err
@@ -560,6 +579,44 @@
return manifest, nil
}
+// newProgressPool creates a *mpb.Progress and a cleanup function.
+// The caller must eventually call the returned cleanup function after the pool will no longer be updated.
+func (c *copier) newProgressPool(ctx context.Context) (*mpb.Progress, func()) {
+ ctx, cancel := context.WithCancel(ctx)
+ pool := mpb.New(mpb.WithWidth(40), mpb.WithOutput(c.progressOutput), mpb.WithContext(ctx))
+ return pool, func() {
+ cancel()
+ pool.Wait()
+ }
+}
+
+// createProgressBar creates a mpb.Bar in pool. Note that if the copier's reportWriter
+// is ioutil.Discard, the progress bar's output will be discarded
+func (c *copier) createProgressBar(pool *mpb.Progress, info types.BlobInfo, kind string) *mpb.Bar {
+ // shortDigestLen is the length of the digest used for blobs.
+ const shortDigestLen = 12
+
+ prefix := fmt.Sprintf("Copying %s %s", kind, info.Digest.Encoded())
+ // Truncate the prefix (chopping of some part of the digest) to make all progress bars aligned in a column.
+ maxPrefixLen := len("Copying blob ") + shortDigestLen
+ if len(prefix) > maxPrefixLen {
+ prefix = prefix[:maxPrefixLen]
+ }
+
+ bar := pool.AddBar(info.Size,
+ mpb.PrependDecorators(
+ decor.Name(prefix),
+ ),
+ mpb.AppendDecorators(
+ decor.CountersKibiByte("%.1f / %.1f"),
+ ),
+ )
+ if c.progressOutput == ioutil.Discard {
+ c.Printf("Copying %s %s\n", kind, info.Digest)
+ }
+ return bar
+}
+
// copyConfig copies config.json, if any, from src to dest.
func (c *copier) copyConfig(ctx context.Context, src types.Image) error {
srcInfo := src.ConfigInfo()
@@ -568,12 +625,20 @@
if err != nil {
return errors.Wrapf(err, "Error reading config blob %s", srcInfo.Digest)
}
- bar := createProgressBar(srcInfo, "config", c.reportWriter)
- defer bar.Finish()
- bar.Start()
- destInfo, err := c.copyBlobFromStream(ctx, bytes.NewReader(configBlob), srcInfo, nil, false, true, bar)
+
+ destInfo, err := func() (types.BlobInfo, error) { // A scope for defer
+ progressPool, progressCleanup := c.newProgressPool(ctx)
+ defer progressCleanup()
+ bar := c.createProgressBar(progressPool, srcInfo, "config")
+ destInfo, err := c.copyBlobFromStream(ctx, bytes.NewReader(configBlob), srcInfo, nil, false, true, bar)
+ if err != nil {
+ return types.BlobInfo{}, err
+ }
+ bar.SetTotal(int64(len(configBlob)), true)
+ return destInfo, nil
+ }()
if err != nil {
- return err
+ return nil
}
if destInfo.Digest != srcInfo.Digest {
return errors.Errorf("Internal error: copying uncompressed config blob %s changed digest to %s", srcInfo.Digest, destInfo.Digest)
@@ -591,7 +656,7 @@
// copyLayer copies a layer with srcInfo (with known Digest and possibly known Size) in src to dest, perhaps compressing it if canCompress,
// and returns a complete blobInfo of the copied layer, and a value for LayerDiffIDs if diffIDIsNeeded
-func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, bar *pb.ProgressBar) (types.BlobInfo, digest.Digest, error) {
+func (ic *imageCopier) copyLayer(ctx context.Context, srcInfo types.BlobInfo, bar *mpb.Bar) (types.BlobInfo, digest.Digest, error) {
cachedDiffID := ic.c.blobInfoCache.UncompressedDigest(srcInfo.Digest) // May be ""
diffIDIsNeeded := ic.diffIDsAreNeeded && cachedDiffID == ""
@@ -602,9 +667,7 @@
return types.BlobInfo{}, "", errors.Wrapf(err, "Error trying to reuse blob %s at destination", srcInfo.Digest)
}
if reused {
- bar.Prefix(fmt.Sprintf("Skipping blob %s (already present):", shortDigest(srcInfo.Digest)))
- bar.Add64(bar.Total)
- bar.Finish()
+ logrus.Debugf("Skipping blob %s (already present):", srcInfo.Digest)
return blobInfo, cachedDiffID, nil
}
}
@@ -616,8 +679,7 @@
}
defer srcStream.Close()
- blobInfo, diffIDChan, err := ic.copyLayerFromStream(ctx, srcStream, types.BlobInfo{Digest: srcInfo.Digest, Size: srcBlobSize},
- diffIDIsNeeded, bar)
+ blobInfo, diffIDChan, err := ic.copyLayerFromStream(ctx, srcStream, types.BlobInfo{Digest: srcInfo.Digest, Size: srcBlobSize}, diffIDIsNeeded, bar)
if err != nil {
return types.BlobInfo{}, "", err
}
@@ -645,7 +707,7 @@
// perhaps compressing the stream if canCompress,
// and returns a complete blobInfo of the copied blob and perhaps a <-chan diffIDResult if diffIDIsNeeded, to be read by the caller.
func (ic *imageCopier) copyLayerFromStream(ctx context.Context, srcStream io.Reader, srcInfo types.BlobInfo,
- diffIDIsNeeded bool, bar *pb.ProgressBar) (types.BlobInfo, <-chan diffIDResult, error) {
+ diffIDIsNeeded bool, bar *mpb.Bar) (types.BlobInfo, <-chan diffIDResult, error) {
var getDiffIDRecorder func(compression.DecompressorFunc) io.Writer // = nil
var diffIDChan chan diffIDResult
@@ -706,7 +768,7 @@
// and returns a complete blobInfo of the copied blob.
func (c *copier) copyBlobFromStream(ctx context.Context, srcStream io.Reader, srcInfo types.BlobInfo,
getOriginalLayerCopyWriter func(decompressor compression.DecompressorFunc) io.Writer,
- canModifyBlob bool, isConfig bool, bar *pb.ProgressBar) (types.BlobInfo, error) {
+ canModifyBlob bool, isConfig bool, bar *mpb.Bar) (types.BlobInfo, error) {
// The copying happens through a pipeline of connected io.Readers.
// === Input: srcStream
@@ -729,7 +791,7 @@
return types.BlobInfo{}, errors.Wrapf(err, "Error reading blob %s", srcInfo.Digest)
}
isCompressed := decompressor != nil
- destStream = bar.NewProxyReader(destStream)
+ destStream = bar.ProxyReader(destStream)
// === Send a copy of the original, uncompressed, stream, to a separate path if necessary.
var originalLayerReader io.Reader // DO NOT USE this other than to drain the input if no other consumer in the pipeline has done so.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/docker/docker_client.go new/image-1.5/docker/docker_client.go
--- old/image-1.3/docker/docker_client.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/docker/docker_client.go 2019-02-26 15:39:32.000000000 +0100
@@ -91,7 +91,6 @@
password string
signatureBase signatureStorageBase
scope authScope
- extraScope *authScope // If non-nil, a temporary extra token scope (necessary for mounting from another repo)
// The following members are detected registry properties:
// They are set after a successful detectProperties(), and never change afterwards.
scheme string // Empty value also used to indicate detectProperties() has not yet succeeded.
@@ -282,7 +281,7 @@
client.username = username
client.password = password
- resp, err := client.makeRequest(ctx, "GET", "/v2/", nil, nil, v2Auth)
+ resp, err := client.makeRequest(ctx, "GET", "/v2/", nil, nil, v2Auth, nil)
if err != nil {
return err
}
@@ -362,8 +361,8 @@
q.Set("n", strconv.Itoa(limit))
u.RawQuery = q.Encode()
- logrus.Debugf("trying to talk to v1 search endpoint\n")
- resp, err := client.makeRequest(ctx, "GET", u.String(), nil, nil, noAuth)
+ logrus.Debugf("trying to talk to v1 search endpoint")
+ resp, err := client.makeRequest(ctx, "GET", u.String(), nil, nil, noAuth, nil)
if err != nil {
logrus.Debugf("error getting search results from v1 endpoint %q: %v", registry, err)
} else {
@@ -379,8 +378,8 @@
}
}
- logrus.Debugf("trying to talk to v2 search endpoint\n")
- resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil, v2Auth)
+ logrus.Debugf("trying to talk to v2 search endpoint")
+ resp, err := client.makeRequest(ctx, "GET", "/v2/_catalog", nil, nil, v2Auth, nil)
if err != nil {
logrus.Debugf("error getting search results from v2 endpoint %q: %v", registry, err)
} else {
@@ -409,20 +408,20 @@
// makeRequest creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
// The host name and schema is taken from the client or autodetected, and the path is relative to it, i.e. the path usually starts with /v2/.
-func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader, auth sendAuth) (*http.Response, error) {
+func (c *dockerClient) makeRequest(ctx context.Context, method, path string, headers map[string][]string, stream io.Reader, auth sendAuth, extraScope *authScope) (*http.Response, error) {
if err := c.detectProperties(ctx); err != nil {
return nil, err
}
url := fmt.Sprintf("%s://%s%s", c.scheme, c.registry, path)
- return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth)
+ return c.makeRequestToResolvedURL(ctx, method, url, headers, stream, -1, auth, extraScope)
}
// makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client.
// streamLen, if not -1, specifies the length of the data expected on stream.
// makeRequest should generally be preferred.
// TODO(runcom): too many arguments here, use a struct
-func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth) (*http.Response, error) {
+func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth, extraScope *authScope) (*http.Response, error) {
req, err := http.NewRequest(method, url, stream)
if err != nil {
return nil, err
@@ -441,7 +440,7 @@
req.Header.Add("User-Agent", c.sys.DockerRegistryUserAgent)
}
if auth == v2Auth {
- if err := c.setupRequestAuth(req); err != nil {
+ if err := c.setupRequestAuth(req, extraScope); err != nil {
return nil, err
}
}
@@ -460,7 +459,7 @@
// 2) gcr.io is sending 401 without a WWW-Authenticate header in the real request
//
// debugging: https://github.com/containers/image/pull/211#issuecomment-273426236 and follows up
-func (c *dockerClient) setupRequestAuth(req *http.Request) error {
+func (c *dockerClient) setupRequestAuth(req *http.Request, extraScope *authScope) error {
if len(c.challenges) == 0 {
return nil
}
@@ -474,10 +473,10 @@
case "bearer":
cacheKey := ""
scopes := []authScope{c.scope}
- if c.extraScope != nil {
+ if extraScope != nil {
// Using ':' as a separator here is unambiguous because getBearerToken below uses the same separator when formatting a remote request (and because repository names can't contain colons).
- cacheKey = fmt.Sprintf("%s:%s", c.extraScope.remoteName, c.extraScope.actions)
- scopes = append(scopes, *c.extraScope)
+ cacheKey = fmt.Sprintf("%s:%s", extraScope.remoteName, extraScope.actions)
+ scopes = append(scopes, *extraScope)
}
var token bearerToken
t, inCache := c.tokenCache.Load(cacheKey)
@@ -564,7 +563,7 @@
ping := func(scheme string) error {
url := fmt.Sprintf(resolvedPingV2URL, scheme, c.registry)
- resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth)
+ resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth, nil)
if err != nil {
logrus.Debugf("Ping %s err %s (%#v)", url, err.Error(), err)
return err
@@ -591,7 +590,7 @@
// best effort to understand if we're talking to a V1 registry
pingV1 := func(scheme string) bool {
url := fmt.Sprintf(resolvedPingV1URL, scheme, c.registry)
- resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth)
+ resp, err := c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth, nil)
if err != nil {
logrus.Debugf("Ping %s err %s (%#v)", url, err.Error(), err)
return false
@@ -625,7 +624,7 @@
// using the original data structures.
func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerReference, manifestDigest digest.Digest) (*extensionSignatureList, error) {
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(ref.ref), manifestDigest)
- res, err := c.makeRequest(ctx, "GET", path, nil, nil, v2Auth)
+ res, err := c.makeRequest(ctx, "GET", path, nil, nil, v2Auth, nil)
if err != nil {
return nil, err
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/docker/docker_image.go new/image-1.5/docker/docker_image.go
--- old/image-1.3/docker/docker_image.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/docker/docker_image.go 2019-02-26 15:39:32.000000000 +0100
@@ -66,7 +66,7 @@
tags := make([]string, 0)
for {
- res, err := client.makeRequest(ctx, "GET", path, nil, nil, v2Auth)
+ res, err := client.makeRequest(ctx, "GET", path, nil, nil, v2Auth, nil)
if err != nil {
return nil, err
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/docker/docker_image_dest.go new/image-1.5/docker/docker_image_dest.go
--- old/image-1.3/docker/docker_image_dest.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/docker/docker_image_dest.go 2019-02-26 15:39:32.000000000 +0100
@@ -12,6 +12,7 @@
"net/url"
"os"
"path/filepath"
+ "strings"
"github.com/containers/image/docker/reference"
"github.com/containers/image/manifest"
@@ -113,7 +114,7 @@
// HasThreadSafePutBlob indicates whether PutBlob can be executed concurrently.
func (d *dockerImageDestination) HasThreadSafePutBlob() bool {
- return false
+ return true
}
// PutBlob writes contents of stream and returns data representing the result (with all data filled in).
@@ -140,7 +141,7 @@
// FIXME? Chunked upload, progress reporting, etc.
uploadPath := fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref))
logrus.Debugf("Uploading %s", uploadPath)
- res, err := d.c.makeRequest(ctx, "POST", uploadPath, nil, nil, v2Auth)
+ res, err := d.c.makeRequest(ctx, "POST", uploadPath, nil, nil, v2Auth, nil)
if err != nil {
return types.BlobInfo{}, err
}
@@ -157,7 +158,7 @@
digester := digest.Canonical.Digester()
sizeCounter := &sizeCounter{}
tee := io.TeeReader(stream, io.MultiWriter(digester.Hash(), sizeCounter))
- res, err = d.c.makeRequestToResolvedURL(ctx, "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, v2Auth)
+ res, err = d.c.makeRequestToResolvedURL(ctx, "PATCH", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, tee, inputInfo.Size, v2Auth, nil)
if err != nil {
logrus.Debugf("Error uploading layer chunked, response %#v", res)
return types.BlobInfo{}, err
@@ -176,7 +177,7 @@
// TODO: check inputInfo.Digest == computedDigest https://github.com/containers/image/pull/70#discussion_r77646717
locationQuery.Set("digest", computedDigest.String())
uploadLocation.RawQuery = locationQuery.Encode()
- res, err = d.c.makeRequestToResolvedURL(ctx, "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, v2Auth)
+ res, err = d.c.makeRequestToResolvedURL(ctx, "PUT", uploadLocation.String(), map[string][]string{"Content-Type": {"application/octet-stream"}}, nil, -1, v2Auth, nil)
if err != nil {
return types.BlobInfo{}, err
}
@@ -194,10 +195,10 @@
// blobExists returns true iff repo contains a blob with digest, and if so, also its size.
// If the destination does not contain the blob, or it is unknown, blobExists ordinarily returns (false, -1, nil);
// it returns a non-nil error only on an unexpected failure.
-func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.Named, digest digest.Digest) (bool, int64, error) {
+func (d *dockerImageDestination) blobExists(ctx context.Context, repo reference.Named, digest digest.Digest, extraScope *authScope) (bool, int64, error) {
checkPath := fmt.Sprintf(blobsPath, reference.Path(repo), digest.String())
logrus.Debugf("Checking %s", checkPath)
- res, err := d.c.makeRequest(ctx, "HEAD", checkPath, nil, nil, v2Auth)
+ res, err := d.c.makeRequest(ctx, "HEAD", checkPath, nil, nil, v2Auth, extraScope)
if err != nil {
return false, -1, err
}
@@ -218,7 +219,7 @@
}
// mountBlob tries to mount blob srcDigest from srcRepo to the current destination.
-func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo reference.Named, srcDigest digest.Digest) error {
+func (d *dockerImageDestination) mountBlob(ctx context.Context, srcRepo reference.Named, srcDigest digest.Digest, extraScope *authScope) error {
u := url.URL{
Path: fmt.Sprintf(blobUploadPath, reference.Path(d.ref.ref)),
RawQuery: url.Values{
@@ -228,7 +229,7 @@
}
mountPath := u.String()
logrus.Debugf("Trying to mount %s", mountPath)
- res, err := d.c.makeRequest(ctx, "POST", mountPath, nil, nil, v2Auth)
+ res, err := d.c.makeRequest(ctx, "POST", mountPath, nil, nil, v2Auth, extraScope)
if err != nil {
return err
}
@@ -246,7 +247,7 @@
return errors.Wrap(err, "Error determining upload URL after a mount attempt")
}
logrus.Debugf("... started an upload instead of mounting, trying to cancel at %s", uploadLocation.String())
- res2, err := d.c.makeRequestToResolvedURL(ctx, "DELETE", uploadLocation.String(), nil, nil, -1, v2Auth)
+ res2, err := d.c.makeRequestToResolvedURL(ctx, "DELETE", uploadLocation.String(), nil, nil, -1, v2Auth, extraScope)
if err != nil {
logrus.Debugf("Error trying to cancel an inadvertent upload: %s", err)
} else {
@@ -276,7 +277,7 @@
}
// First, check whether the blob happens to already exist at the destination.
- exists, size, err := d.blobExists(ctx, d.ref.ref, info.Digest)
+ exists, size, err := d.blobExists(ctx, d.ref.ref, info.Digest, nil)
if err != nil {
return false, types.BlobInfo{}, err
}
@@ -286,15 +287,6 @@
}
// Then try reusing blobs from other locations.
-
- // Checking candidateRepo, and mounting from it, requires an expanded token scope.
- // We still want to reuse the ping information and other aspects of the client, so rather than make a fresh copy, there is this a bit ugly extraScope hack.
- if d.c.extraScope != nil {
- return false, types.BlobInfo{}, errors.New("Internal error: dockerClient.extraScope was set before TryReusingBlob")
- }
- defer func() {
- d.c.extraScope = nil
- }()
for _, candidate := range cache.CandidateLocations(d.ref.Transport(), bicTransportScope(d.ref), info.Digest, canSubstitute) {
candidateRepo, err := parseBICLocationReference(candidate.Location)
if err != nil {
@@ -314,7 +306,10 @@
}
// Whatever happens here, don't abort the entire operation. It's likely we just don't have permissions, and if it is a critical network error, we will find out soon enough anyway.
- d.c.extraScope = &authScope{
+
+ // Checking candidateRepo, and mounting from it, requires an
+ // expanded token scope.
+ extraScope := &authScope{
remoteName: reference.Path(candidateRepo),
actions: "pull",
}
@@ -325,7 +320,7 @@
// Even worse, docker/distribution does not actually reasonably implement canceling uploads
// (it would require a "delete" action in the token, and Quay does not give that to anyone, so we can't ask);
// so, be a nice client and don't create unnecesary upload sessions on the server.
- exists, size, err := d.blobExists(ctx, candidateRepo, candidate.Digest)
+ exists, size, err := d.blobExists(ctx, candidateRepo, candidate.Digest, extraScope)
if err != nil {
logrus.Debugf("... Failed: %v", err)
continue
@@ -335,7 +330,7 @@
continue // logrus.Debug() already happened in blobExists
}
if candidateRepo.Name() != d.ref.ref.Name() {
- if err := d.mountBlob(ctx, candidateRepo, candidate.Digest); err != nil {
+ if err := d.mountBlob(ctx, candidateRepo, candidate.Digest, extraScope); err != nil {
logrus.Debugf("... Mount failed: %v", err)
continue
}
@@ -369,7 +364,7 @@
if mimeType != "" {
headers["Content-Type"] = []string{mimeType}
}
- res, err := d.c.makeRequest(ctx, "PUT", path, headers, bytes.NewReader(m), v2Auth)
+ res, err := d.c.makeRequest(ctx, "PUT", path, headers, bytes.NewReader(m), v2Auth, nil)
if err != nil {
return err
}
@@ -396,14 +391,29 @@
if !ok || len(errors) == 0 {
return false
}
- ec, ok := errors[0].(errcode.ErrorCoder)
+ err = errors[0]
+ ec, ok := err.(errcode.ErrorCoder)
if !ok {
return false
}
+
+ switch ec.ErrorCode() {
// ErrorCodeManifestInvalid is returned by OpenShift with acceptschema2=false.
+ case v2.ErrorCodeManifestInvalid:
+ return true
// ErrorCodeTagInvalid is returned by docker/distribution (at least as of commit ec87e9b6971d831f0eff752ddb54fb64693e51cd)
// when uploading to a tag (because it can’t find a matching tag inside the manifest)
- return ec.ErrorCode() == v2.ErrorCodeManifestInvalid || ec.ErrorCode() == v2.ErrorCodeTagInvalid
+ case v2.ErrorCodeTagInvalid:
+ return true
+ // ErrorCodeUnsupported with 'Invalid JSON syntax' is returned by AWS ECR when
+ // uploading an OCI manifest that is (correctly, according to the spec) missing
+ // a top-level media type. See libpod issue #1719
+ // FIXME: remove this case when ECR behavior is fixed
+ case errcode.ErrorCodeUnsupported:
+ return strings.Contains(err.Error(), "Invalid JSON syntax")
+ default:
+ return false
+ }
}
func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error {
@@ -574,7 +584,7 @@
}
path := fmt.Sprintf(extensionsSignaturePath, reference.Path(d.ref.ref), d.manifestDigest.String())
- res, err := d.c.makeRequest(ctx, "PUT", path, nil, bytes.NewReader(body), v2Auth)
+ res, err := d.c.makeRequest(ctx, "PUT", path, nil, bytes.NewReader(body), v2Auth, nil)
if err != nil {
return err
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/docker/docker_image_src.go new/image-1.5/docker/docker_image_src.go
--- old/image-1.3/docker/docker_image_src.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/docker/docker_image_src.go 2019-02-26 15:39:32.000000000 +0100
@@ -89,7 +89,7 @@
path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest)
headers := make(map[string][]string)
headers["Accept"] = manifest.DefaultRequestedManifestMIMETypes
- res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth)
+ res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth, nil)
if err != nil {
return nil, "", err
}
@@ -137,7 +137,7 @@
err error
)
for _, url := range urls {
- resp, err = s.c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth)
+ resp, err = s.c.makeRequestToResolvedURL(ctx, "GET", url, nil, nil, -1, noAuth, nil)
if err == nil {
if resp.StatusCode != http.StatusOK {
err = errors.Errorf("error fetching external blob from %q: %d (%s)", url, resp.StatusCode, http.StatusText(resp.StatusCode))
@@ -147,10 +147,10 @@
break
}
}
- if resp.Body != nil && err == nil {
- return resp.Body, getBlobSize(resp), nil
+ if err != nil {
+ return nil, 0, err
}
- return nil, 0, err
+ return resp.Body, getBlobSize(resp), nil
}
func getBlobSize(resp *http.Response) int64 {
@@ -176,7 +176,7 @@
path := fmt.Sprintf(blobsPath, reference.Path(s.ref.ref), info.Digest.String())
logrus.Debugf("Downloading %s", path)
- res, err := s.c.makeRequest(ctx, "GET", path, nil, nil, v2Auth)
+ res, err := s.c.makeRequest(ctx, "GET", path, nil, nil, v2Auth, nil)
if err != nil {
return nil, 0, err
}
@@ -340,7 +340,7 @@
return err
}
getPath := fmt.Sprintf(manifestPath, reference.Path(ref.ref), refTail)
- get, err := c.makeRequest(ctx, "GET", getPath, headers, nil, v2Auth)
+ get, err := c.makeRequest(ctx, "GET", getPath, headers, nil, v2Auth, nil)
if err != nil {
return err
}
@@ -362,7 +362,7 @@
// When retrieving the digest from a registry >= 2.3 use the following header:
// "Accept": "application/vnd.docker.distribution.manifest.v2+json"
- delete, err := c.makeRequest(ctx, "DELETE", deletePath, headers, nil, v2Auth)
+ delete, err := c.makeRequest(ctx, "DELETE", deletePath, headers, nil, v2Auth, nil)
if err != nil {
return err
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/docker/tarfile/src.go new/image-1.5/docker/tarfile/src.go
--- old/image-1.3/docker/tarfile/src.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/docker/tarfile/src.go 2019-02-26 15:39:32.000000000 +0100
@@ -9,6 +9,7 @@
"io/ioutil"
"os"
"path"
+ "sync"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/manifest"
@@ -21,8 +22,10 @@
// Source is a partial implementation of types.ImageSource for reading from tarPath.
type Source struct {
tarPath string
- removeTarPathOnClose bool // Remove temp file on close if true
+ removeTarPathOnClose bool // Remove temp file on close if true
+ cacheDataLock sync.Once // Atomic way to ensure that ensureCachedDataIsPresent is only invoked once
// The following data is only available after ensureCachedDataIsPresent() succeeds
+ cacheDataResult error // The return value of ensureCachedDataIsPresent, since it should be as safe to cache as the side effects
tarManifest *ManifestItem // nil if not available yet.
configBytes []byte
configDigest digest.Digest
@@ -199,43 +202,46 @@
// ensureCachedDataIsPresent loads data necessary for any of the public accessors.
func (s *Source) ensureCachedDataIsPresent() error {
- if s.tarManifest != nil {
- return nil
- }
-
- // Read and parse manifest.json
- tarManifest, err := s.loadTarManifest()
- if err != nil {
- return err
- }
+ s.cacheDataLock.Do(func() {
+ // Read and parse manifest.json
+ tarManifest, err := s.loadTarManifest()
+ if err != nil {
+ s.cacheDataResult = err
+ return
+ }
- // Check to make sure length is 1
- if len(tarManifest) != 1 {
- return errors.Errorf("Unexpected tar manifest.json: expected 1 item, got %d", len(tarManifest))
- }
+ // Check to make sure length is 1
+ if len(tarManifest) != 1 {
+ s.cacheDataResult = errors.Errorf("Unexpected tar manifest.json: expected 1 item, got %d", len(tarManifest))
+ return
+ }
- // Read and parse config.
- configBytes, err := s.readTarComponent(tarManifest[0].Config)
- if err != nil {
- return err
- }
- var parsedConfig manifest.Schema2Image // There's a lot of info there, but we only really care about layer DiffIDs.
- if err := json.Unmarshal(configBytes, &parsedConfig); err != nil {
- return errors.Wrapf(err, "Error decoding tar config %s", tarManifest[0].Config)
- }
+ // Read and parse config.
+ configBytes, err := s.readTarComponent(tarManifest[0].Config)
+ if err != nil {
+ s.cacheDataResult = err
+ return
+ }
+ var parsedConfig manifest.Schema2Image // There's a lot of info there, but we only really care about layer DiffIDs.
+ if err := json.Unmarshal(configBytes, &parsedConfig); err != nil {
+ s.cacheDataResult = errors.Wrapf(err, "Error decoding tar config %s", tarManifest[0].Config)
+ return
+ }
- knownLayers, err := s.prepareLayerData(&tarManifest[0], &parsedConfig)
- if err != nil {
- return err
- }
+ knownLayers, err := s.prepareLayerData(&tarManifest[0], &parsedConfig)
+ if err != nil {
+ s.cacheDataResult = err
+ return
+ }
- // Success; commit.
- s.tarManifest = &tarManifest[0]
- s.configBytes = configBytes
- s.configDigest = digest.FromBytes(configBytes)
- s.orderedDiffIDList = parsedConfig.RootFS.DiffIDs
- s.knownLayers = knownLayers
- return nil
+ // Success; commit.
+ s.tarManifest = &tarManifest[0]
+ s.configBytes = configBytes
+ s.configDigest = digest.FromBytes(configBytes)
+ s.orderedDiffIDList = parsedConfig.RootFS.DiffIDs
+ s.knownLayers = knownLayers
+ })
+ return s.cacheDataResult
}
// loadTarManifest loads and decodes the manifest.json.
@@ -399,7 +405,7 @@
// HasThreadSafeGetBlob indicates whether GetBlob can be executed concurrently.
func (s *Source) HasThreadSafeGetBlob() bool {
- return false
+ return true
}
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/ostree/ostree_src.go new/image-1.5/ostree/ostree_src.go
--- old/image-1.3/ostree/ostree_src.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/ostree/ostree_src.go 2019-02-26 15:39:32.000000000 +0100
@@ -17,7 +17,7 @@
"github.com/containers/image/types"
"github.com/containers/storage/pkg/ioutils"
"github.com/klauspost/pgzip"
- "github.com/opencontainers/go-digest"
+ digest "github.com/opencontainers/go-digest"
glib "github.com/ostreedev/ostree-go/pkg/glibobject"
"github.com/pkg/errors"
"github.com/vbatts/tar-split/tar/asm"
@@ -313,24 +313,19 @@
if err != nil {
return nil, 0, err
}
- defer mfz.Close()
metaUnpacker := storage.NewJSONUnpacker(mfz)
getter, err := newOSTreePathFileGetter(s.repo, branch)
if err != nil {
+ mfz.Close()
return nil, 0, err
}
ots := asm.NewOutputTarStream(getter, metaUnpacker)
- pipeReader, pipeWriter := io.Pipe()
- go func() {
- io.Copy(pipeWriter, ots)
- pipeWriter.Close()
- }()
-
- rc := ioutils.NewReadCloserWrapper(pipeReader, func() error {
+ rc := ioutils.NewReadCloserWrapper(ots, func() error {
getter.Close()
+ mfz.Close()
return ots.Close()
})
return rc, layerSize, nil
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/pkg/blobinfocache/memory.go new/image-1.5/pkg/blobinfocache/memory.go
--- old/image-1.3/pkg/blobinfocache/memory.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/pkg/blobinfocache/memory.go 2019-02-26 15:39:32.000000000 +0100
@@ -1,6 +1,7 @@
package blobinfocache
import (
+ "sync"
"time"
"github.com/containers/image/types"
@@ -17,6 +18,7 @@
// memoryCache implements an in-memory-only BlobInfoCache
type memoryCache struct {
+ mutex *sync.Mutex // synchronizes concurrent accesses
uncompressedDigests map[digest.Digest]digest.Digest
digestsByUncompressed map[digest.Digest]map[digest.Digest]struct{} // stores a set of digests for each uncompressed digest
knownLocations map[locationKey]map[types.BICLocationReference]time.Time // stores last known existence time for each location reference
@@ -28,6 +30,7 @@
// Manual users of types.{ImageSource,ImageDestination} might also use this instead of a persistent cache.
func NewMemoryCache() types.BlobInfoCache {
return &memoryCache{
+ mutex: new(sync.Mutex),
uncompressedDigests: map[digest.Digest]digest.Digest{},
digestsByUncompressed: map[digest.Digest]map[digest.Digest]struct{}{},
knownLocations: map[locationKey]map[types.BICLocationReference]time.Time{},
@@ -38,6 +41,15 @@
// May return anyDigest if it is known to be uncompressed.
// Returns "" if nothing is known about the digest (it may be compressed or uncompressed).
func (mem *memoryCache) UncompressedDigest(anyDigest digest.Digest) digest.Digest {
+ mem.mutex.Lock()
+ defer mem.mutex.Unlock()
+ return mem.uncompressedDigest(anyDigest)
+}
+
+// uncompressedDigest returns an uncompressed digest corresponding to anyDigest.
+// May return anyDigest if it is known to be uncompressed.
+// Returns "" if nothing is known about the digest (it may be compressed or uncompressed).
+func (mem *memoryCache) uncompressedDigest(anyDigest digest.Digest) digest.Digest {
if d, ok := mem.uncompressedDigests[anyDigest]; ok {
return d
}
@@ -56,6 +68,8 @@
// because a manifest/config pair exists); otherwise the cache could be poisoned and allow substituting unexpected blobs.
// (Eventually, the DiffIDs in image config could detect the substitution, but that may be too late, and not all image formats contain that data.)
func (mem *memoryCache) RecordDigestUncompressedPair(anyDigest digest.Digest, uncompressed digest.Digest) {
+ mem.mutex.Lock()
+ defer mem.mutex.Unlock()
if previous, ok := mem.uncompressedDigests[anyDigest]; ok && previous != uncompressed {
logrus.Warnf("Uncompressed digest for blob %s previously recorded as %s, now %s", anyDigest, previous, uncompressed)
}
@@ -72,6 +86,8 @@
// RecordKnownLocation records that a blob with the specified digest exists within the specified (transport, scope) scope,
// and can be reused given the opaque location data.
func (mem *memoryCache) RecordKnownLocation(transport types.ImageTransport, scope types.BICTransportScope, blobDigest digest.Digest, location types.BICLocationReference) {
+ mem.mutex.Lock()
+ defer mem.mutex.Unlock()
key := locationKey{transport: transport.Name(), scope: scope, blobDigest: blobDigest}
locationScope, ok := mem.knownLocations[key]
if !ok {
@@ -103,11 +119,13 @@
// data from previous RecordDigestUncompressedPair calls is used to also look up variants of the blob which have the same
// uncompressed digest.
func (mem *memoryCache) CandidateLocations(transport types.ImageTransport, scope types.BICTransportScope, primaryDigest digest.Digest, canSubstitute bool) []types.BICReplacementCandidate {
+ mem.mutex.Lock()
+ defer mem.mutex.Unlock()
res := []candidateWithTime{}
res = mem.appendReplacementCandidates(res, transport, scope, primaryDigest)
var uncompressedDigest digest.Digest // = ""
if canSubstitute {
- if uncompressedDigest = mem.UncompressedDigest(primaryDigest); uncompressedDigest != "" {
+ if uncompressedDigest = mem.uncompressedDigest(primaryDigest); uncompressedDigest != "" {
otherDigests := mem.digestsByUncompressed[uncompressedDigest] // nil if not present in the map
for d := range otherDigests {
if d != primaryDigest && d != uncompressedDigest {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/pkg/sysregistriesv2/system_registries_v2.go new/image-1.5/pkg/sysregistriesv2/system_registries_v2.go
--- old/image-1.3/pkg/sysregistriesv2/system_registries_v2.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/pkg/sysregistriesv2/system_registries_v2.go 2019-02-26 15:39:32.000000000 +0100
@@ -53,20 +53,23 @@
Prefix string `toml:"prefix"`
}
-// backwards compatability to sysregistries v1
-type v1TOMLregistries struct {
+// V1TOMLregistries is for backwards compatibility to sysregistries v1
+type V1TOMLregistries struct {
Registries []string `toml:"registries"`
}
+// V1TOMLConfig is for backwards compatibility to sysregistries v1
+type V1TOMLConfig struct {
+ Search V1TOMLregistries `toml:"search"`
+ Insecure V1TOMLregistries `toml:"insecure"`
+ Block V1TOMLregistries `toml:"block"`
+}
+
// tomlConfig is the data type used to unmarshal the toml config.
type tomlConfig struct {
Registries []Registry `toml:"registry"`
// backwards compatability to sysregistries v1
- V1Registries struct {
- Search v1TOMLregistries `toml:"search"`
- Insecure v1TOMLregistries `toml:"insecure"`
- Block v1TOMLregistries `toml:"block"`
- } `toml:"registries"`
+ V1TOMLConfig `toml:"registries"`
}
// InvalidRegistries represents an invalid registry configurations. An example
@@ -129,21 +132,21 @@
// Note: config.V1Registries.Search needs to be processed first to ensure registryOrder is populated in the right order
// if one of the search registries is also in one of the other lists.
- for _, search := range config.V1Registries.Search.Registries {
+ for _, search := range config.V1TOMLConfig.Search.Registries {
reg, err := getRegistry(search)
if err != nil {
return nil, err
}
reg.Search = true
}
- for _, blocked := range config.V1Registries.Block.Registries {
+ for _, blocked := range config.V1TOMLConfig.Block.Registries {
reg, err := getRegistry(blocked)
if err != nil {
return nil, err
}
reg.Blocked = true
}
- for _, insecure := range config.V1Registries.Insecure.Registries {
+ for _, insecure := range config.V1TOMLConfig.Insecure.Registries {
reg, err := getRegistry(insecure)
if err != nil {
return nil, err
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/storage/storage_image.go new/image-1.5/storage/storage_image.go
--- old/image-1.3/storage/storage_image.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/storage/storage_image.go 2019-02-26 15:39:32.000000000 +0100
@@ -14,6 +14,7 @@
"sync"
"sync/atomic"
+ "github.com/containers/image/docker/reference"
"github.com/containers/image/image"
"github.com/containers/image/internal/tmpdir"
"github.com/containers/image/manifest"
@@ -70,6 +71,13 @@
size int64
}
+// manifestBigDataKey returns a key suitable for recording a manifest with the specified digest using storage.Store.ImageBigData and related functions.
+// If a specific manifest digest is explicitly requested by the user, the key retruned function should be used preferably;
+// for compatibility, if a manifest is not available under this key, check also storage.ImageDigestBigDataKey
+func manifestBigDataKey(digest digest.Digest) string {
+ return storage.ImageDigestManifestBigDataNamePrefix + "-" + digest.String()
+}
+
// newImageSource sets up an image for reading.
func newImageSource(imageRef storageReference) (*storageImageSource, error) {
// First, locate the image.
@@ -177,12 +185,29 @@
return nil, "", ErrNoManifestLists
}
if len(s.cachedManifest) == 0 {
- // We stored the manifest as an item named after storage.ImageDigestBigDataKey.
- cachedBlob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, storage.ImageDigestBigDataKey)
- if err != nil {
- return nil, "", err
+ // The manifest is stored as a big data item.
+ // Prefer the manifest corresponding to the user-specified digest, if available.
+ if s.imageRef.named != nil {
+ if digested, ok := s.imageRef.named.(reference.Digested); ok {
+ key := manifestBigDataKey(digested.Digest())
+ blob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, key)
+ if err != nil && !os.IsNotExist(err) { // os.IsNotExist is true if the image exists but there is no data corresponding to key
+ return nil, "", err
+ }
+ if err == nil {
+ s.cachedManifest = blob
+ }
+ }
+ }
+ // If the user did not specify a digest, or this is an old image stored before manifestBigDataKey was introduced, use the default manifest.
+ // Note that the manifest may not match the expected digest, and that is likely to fail eventually, e.g. in c/image/image/UnparsedImage.Manifest().
+ if len(s.cachedManifest) == 0 {
+ cachedBlob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, storage.ImageDigestBigDataKey)
+ if err != nil {
+ return nil, "", err
+ }
+ s.cachedManifest = cachedBlob
}
- s.cachedManifest = cachedBlob
}
return s.cachedManifest, manifest.GuessMIMEType(s.cachedManifest), err
}
@@ -660,6 +685,7 @@
}
lastLayer = layer.ID
}
+
// If one of those blobs was a configuration blob, then we can try to dig out the date when the image
// was originally created, in case we're just copying it. If not, no harm done.
options := &storage.ImageOptions{}
@@ -667,9 +693,6 @@
logrus.Debugf("setting image creation date to %s", inspect.Created)
options.CreationDate = *inspect.Created
}
- if manifestDigest, err := manifest.Digest(s.manifest); err == nil {
- options.Digest = manifestDigest
- }
// Create the image record, pointing to the most-recently added layer.
intendedID := s.imageRef.id
if intendedID == "" {
@@ -735,8 +758,20 @@
}
logrus.Debugf("set names of image %q to %v", img.ID, names)
}
- // Save the manifest. Use storage.ImageDigestBigDataKey as the item's
- // name, so that its digest can be used to locate the image in the Store.
+ // Save the manifest. Allow looking it up by digest by using the key convention defined by the Store.
+ // Record the manifest twice: using a digest-specific key to allow references to that specific digest instance,
+ // and using storage.ImageDigestBigDataKey for future users that don’t specify any digest and for compatibility with older readers.
+ manifestDigest, err := manifest.Digest(s.manifest)
+ if err != nil {
+ return errors.Wrapf(err, "error computing manifest digest")
+ }
+ if err := s.imageRef.transport.store.SetImageBigData(img.ID, manifestBigDataKey(manifestDigest), s.manifest); err != nil {
+ if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
+ logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
+ }
+ logrus.Debugf("error saving manifest for image %q: %v", img.ID, err)
+ return err
+ }
if err := s.imageRef.transport.store.SetImageBigData(img.ID, storage.ImageDigestBigDataKey, s.manifest); err != nil {
if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil {
logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2)
@@ -788,9 +823,21 @@
}
// PutManifest writes the manifest to the destination.
-func (s *storageImageDestination) PutManifest(ctx context.Context, manifest []byte) error {
- s.manifest = make([]byte, len(manifest))
- copy(s.manifest, manifest)
+func (s *storageImageDestination) PutManifest(ctx context.Context, manifestBlob []byte) error {
+ if s.imageRef.named != nil {
+ if digested, ok := s.imageRef.named.(reference.Digested); ok {
+ matches, err := manifest.MatchesDigest(manifestBlob, digested.Digest())
+ if err != nil {
+ return err
+ }
+ if !matches {
+ return fmt.Errorf("Manifest does not match expected digest %s", digested.Digest())
+ }
+ }
+ }
+
+ s.manifest = make([]byte, len(manifestBlob))
+ copy(s.manifest, manifestBlob)
return nil
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/storage/storage_reference.go new/image-1.5/storage/storage_reference.go
--- old/image-1.3/storage/storage_reference.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/storage/storage_reference.go 2019-02-26 15:39:32.000000000 +0100
@@ -55,7 +55,7 @@
// one present with the same name or ID, and return the image.
func (s *storageReference) resolveImage() (*storage.Image, error) {
var loadedImage *storage.Image
- if s.id == "" {
+ if s.id == "" && s.named != nil {
// Look for an image that has the expanded reference name as an explicit Name value.
image, err := s.transport.store.Image(s.named.String())
if image != nil && err == nil {
@@ -69,7 +69,7 @@
// though possibly with a different tag or digest, as a Name value, so
// that the canonical reference can be implicitly resolved to the image.
images, err := s.transport.store.ImagesByDigest(digested.Digest())
- if images != nil && err == nil {
+ if err == nil && len(images) > 0 {
for _, image := range images {
if imageMatchesRepo(image, s.named) {
loadedImage = image
@@ -97,6 +97,24 @@
return nil, ErrNoSuchImage
}
}
+ // Default to having the image digest that we hand back match the most recently
+ // added manifest...
+ if digest, ok := loadedImage.BigDataDigests[storage.ImageDigestBigDataKey]; ok {
+ loadedImage.Digest = digest
+ }
+ // ... unless the named reference says otherwise, and it matches one of the digests
+ // in the image. For those cases, set the Digest field to that value, for the
+ // sake of older consumers that don't know there's a whole list in there now.
+ if s.named != nil {
+ if digested, ok := s.named.(reference.Digested); ok {
+ for _, digest := range loadedImage.Digests {
+ if digest == digested.Digest() {
+ loadedImage.Digest = digest
+ break
+ }
+ }
+ }
+ }
return loadedImage, nil
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/storage/storage_transport.go new/image-1.5/storage/storage_transport.go
--- old/image-1.3/storage/storage_transport.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/storage/storage_transport.go 2019-02-26 15:39:32.000000000 +0100
@@ -284,11 +284,6 @@
}
}
if sref, ok := ref.(*storageReference); ok {
- if sref.id != "" {
- if img, err := store.Image(sref.id); err == nil {
- return img, nil
- }
- }
tmpRef := *sref
if img, err := tmpRef.resolveImage(); err == nil {
return img, nil
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/vendor.conf new/image-1.5/vendor.conf
--- old/image-1.3/vendor.conf 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/vendor.conf 2019-02-26 15:39:32.000000000 +0100
@@ -28,7 +28,6 @@
golang.org/x/net 6b27048ae5e6ad1ef927e72e437531493de612fe
golang.org/x/sync 42b317875d0fa942474b76e1b46a6060d720ae6e
golang.org/x/sys 43e60d72a8e2bd92ee98319ba9a384a0e9837c08
-gopkg.in/cheggaaa/pb.v1 v1.0.27
gopkg.in/yaml.v2 a3f3340b5840cee44f372bddb5880fcbc419b46a
k8s.io/client-go bcde30fb7eaed76fd98a36b4120321b94995ffb6
github.com/xeipuuv/gojsonschema master
@@ -48,3 +47,6 @@
github.com/klauspost/pgzip v1.2.1
github.com/klauspost/compress v1.4.1
github.com/klauspost/cpuid v1.2.0
+github.com/vbauerster/mpb v3.3.4
+github.com/mattn/go-isatty v0.0.4
+github.com/VividCortex/ewma v1.1.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/image-1.3/version/version.go new/image-1.5/version/version.go
--- old/image-1.3/version/version.go 2018-12-21 15:36:20.000000000 +0100
+++ new/image-1.5/version/version.go 2019-02-26 15:39:32.000000000 +0100
@@ -8,10 +8,10 @@
// VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 1
// VersionPatch is for backwards-compatible bug fixes
- VersionPatch = 0
+ VersionPatch = 5
// VersionDev indicates development branch. Releases will be empty string.
- VersionDev = "-dev"
+ VersionDev = ""
)
// Version is the specification version that the package types support.
++++++ libpod-1.1.0.tar.xz -> libpod-1.2.0.tar.xz ++++++
++++ 52866 lines of diff (skipped)
++++++ storage-1.10.tar.xz -> storage-1.12.1.tar.xz ++++++
++++ 9629 lines of diff (skipped)