|
@@ -41,29 +41,42 @@ var (
|
|
|
errRootFSInvalid = errors.New("invalid rootfs in image configuration")
|
|
|
)
|
|
|
|
|
|
-// ImageConfigPullError is an error pulling the image config blob
|
|
|
+// imageConfigPullError is an error pulling the image config blob
|
|
|
// (only applies to schema2).
|
|
|
-type ImageConfigPullError struct {
|
|
|
+type imageConfigPullError struct {
|
|
|
Err error
|
|
|
}
|
|
|
|
|
|
-// Error returns the error string for ImageConfigPullError.
|
|
|
-func (e ImageConfigPullError) Error() string {
|
|
|
+// Error returns the error string for imageConfigPullError.
|
|
|
+func (e imageConfigPullError) Error() string {
|
|
|
return "error pulling image configuration: " + e.Err.Error()
|
|
|
}
|
|
|
|
|
|
-type v2Puller struct {
|
|
|
- V2MetadataService metadata.V2MetadataService
|
|
|
- endpoint registry.APIEndpoint
|
|
|
- config *ImagePullConfig
|
|
|
- repoInfo *registry.RepositoryInfo
|
|
|
- repo distribution.Repository
|
|
|
- manifestStore *manifestStore
|
|
|
+// newPuller returns a puller to pull from a v2 registry.
|
|
|
+func newPuller(endpoint registry.APIEndpoint, repoInfo *registry.RepositoryInfo, config *ImagePullConfig, local ContentStore) *puller {
|
|
|
+ return &puller{
|
|
|
+ metadataService: metadata.NewV2MetadataService(config.MetadataStore),
|
|
|
+ endpoint: endpoint,
|
|
|
+ config: config,
|
|
|
+ repoInfo: repoInfo,
|
|
|
+ manifestStore: &manifestStore{
|
|
|
+ local: local,
|
|
|
+ },
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+type puller struct {
|
|
|
+ metadataService metadata.V2MetadataService
|
|
|
+ endpoint registry.APIEndpoint
|
|
|
+ config *ImagePullConfig
|
|
|
+ repoInfo *registry.RepositoryInfo
|
|
|
+ repo distribution.Repository
|
|
|
+ manifestStore *manifestStore
|
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
|
|
+func (p *puller) pull(ctx context.Context, ref reference.Named) (err error) {
|
|
|
// TODO(tiborvass): was ReceiveTimeout
|
|
|
- p.repo, err = NewV2Repository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
|
|
|
+ p.repo, err = newRepository(ctx, p.repoInfo, p.endpoint, p.config.MetaHeaders, p.config.AuthConfig, "pull")
|
|
|
if err != nil {
|
|
|
logrus.Warnf("Error getting v2 registry: %v", err)
|
|
|
return err
|
|
@@ -74,7 +87,7 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
- if err = p.pullV2Repository(ctx, ref); err != nil {
|
|
|
+ if err = p.pullRepository(ctx, ref); err != nil {
|
|
|
if _, ok := err.(fallbackError); ok {
|
|
|
return err
|
|
|
}
|
|
@@ -88,10 +101,10 @@ func (p *v2Puller) Pull(ctx context.Context, ref reference.Named) (err error) {
|
|
|
return err
|
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (err error) {
|
|
|
+func (p *puller) pullRepository(ctx context.Context, ref reference.Named) (err error) {
|
|
|
var layersDownloaded bool
|
|
|
if !reference.IsNameOnly(ref) {
|
|
|
- layersDownloaded, err = p.pullV2Tag(ctx, ref, p.config.Platform)
|
|
|
+ layersDownloaded, err = p.pullTag(ctx, ref, p.config.Platform)
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
@@ -106,7 +119,7 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
|
|
|
if err != nil {
|
|
|
return err
|
|
|
}
|
|
|
- pulledNew, err := p.pullV2Tag(ctx, tagRef, p.config.Platform)
|
|
|
+ pulledNew, err := p.pullTag(ctx, tagRef, p.config.Platform)
|
|
|
if err != nil {
|
|
|
// Since this is the pull-all-tags case, don't
|
|
|
// allow an error pulling a particular tag to
|
|
@@ -122,38 +135,50 @@ func (p *v2Puller) pullV2Repository(ctx context.Context, ref reference.Named) (e
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- writeStatus(reference.FamiliarString(ref), p.config.ProgressOutput, layersDownloaded)
|
|
|
+ p.writeStatus(reference.FamiliarString(ref), layersDownloaded)
|
|
|
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-type v2LayerDescriptor struct {
|
|
|
- digest digest.Digest
|
|
|
- diffID layer.DiffID
|
|
|
- repoInfo *registry.RepositoryInfo
|
|
|
- repo distribution.Repository
|
|
|
- V2MetadataService metadata.V2MetadataService
|
|
|
- tmpFile *os.File
|
|
|
- verifier digest.Verifier
|
|
|
- src distribution.Descriptor
|
|
|
+// writeStatus writes a status message to out. If layersDownloaded is true, the
|
|
|
+// status message indicates that a newer image was downloaded. Otherwise, it
|
|
|
+// indicates that the image is up to date. requestedTag is the tag the message
|
|
|
+// will refer to.
|
|
|
+func (p *puller) writeStatus(requestedTag string, layersDownloaded bool) {
|
|
|
+ if layersDownloaded {
|
|
|
+ progress.Message(p.config.ProgressOutput, "", "Status: Downloaded newer image for "+requestedTag)
|
|
|
+ } else {
|
|
|
+ progress.Message(p.config.ProgressOutput, "", "Status: Image is up to date for "+requestedTag)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-func (ld *v2LayerDescriptor) Key() string {
|
|
|
+type layerDescriptor struct {
|
|
|
+ digest digest.Digest
|
|
|
+ diffID layer.DiffID
|
|
|
+ repoInfo *registry.RepositoryInfo
|
|
|
+ repo distribution.Repository
|
|
|
+ metadataService metadata.V2MetadataService
|
|
|
+ tmpFile *os.File
|
|
|
+ verifier digest.Verifier
|
|
|
+ src distribution.Descriptor
|
|
|
+}
|
|
|
+
|
|
|
+func (ld *layerDescriptor) Key() string {
|
|
|
return "v2:" + ld.digest.String()
|
|
|
}
|
|
|
|
|
|
-func (ld *v2LayerDescriptor) ID() string {
|
|
|
+func (ld *layerDescriptor) ID() string {
|
|
|
return stringid.TruncateID(ld.digest.String())
|
|
|
}
|
|
|
|
|
|
-func (ld *v2LayerDescriptor) DiffID() (layer.DiffID, error) {
|
|
|
+func (ld *layerDescriptor) DiffID() (layer.DiffID, error) {
|
|
|
if ld.diffID != "" {
|
|
|
return ld.diffID, nil
|
|
|
}
|
|
|
- return ld.V2MetadataService.GetDiffID(ld.digest)
|
|
|
+ return ld.metadataService.GetDiffID(ld.digest)
|
|
|
}
|
|
|
|
|
|
-func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) {
|
|
|
+func (ld *layerDescriptor) Download(ctx context.Context, progressOutput progress.Output) (io.ReadCloser, int64, error) {
|
|
|
logrus.Debugf("pulling blob %q", ld.digest)
|
|
|
|
|
|
var (
|
|
@@ -291,7 +316,7 @@ func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progre
|
|
|
}), size, nil
|
|
|
}
|
|
|
|
|
|
-func (ld *v2LayerDescriptor) Close() {
|
|
|
+func (ld *layerDescriptor) Close() {
|
|
|
if ld.tmpFile != nil {
|
|
|
ld.tmpFile.Close()
|
|
|
if err := os.RemoveAll(ld.tmpFile.Name()); err != nil {
|
|
@@ -300,7 +325,7 @@ func (ld *v2LayerDescriptor) Close() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-func (ld *v2LayerDescriptor) truncateDownloadFile() error {
|
|
|
+func (ld *layerDescriptor) truncateDownloadFile() error {
|
|
|
// Need a new hash context since we will be redoing the download
|
|
|
ld.verifier = nil
|
|
|
|
|
@@ -317,13 +342,12 @@ func (ld *v2LayerDescriptor) truncateDownloadFile() error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-func (ld *v2LayerDescriptor) Registered(diffID layer.DiffID) {
|
|
|
+func (ld *layerDescriptor) Registered(diffID layer.DiffID) {
|
|
|
// Cache mapping from this layer's DiffID to the blobsum
|
|
|
- ld.V2MetadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.Name.Name()})
|
|
|
+ _ = ld.metadataService.Add(diffID, metadata.V2Metadata{Digest: ld.digest, SourceRepository: ld.repoInfo.Name.Name()})
|
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform *specs.Platform) (tagUpdated bool, err error) {
|
|
|
-
|
|
|
+func (p *puller) pullTag(ctx context.Context, ref reference.Named, platform *specs.Platform) (tagUpdated bool, err error) {
|
|
|
var (
|
|
|
tagOrDigest string // Used for logging/progress only
|
|
|
dgst digest.Digest
|
|
@@ -397,19 +421,8 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
|
|
|
}
|
|
|
|
|
|
if m, ok := manifest.(*schema2.DeserializedManifest); ok {
|
|
|
- var allowedMediatype bool
|
|
|
- for _, t := range p.config.Schema2Types {
|
|
|
- if m.Manifest.Config.MediaType == t {
|
|
|
- allowedMediatype = true
|
|
|
- break
|
|
|
- }
|
|
|
- }
|
|
|
- if !allowedMediatype {
|
|
|
- configClass := mediaTypeClasses[m.Manifest.Config.MediaType]
|
|
|
- if configClass == "" {
|
|
|
- configClass = "unknown"
|
|
|
- }
|
|
|
- return false, invalidManifestClassError{m.Manifest.Config.MediaType, configClass}
|
|
|
+ if err := p.validateMediaType(m.Manifest.Config.MediaType); err != nil {
|
|
|
+ return false, err
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -486,7 +499,29 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named, platform
|
|
|
return true, nil
|
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
|
+// validateMediaType validates if the given mediaType is accepted by the puller's
|
|
|
+// configuration.
|
|
|
+func (p *puller) validateMediaType(mediaType string) error {
|
|
|
+ var allowedMediaTypes []string
|
|
|
+ if len(p.config.Schema2Types) > 0 {
|
|
|
+ allowedMediaTypes = p.config.Schema2Types
|
|
|
+ } else {
|
|
|
+ allowedMediaTypes = defaultImageTypes
|
|
|
+ }
|
|
|
+ for _, t := range allowedMediaTypes {
|
|
|
+ if mediaType == t {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ configClass := mediaTypeClasses[mediaType]
|
|
|
+ if configClass == "" {
|
|
|
+ configClass = "unknown"
|
|
|
+ }
|
|
|
+ return invalidManifestClassError{mediaType, configClass}
|
|
|
+}
|
|
|
+
|
|
|
+func (p *puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
|
if platform != nil {
|
|
|
// Early bath if the requested OS doesn't match that of the configuration.
|
|
|
// This avoids doing the download, only to potentially fail later.
|
|
@@ -539,11 +574,11 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
|
|
|
continue
|
|
|
}
|
|
|
|
|
|
- layerDescriptor := &v2LayerDescriptor{
|
|
|
- digest: blobSum,
|
|
|
- repoInfo: p.repoInfo,
|
|
|
- repo: p.repo,
|
|
|
- V2MetadataService: p.V2MetadataService,
|
|
|
+ layerDescriptor := &layerDescriptor{
|
|
|
+ digest: blobSum,
|
|
|
+ repoInfo: p.repoInfo,
|
|
|
+ repo: p.repo,
|
|
|
+ metadataService: p.metadataService,
|
|
|
}
|
|
|
|
|
|
descriptors = append(descriptors, layerDescriptor)
|
|
@@ -570,7 +605,7 @@ func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unv
|
|
|
return imageID, manifestDigest, nil
|
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullSchema2Layers(ctx context.Context, target distribution.Descriptor, layers []distribution.Descriptor, platform *specs.Platform) (id digest.Digest, err error) {
|
|
|
+func (p *puller) pullSchema2Layers(ctx context.Context, target distribution.Descriptor, layers []distribution.Descriptor, platform *specs.Platform) (id digest.Digest, err error) {
|
|
|
if _, err := p.config.ImageStore.Get(ctx, target.Digest); err == nil {
|
|
|
// If the image already exists locally, no need to pull
|
|
|
// anything.
|
|
@@ -585,12 +620,12 @@ func (p *v2Puller) pullSchema2Layers(ctx context.Context, target distribution.De
|
|
|
if err := d.Digest.Validate(); err != nil {
|
|
|
return "", errors.Wrapf(err, "could not validate layer digest %q", d.Digest)
|
|
|
}
|
|
|
- layerDescriptor := &v2LayerDescriptor{
|
|
|
- digest: d.Digest,
|
|
|
- repo: p.repo,
|
|
|
- repoInfo: p.repoInfo,
|
|
|
- V2MetadataService: p.V2MetadataService,
|
|
|
- src: d,
|
|
|
+ layerDescriptor := &layerDescriptor{
|
|
|
+ digest: d.Digest,
|
|
|
+ repo: p.repo,
|
|
|
+ repoInfo: p.repoInfo,
|
|
|
+ metadataService: p.metadataService,
|
|
|
+ src: d,
|
|
|
}
|
|
|
|
|
|
descriptors = append(descriptors, layerDescriptor)
|
|
@@ -608,7 +643,7 @@ func (p *v2Puller) pullSchema2Layers(ctx context.Context, target distribution.De
|
|
|
go func() {
|
|
|
configJSON, err := p.pullSchema2Config(ctx, target.Digest)
|
|
|
if err != nil {
|
|
|
- configErrChan <- ImageConfigPullError{Err: err}
|
|
|
+ configErrChan <- imageConfigPullError{Err: err}
|
|
|
cancel()
|
|
|
return
|
|
|
}
|
|
@@ -637,7 +672,7 @@ func (p *v2Puller) pullSchema2Layers(ctx context.Context, target distribution.De
|
|
|
// check to block Windows images being pulled on Linux is implemented, it
|
|
|
// may be necessary to perform the same type of serialisation.
|
|
|
if runtime.GOOS == "windows" {
|
|
|
- configJSON, configRootFS, configPlatform, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
|
|
|
+ configJSON, configRootFS, configPlatform, err = receiveConfig(configChan, configErrChan)
|
|
|
if err != nil {
|
|
|
return "", err
|
|
|
}
|
|
@@ -663,7 +698,7 @@ func (p *v2Puller) pullSchema2Layers(ctx context.Context, target distribution.De
|
|
|
// Populate diff ids in descriptors to avoid downloading foreign layers
|
|
|
// which have been side loaded
|
|
|
for i := range descriptors {
|
|
|
- descriptors[i].(*v2LayerDescriptor).diffID = configRootFS.DiffIDs[i]
|
|
|
+ descriptors[i].(*layerDescriptor).diffID = configRootFS.DiffIDs[i]
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -698,7 +733,7 @@ func (p *v2Puller) pullSchema2Layers(ctx context.Context, target distribution.De
|
|
|
}
|
|
|
|
|
|
if configJSON == nil {
|
|
|
- configJSON, configRootFS, _, err = receiveConfig(p.config.ImageStore, configChan, configErrChan)
|
|
|
+ configJSON, configRootFS, _, err = receiveConfig(configChan, configErrChan)
|
|
|
if err == nil && configRootFS == nil {
|
|
|
err = errRootFSInvalid
|
|
|
}
|
|
@@ -745,7 +780,7 @@ func (p *v2Puller) pullSchema2Layers(ctx context.Context, target distribution.De
|
|
|
return imageID, nil
|
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
|
+func (p *puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *schema2.DeserializedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
|
manifestDigest, err = schema2ManifestDigest(ref, mfst)
|
|
|
if err != nil {
|
|
|
return "", "", err
|
|
@@ -754,7 +789,7 @@ func (p *v2Puller) pullSchema2(ctx context.Context, ref reference.Named, mfst *s
|
|
|
return id, manifestDigest, err
|
|
|
}
|
|
|
|
|
|
-func (p *v2Puller) pullOCI(ctx context.Context, ref reference.Named, mfst *ocischema.DeserializedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
|
+func (p *puller) pullOCI(ctx context.Context, ref reference.Named, mfst *ocischema.DeserializedManifest, platform *specs.Platform) (id digest.Digest, manifestDigest digest.Digest, err error) {
|
|
|
manifestDigest, err = schema2ManifestDigest(ref, mfst)
|
|
|
if err != nil {
|
|
|
return "", "", err
|
|
@@ -763,14 +798,14 @@ func (p *v2Puller) pullOCI(ctx context.Context, ref reference.Named, mfst *ocisc
|
|
|
return id, manifestDigest, err
|
|
|
}
|
|
|
|
|
|
-func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, *specs.Platform, error) {
|
|
|
+func receiveConfig(configChan <-chan []byte, errChan <-chan error) ([]byte, *image.RootFS, *specs.Platform, error) {
|
|
|
select {
|
|
|
case configJSON := <-configChan:
|
|
|
- rootfs, err := s.RootFSFromConfig(configJSON)
|
|
|
+ rootfs, err := rootFSFromConfig(configJSON)
|
|
|
if err != nil {
|
|
|
return nil, nil, nil, err
|
|
|
}
|
|
|
- platform, err := s.PlatformFromConfig(configJSON)
|
|
|
+ platform, err := platformFromConfig(configJSON)
|
|
|
if err != nil {
|
|
|
return nil, nil, nil, err
|
|
|
}
|
|
@@ -784,7 +819,7 @@ func receiveConfig(s ImageConfigStore, configChan <-chan []byte, errChan <-chan
|
|
|
|
|
|
// pullManifestList handles "manifest lists" which point to various
|
|
|
// platform-specific manifests.
|
|
|
-func (p *v2Puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, pp *specs.Platform) (id digest.Digest, manifestListDigest digest.Digest, err error) {
|
|
|
+func (p *puller) pullManifestList(ctx context.Context, ref reference.Named, mfstList *manifestlist.DeserializedManifestList, pp *specs.Platform) (id digest.Digest, manifestListDigest digest.Digest, err error) {
|
|
|
manifestListDigest, err = schema2ManifestDigest(ref, mfstList)
|
|
|
if err != nil {
|
|
|
return "", "", err
|
|
@@ -863,7 +898,7 @@ const (
|
|
|
defaultMaxSchemaPullAttempts = 5
|
|
|
)
|
|
|
|
|
|
-func (p *v2Puller) pullSchema2Config(ctx context.Context, dgst digest.Digest) (configJSON []byte, err error) {
|
|
|
+func (p *puller) pullSchema2Config(ctx context.Context, dgst digest.Digest) (configJSON []byte, err error) {
|
|
|
blobs := p.repo.Blobs(ctx)
|
|
|
err = retry(ctx, defaultMaxSchemaPullAttempts, defaultSchemaPullBackoff, func(ctx context.Context) (err error) {
|
|
|
configJSON, err = blobs.Get(ctx, dgst)
|