vendor github.com/containers/image@v2.0.1

* progress bar: use spinners for unknown blob sizes
* use 'containers_image_ostree' as build tag
* ostree: default is no OStree support
* Add "Env" to ImageInspectInfo
* config.go: improve debug message
* config.go: log where credentials come from
* Fix typo in docs/containers-registries.conf.5.md
* docker: delete: support all MIME types
* Try harder in storageImageDestination.TryReusingBlob
* docker: allow deleting OCI images
* ostree: improve error message

Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
This commit is contained in:
Valentin Rothberg
2019-07-18 16:50:34 +02:00
parent d6b41eb393
commit c1b792c1e0
20 changed files with 119 additions and 83 deletions

2
go.mod
View File

@ -18,7 +18,7 @@ require (
github.com/containernetworking/cni v0.7.1 github.com/containernetworking/cni v0.7.1
github.com/containernetworking/plugins v0.8.1 github.com/containernetworking/plugins v0.8.1
github.com/containers/buildah v1.9.0 github.com/containers/buildah v1.9.0
github.com/containers/image v2.0.0+incompatible github.com/containers/image v2.0.1+incompatible
github.com/containers/psgo v1.3.1 github.com/containers/psgo v1.3.1
github.com/containers/storage v1.12.13 github.com/containers/storage v1.12.13
github.com/coreos/bbolt v1.3.3 // indirect github.com/coreos/bbolt v1.3.3 // indirect

2
go.sum
View File

@ -70,6 +70,8 @@ github.com/containers/buildah v1.9.0 h1:ktVRCGNoVfW8PlTuCKUeh+zGdqn1Nik80DSWvGX+
github.com/containers/buildah v1.9.0/go.mod h1:1CsiLJvyU+h+wOjnqJJOWuJCVcMxZOr5HN/gHGdzJxY= github.com/containers/buildah v1.9.0/go.mod h1:1CsiLJvyU+h+wOjnqJJOWuJCVcMxZOr5HN/gHGdzJxY=
github.com/containers/image v2.0.0+incompatible h1:FTr6Br7jlIKNCKMjSOMbAxKp2keQ0//jzJaYNTVhauk= github.com/containers/image v2.0.0+incompatible h1:FTr6Br7jlIKNCKMjSOMbAxKp2keQ0//jzJaYNTVhauk=
github.com/containers/image v2.0.0+incompatible/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M= github.com/containers/image v2.0.0+incompatible/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M=
github.com/containers/image v2.0.1+incompatible h1:w39mlElA/aSFZ6moFa5N+A4MWu9c8hgdMiMMYnH94Hs=
github.com/containers/image v2.0.1+incompatible/go.mod h1:8Vtij257IWSanUQKe1tAeNOm2sRVkSqQTVQ1IlwI3+M=
github.com/containers/psgo v1.3.0 h1:kDhiA4gNNyJ2qCzmOuBf6AmrF/Pp+6Jo98P68R7fB8I= github.com/containers/psgo v1.3.0 h1:kDhiA4gNNyJ2qCzmOuBf6AmrF/Pp+6Jo98P68R7fB8I=
github.com/containers/psgo v1.3.0/go.mod h1:7MELvPTW1fj6yMrwD9I1Iasx1vU+hKlRkHXAJ51sFtU= github.com/containers/psgo v1.3.0/go.mod h1:7MELvPTW1fj6yMrwD9I1Iasx1vU+hKlRkHXAJ51sFtU=
github.com/containers/psgo v1.3.1-0.20190626112706-fbef66e4ce92 h1:aVJs/Av0Yc9uNoWnIwmG+6Z+XozuRXFwvLwAOVmwlvI= github.com/containers/psgo v1.3.1-0.20190626112706-fbef66e4ce92 h1:aVJs/Av0Yc9uNoWnIwmG+6Z+XozuRXFwvLwAOVmwlvI=

View File

@ -2,5 +2,5 @@
if ! pkg-config glib-2.0 gobject-2.0 ostree-1 libselinux 2> /dev/null ; then if ! pkg-config glib-2.0 gobject-2.0 ostree-1 libselinux 2> /dev/null ; then
echo containers_image_ostree_stub echo containers_image_ostree_stub
else else
echo ostree echo containers_image_ostree
fi fi

View File

@ -597,15 +597,32 @@ func (c *copier) createProgressBar(pool *mpb.Progress, info types.BlobInfo, kind
prefix = prefix[:maxPrefixLen] prefix = prefix[:maxPrefixLen]
} }
bar := pool.AddBar(info.Size, // Use a normal progress bar when we know the size (i.e., size > 0).
mpb.BarClearOnComplete(), // Otherwise, use a spinner to indicate that something's happening.
mpb.PrependDecorators( var bar *mpb.Bar
decor.Name(prefix), if info.Size > 0 {
), bar = pool.AddBar(info.Size,
mpb.AppendDecorators( mpb.BarClearOnComplete(),
decor.OnComplete(decor.CountersKibiByte("%.1f / %.1f"), " "+onComplete), mpb.PrependDecorators(
), decor.Name(prefix),
) ),
mpb.AppendDecorators(
decor.OnComplete(decor.CountersKibiByte("%.1f / %.1f"), " "+onComplete),
),
)
} else {
bar = pool.AddSpinner(info.Size,
mpb.SpinnerOnLeft,
mpb.BarClearOnComplete(),
mpb.SpinnerStyle([]string{".", "..", "...", "....", ""}),
mpb.PrependDecorators(
decor.Name(prefix),
),
mpb.AppendDecorators(
decor.OnComplete(decor.Name(""), " "+onComplete),
),
)
}
if c.progressOutput == ioutil.Discard { if c.progressOutput == ioutil.Discard {
c.Printf("Copying %s %s\n", kind, info.Digest) c.Printf("Copying %s %s\n", kind, info.Digest)
} }

View File

@ -138,8 +138,9 @@ func (s *dockerImageSource) GetManifest(ctx context.Context, instanceDigest *dig
func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest string) ([]byte, string, error) { func (s *dockerImageSource) fetchManifest(ctx context.Context, tagOrDigest string) ([]byte, string, error) {
path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest) path := fmt.Sprintf(manifestPath, reference.Path(s.ref.ref), tagOrDigest)
headers := make(map[string][]string) headers := map[string][]string{
headers["Accept"] = manifest.DefaultRequestedManifestMIMETypes "Accept": manifest.DefaultRequestedManifestMIMETypes,
}
res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth, nil) res, err := s.c.makeRequest(ctx, "GET", path, headers, nil, v2Auth, nil)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
@ -381,11 +382,9 @@ func deleteImage(ctx context.Context, sys *types.SystemContext, ref dockerRefere
return err return err
} }
// When retrieving the digest from a registry >= 2.3 use the following header: headers := map[string][]string{
// "Accept": "application/vnd.docker.distribution.manifest.v2+json" "Accept": manifest.DefaultRequestedManifestMIMETypes,
headers := make(map[string][]string) }
headers["Accept"] = []string{manifest.DockerV2Schema2MediaType}
refTail, err := ref.tagOrDigest() refTail, err := ref.tagOrDigest()
if err != nil { if err != nil {
return err return err

View File

@ -15,24 +15,24 @@ import (
"github.com/containers/image/manifest" "github.com/containers/image/manifest"
"github.com/containers/image/pkg/compression" "github.com/containers/image/pkg/compression"
"github.com/containers/image/types" "github.com/containers/image/types"
"github.com/opencontainers/go-digest" digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
// Source is a partial implementation of types.ImageSource for reading from tarPath. // Source is a partial implementation of types.ImageSource for reading from tarPath.
type Source struct { type Source struct {
tarPath string 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 // 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. tarManifest *ManifestItem // nil if not available yet.
configBytes []byte configBytes []byte
configDigest digest.Digest configDigest digest.Digest
orderedDiffIDList []digest.Digest orderedDiffIDList []digest.Digest
knownLayers map[digest.Digest]*layerInfo knownLayers map[digest.Digest]*layerInfo
// Other state // Other state
generatedManifest []byte // Private cache for GetManifest(), nil if not set yet. generatedManifest []byte // Private cache for GetManifest(), nil if not set yet.
cacheDataLock sync.Once // Private state for ensureCachedDataIsPresent to make it concurrency-safe
cacheDataResult error // Private state for ensureCachedDataIsPresent
} }
type layerInfo struct { type layerInfo struct {
@ -201,49 +201,52 @@ func (s *Source) readTarComponent(path string) ([]byte, error) {
} }
// ensureCachedDataIsPresent loads data necessary for any of the public accessors. // ensureCachedDataIsPresent loads data necessary for any of the public accessors.
// It is safe to call this from multi-threaded code.
func (s *Source) ensureCachedDataIsPresent() error { func (s *Source) ensureCachedDataIsPresent() error {
s.cacheDataLock.Do(func() { s.cacheDataLock.Do(func() {
// Read and parse manifest.json s.cacheDataResult = s.ensureCachedDataIsPresentPrivate()
tarManifest, err := s.loadTarManifest()
if err != nil {
s.cacheDataResult = err
return
}
// 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 {
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 {
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 s.cacheDataResult return s.cacheDataResult
} }
// ensureCachedDataIsPresentPrivate is a private implementation detail of ensureCachedDataIsPresent.
// Call ensureCachedDataIsPresent instead.
func (s *Source) ensureCachedDataIsPresentPrivate() error {
// Read and parse manifest.json
tarManifest, err := s.loadTarManifest()
if err != nil {
return err
}
// 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))
}
// 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)
}
knownLayers, err := s.prepareLayerData(&tarManifest[0], &parsedConfig)
if err != nil {
return err
}
// Success; commit.
s.tarManifest = &tarManifest[0]
s.configBytes = configBytes
s.configDigest = digest.FromBytes(configBytes)
s.orderedDiffIDList = parsedConfig.RootFS.DiffIDs
s.knownLayers = knownLayers
return nil
}
// loadTarManifest loads and decodes the manifest.json. // loadTarManifest loads and decodes the manifest.json.
func (s *Source) loadTarManifest() ([]ManifestItem, error) { func (s *Source) loadTarManifest() ([]ManifestItem, error) {
// FIXME? Do we need to deal with the legacy format? // FIXME? Do we need to deal with the legacy format?

View File

@ -226,6 +226,7 @@ func (m *Schema1) Inspect(_ func(types.BlobInfo) ([]byte, error)) (*types.ImageI
} }
if s1.Config != nil { if s1.Config != nil {
i.Labels = s1.Config.Labels i.Labels = s1.Config.Labels
i.Env = s1.Config.Env
} }
return i, nil return i, nil
} }

View File

@ -241,6 +241,7 @@ func (m *Schema2) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*t
} }
if s2.Config != nil { if s2.Config != nil {
i.Labels = s2.Config.Labels i.Labels = s2.Config.Labels
i.Env = s2.Config.Env
} }
return i, nil return i, nil
} }

View File

@ -116,6 +116,7 @@ func (m *OCI1) Inspect(configGetter func(types.BlobInfo) ([]byte, error)) (*type
Architecture: v1.Architecture, Architecture: v1.Architecture,
Os: v1.OS, Os: v1.OS,
Layers: layerInfosToStrings(m.LayerInfos()), Layers: layerInfosToStrings(m.LayerInfos()),
Env: d1.Config.Env,
} }
return i, nil return i, nil
} }

View File

@ -1,4 +1,4 @@
// +build !containers_image_ostree_stub // +build containers_image_ostree
package ostree package ostree
@ -218,7 +218,7 @@ func fixFiles(selinuxHnd *C.struct_selabel_handle, root string, dir string, user
defer C.free(unsafe.Pointer(fullpathC)) defer C.free(unsafe.Pointer(fullpathC))
res, err = C.lsetfilecon_raw(fullpathC, context) res, err = C.lsetfilecon_raw(fullpathC, context)
if int(res) < 0 { if int(res) < 0 {
return errors.Wrapf(err, "cannot setfilecon_raw %s", fullpath) return errors.Wrapf(err, "cannot setfilecon_raw %s to %s", fullpath, C.GoString(context))
} }
} }
} }

View File

@ -1,4 +1,4 @@
// +build !containers_image_ostree_stub // +build containers_image_ostree
package ostree package ostree

View File

@ -1,4 +1,4 @@
// +build !containers_image_ostree_stub // +build containers_image_ostree
package ostree package ostree

View File

@ -56,6 +56,7 @@ func SetAuthentication(sys *types.SystemContext, registry, username, password st
// If an entry is not found empty strings are returned for the username and password // If an entry is not found empty strings are returned for the username and password
func GetAuthentication(sys *types.SystemContext, registry string) (string, string, error) { func GetAuthentication(sys *types.SystemContext, registry string) (string, string, error) {
if sys != nil && sys.DockerAuthConfig != nil { if sys != nil && sys.DockerAuthConfig != nil {
logrus.Debug("Returning credentials from DockerAuthConfig")
return sys.DockerAuthConfig.Username, sys.DockerAuthConfig.Password, nil return sys.DockerAuthConfig.Username, sys.DockerAuthConfig.Password, nil
} }
@ -76,12 +77,15 @@ func GetAuthentication(sys *types.SystemContext, registry string) (string, strin
legacyFormat := path == dockerLegacyPath legacyFormat := path == dockerLegacyPath
username, password, err := findAuthentication(registry, path, legacyFormat) username, password, err := findAuthentication(registry, path, legacyFormat)
if err != nil { if err != nil {
logrus.Debugf("Credentials not found")
return "", "", err return "", "", err
} }
if username != "" && password != "" { if username != "" && password != "" {
logrus.Debugf("Returning credentials from %s", path)
return username, password, nil return username, password, nil
} }
} }
logrus.Debugf("Credentials not found")
return "", "", nil return "", "", nil
} }

View File

@ -30,10 +30,10 @@ const builtinRegistriesConfPath = "/etc/containers/registries.conf"
// Endpoint describes a remote location of a registry. // Endpoint describes a remote location of a registry.
type Endpoint struct { type Endpoint struct {
// The endpoint's remote location. // The endpoint's remote location.
Location string `toml:"location"` Location string `toml:"location,omitempty"`
// If true, certs verification will be skipped and HTTP (non-TLS) // If true, certs verification will be skipped and HTTP (non-TLS)
// connections will be allowed. // connections will be allowed.
Insecure bool `toml:"insecure"` Insecure bool `toml:"insecure,omitempty"`
} }
// rewriteReference will substitute the provided reference `prefix` to the // rewriteReference will substitute the provided reference `prefix` to the
@ -56,22 +56,22 @@ func (e *Endpoint) rewriteReference(ref reference.Named, prefix string) (referen
// Registry represents a registry. // Registry represents a registry.
type Registry struct { type Registry struct {
// A registry is an Endpoint too
Endpoint
// The registry's mirrors.
Mirrors []Endpoint `toml:"mirror"`
// If true, pulling from the registry will be blocked.
Blocked bool `toml:"blocked"`
// If true, mirrors will only be used for digest pulls. Pulling images by
// tag can potentially yield different images, depending on which endpoint
// we pull from. Forcing digest-pulls for mirrors avoids that issue.
MirrorByDigestOnly bool `toml:"mirror-by-digest-only"`
// Prefix is used for matching images, and to translate one namespace to // Prefix is used for matching images, and to translate one namespace to
// another. If `Prefix="example.com/bar"`, `location="example.com/foo/bar"` // another. If `Prefix="example.com/bar"`, `location="example.com/foo/bar"`
// and we pull from "example.com/bar/myimage:latest", the image will // and we pull from "example.com/bar/myimage:latest", the image will
// effectively be pulled from "example.com/foo/bar/myimage:latest". // effectively be pulled from "example.com/foo/bar/myimage:latest".
// If no Prefix is specified, it defaults to the specified location. // If no Prefix is specified, it defaults to the specified location.
Prefix string `toml:"prefix"` Prefix string `toml:"prefix"`
// A registry is an Endpoint too
Endpoint
// The registry's mirrors.
Mirrors []Endpoint `toml:"mirror,omitempty"`
// If true, pulling from the registry will be blocked.
Blocked bool `toml:"blocked,omitempty"`
// If true, mirrors will only be used for digest pulls. Pulling images by
// tag can potentially yield different images, depending on which endpoint
// we pull from. Forcing digest-pulls for mirrors avoids that issue.
MirrorByDigestOnly bool `toml:"mirror-by-digest-only,omitempty"`
} }
// PullSource consists of an Endpoint and a Reference. Note that the reference is // PullSource consists of an Endpoint and a Reference. Note that the reference is

View File

@ -491,14 +491,21 @@ func (s *storageImageDestination) TryReusingBlob(ctx context.Context, blobinfo t
// Does the blob correspond to a known DiffID which we already have available? // Does the blob correspond to a known DiffID which we already have available?
// Because we must return the size, which is unknown for unavailable compressed blobs, the returned BlobInfo refers to the // Because we must return the size, which is unknown for unavailable compressed blobs, the returned BlobInfo refers to the
// uncompressed layer, and that can happen only if canSubstitute. // uncompressed layer, and that can happen only if canSubstitute, or if the incoming manifest already specifies the size.
if canSubstitute { if canSubstitute || blobinfo.Size != -1 {
if uncompressedDigest := cache.UncompressedDigest(blobinfo.Digest); uncompressedDigest != "" && uncompressedDigest != blobinfo.Digest { if uncompressedDigest := cache.UncompressedDigest(blobinfo.Digest); uncompressedDigest != "" && uncompressedDigest != blobinfo.Digest {
layers, err := s.imageRef.transport.store.LayersByUncompressedDigest(uncompressedDigest) layers, err := s.imageRef.transport.store.LayersByUncompressedDigest(uncompressedDigest)
if err != nil && errors.Cause(err) != storage.ErrLayerUnknown { if err != nil && errors.Cause(err) != storage.ErrLayerUnknown {
return false, types.BlobInfo{}, errors.Wrapf(err, `Error looking for layers with digest %q`, uncompressedDigest) return false, types.BlobInfo{}, errors.Wrapf(err, `Error looking for layers with digest %q`, uncompressedDigest)
} }
if len(layers) > 0 { if len(layers) > 0 {
if blobinfo.Size != -1 {
s.blobDiffIDs[blobinfo.Digest] = layers[0].UncompressedDigest
return true, blobinfo, nil
}
if !canSubstitute {
return false, types.BlobInfo{}, fmt.Errorf("Internal error: canSubstitute was expected to be true for blobInfo %v", blobinfo)
}
s.blobDiffIDs[uncompressedDigest] = layers[0].UncompressedDigest s.blobDiffIDs[uncompressedDigest] = layers[0].UncompressedDigest
return true, types.BlobInfo{ return true, types.BlobInfo{
Digest: uncompressedDigest, Digest: uncompressedDigest,
@ -627,7 +634,7 @@ func (s *storageImageDestination) Commit(ctx context.Context) error {
if !ok { if !ok {
// Try to find the layer with contents matching that blobsum. // Try to find the layer with contents matching that blobsum.
layer := "" layer := ""
layers, err2 := s.imageRef.transport.store.LayersByUncompressedDigest(blob.Digest) layers, err2 := s.imageRef.transport.store.LayersByUncompressedDigest(diffID)
if err2 == nil && len(layers) > 0 { if err2 == nil && len(layers) > 0 {
layer = layers[0].ID layer = layers[0].ID
} else { } else {

View File

@ -1,4 +1,4 @@
// +build !containers_image_ostree_stub,linux // +build containers_image_ostree,linux
package alltransports package alltransports

View File

@ -1,4 +1,4 @@
// +build containers_image_ostree_stub !linux // +build !containers_image_ostree !linux
package alltransports package alltransports

View File

@ -398,6 +398,7 @@ type ImageInspectInfo struct {
Architecture string Architecture string
Os string Os string
Layers []string Layers []string
Env []string
} }
// DockerAuthConfig contains authorization information for connecting to a registry. // DockerAuthConfig contains authorization information for connecting to a registry.

View File

@ -8,7 +8,7 @@ const (
// VersionMinor is for functionality in a backwards-compatible manner // VersionMinor is for functionality in a backwards-compatible manner
VersionMinor = 0 VersionMinor = 0
// VersionPatch is for backwards-compatible bug fixes // VersionPatch is for backwards-compatible bug fixes
VersionPatch = 0 VersionPatch = 1
// VersionDev indicates development branch. Releases will be empty string. // VersionDev indicates development branch. Releases will be empty string.
VersionDev = "" VersionDev = ""

2
vendor/modules.txt vendored
View File

@ -62,7 +62,7 @@ github.com/containers/buildah/docker
github.com/containers/buildah/pkg/blobcache github.com/containers/buildah/pkg/blobcache
github.com/containers/buildah/pkg/overlay github.com/containers/buildah/pkg/overlay
github.com/containers/buildah/pkg/unshare github.com/containers/buildah/pkg/unshare
# github.com/containers/image v2.0.0+incompatible # github.com/containers/image v2.0.1+incompatible
github.com/containers/image/directory github.com/containers/image/directory
github.com/containers/image/docker github.com/containers/image/docker
github.com/containers/image/docker/archive github.com/containers/image/docker/archive