...

Source file src/github.com/google/go-containerregistry/pkg/v1/remote/image.go

Documentation: github.com/google/go-containerregistry/pkg/v1/remote

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //    http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package remote
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"io"
    21  	"net/http"
    22  	"net/url"
    23  	"sync"
    24  
    25  	"github.com/google/go-containerregistry/internal/redact"
    26  	"github.com/google/go-containerregistry/internal/verify"
    27  	"github.com/google/go-containerregistry/pkg/name"
    28  	v1 "github.com/google/go-containerregistry/pkg/v1"
    29  	"github.com/google/go-containerregistry/pkg/v1/partial"
    30  	"github.com/google/go-containerregistry/pkg/v1/remote/transport"
    31  	"github.com/google/go-containerregistry/pkg/v1/types"
    32  )
    33  
    34  var acceptableImageMediaTypes = []types.MediaType{
    35  	types.DockerManifestSchema2,
    36  	types.OCIManifestSchema1,
    37  }
    38  
    39  // remoteImage accesses an image from a remote registry
    40  type remoteImage struct {
    41  	fetcher      fetcher
    42  	ref          name.Reference
    43  	ctx          context.Context
    44  	manifestLock sync.Mutex // Protects manifest
    45  	manifest     []byte
    46  	configLock   sync.Mutex // Protects config
    47  	config       []byte
    48  	mediaType    types.MediaType
    49  	descriptor   *v1.Descriptor
    50  }
    51  
    52  func (r *remoteImage) ArtifactType() (string, error) {
    53  	// kind of a hack, but RawManifest does appropriate locking/memoization
    54  	// and makes sure r.descriptor is populated.
    55  	if _, err := r.RawManifest(); err != nil {
    56  		return "", err
    57  	}
    58  	return r.descriptor.ArtifactType, nil
    59  }
    60  
    61  var _ partial.CompressedImageCore = (*remoteImage)(nil)
    62  
    63  // Image provides access to a remote image reference.
    64  func Image(ref name.Reference, options ...Option) (v1.Image, error) {
    65  	desc, err := Get(ref, options...)
    66  	if err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	return desc.Image()
    71  }
    72  
    73  func (r *remoteImage) MediaType() (types.MediaType, error) {
    74  	if string(r.mediaType) != "" {
    75  		return r.mediaType, nil
    76  	}
    77  	return types.DockerManifestSchema2, nil
    78  }
    79  
    80  func (r *remoteImage) RawManifest() ([]byte, error) {
    81  	r.manifestLock.Lock()
    82  	defer r.manifestLock.Unlock()
    83  	if r.manifest != nil {
    84  		return r.manifest, nil
    85  	}
    86  
    87  	// NOTE(jonjohnsonjr): We should never get here because the public entrypoints
    88  	// do type-checking via remote.Descriptor. I've left this here for tests that
    89  	// directly instantiate a remoteImage.
    90  	manifest, desc, err := r.fetcher.fetchManifest(r.ctx, r.ref, acceptableImageMediaTypes)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	if r.descriptor == nil {
    96  		r.descriptor = desc
    97  	}
    98  	r.mediaType = desc.MediaType
    99  	r.manifest = manifest
   100  	return r.manifest, nil
   101  }
   102  
   103  func (r *remoteImage) RawConfigFile() ([]byte, error) {
   104  	r.configLock.Lock()
   105  	defer r.configLock.Unlock()
   106  	if r.config != nil {
   107  		return r.config, nil
   108  	}
   109  
   110  	m, err := partial.Manifest(r)
   111  	if err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	if m.Config.Data != nil {
   116  		if err := verify.Descriptor(m.Config); err != nil {
   117  			return nil, err
   118  		}
   119  		r.config = m.Config.Data
   120  		return r.config, nil
   121  	}
   122  
   123  	body, err := r.fetcher.fetchBlob(r.ctx, m.Config.Size, m.Config.Digest)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	defer body.Close()
   128  
   129  	r.config, err = io.ReadAll(body)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	return r.config, nil
   134  }
   135  
   136  // Descriptor retains the original descriptor from an index manifest.
   137  // See partial.Descriptor.
   138  func (r *remoteImage) Descriptor() (*v1.Descriptor, error) {
   139  	// kind of a hack, but RawManifest does appropriate locking/memoization
   140  	// and makes sure r.descriptor is populated.
   141  	_, err := r.RawManifest()
   142  	return r.descriptor, err
   143  }
   144  
   145  func (r *remoteImage) ConfigLayer() (v1.Layer, error) {
   146  	if _, err := r.RawManifest(); err != nil {
   147  		return nil, err
   148  	}
   149  	m, err := partial.Manifest(r)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	return partial.CompressedToLayer(&remoteImageLayer{
   155  		ri:     r,
   156  		ctx:    r.ctx,
   157  		digest: m.Config.Digest,
   158  	})
   159  }
   160  
   161  // remoteImageLayer implements partial.CompressedLayer
   162  type remoteImageLayer struct {
   163  	ri     *remoteImage
   164  	ctx    context.Context
   165  	digest v1.Hash
   166  }
   167  
   168  // Digest implements partial.CompressedLayer
   169  func (rl *remoteImageLayer) Digest() (v1.Hash, error) {
   170  	return rl.digest, nil
   171  }
   172  
   173  // Compressed implements partial.CompressedLayer
   174  func (rl *remoteImageLayer) Compressed() (io.ReadCloser, error) {
   175  	urls := []url.URL{rl.ri.fetcher.url("blobs", rl.digest.String())}
   176  
   177  	// Add alternative layer sources from URLs (usually none).
   178  	d, err := partial.BlobDescriptor(rl, rl.digest)
   179  	if err != nil {
   180  		return nil, err
   181  	}
   182  
   183  	if d.Data != nil {
   184  		return verify.ReadCloser(io.NopCloser(bytes.NewReader(d.Data)), d.Size, d.Digest)
   185  	}
   186  
   187  	// We don't want to log binary layers -- this can break terminals.
   188  	ctx := redact.NewContext(rl.ctx, "omitting binary blobs from logs")
   189  
   190  	for _, s := range d.URLs {
   191  		u, err := url.Parse(s)
   192  		if err != nil {
   193  			return nil, err
   194  		}
   195  		urls = append(urls, *u)
   196  	}
   197  
   198  	// The lastErr for most pulls will be the same (the first error), but for
   199  	// foreign layers we'll want to surface the last one, since we try to pull
   200  	// from the registry first, which would often fail.
   201  	// TODO: Maybe we don't want to try pulling from the registry first?
   202  	var lastErr error
   203  	for _, u := range urls {
   204  		req, err := http.NewRequest(http.MethodGet, u.String(), nil)
   205  		if err != nil {
   206  			return nil, err
   207  		}
   208  
   209  		resp, err := rl.ri.fetcher.Do(req.WithContext(ctx))
   210  		if err != nil {
   211  			lastErr = err
   212  			continue
   213  		}
   214  
   215  		if err := transport.CheckError(resp, http.StatusOK); err != nil {
   216  			resp.Body.Close()
   217  			lastErr = err
   218  			continue
   219  		}
   220  
   221  		return verify.ReadCloser(resp.Body, d.Size, rl.digest)
   222  	}
   223  
   224  	return nil, lastErr
   225  }
   226  
   227  // Manifest implements partial.WithManifest so that we can use partial.BlobSize below.
   228  func (rl *remoteImageLayer) Manifest() (*v1.Manifest, error) {
   229  	return partial.Manifest(rl.ri)
   230  }
   231  
   232  // MediaType implements v1.Layer
   233  func (rl *remoteImageLayer) MediaType() (types.MediaType, error) {
   234  	bd, err := partial.BlobDescriptor(rl, rl.digest)
   235  	if err != nil {
   236  		return "", err
   237  	}
   238  
   239  	return bd.MediaType, nil
   240  }
   241  
   242  // Size implements partial.CompressedLayer
   243  func (rl *remoteImageLayer) Size() (int64, error) {
   244  	// Look up the size of this digest in the manifest to avoid a request.
   245  	return partial.BlobSize(rl, rl.digest)
   246  }
   247  
   248  // ConfigFile implements partial.WithManifestAndConfigFile so that we can use partial.BlobToDiffID below.
   249  func (rl *remoteImageLayer) ConfigFile() (*v1.ConfigFile, error) {
   250  	return partial.ConfigFile(rl.ri)
   251  }
   252  
   253  // DiffID implements partial.WithDiffID so that we don't recompute a DiffID that we already have
   254  // available in our ConfigFile.
   255  func (rl *remoteImageLayer) DiffID() (v1.Hash, error) {
   256  	return partial.BlobToDiffID(rl, rl.digest)
   257  }
   258  
   259  // Descriptor retains the original descriptor from an image manifest.
   260  // See partial.Descriptor.
   261  func (rl *remoteImageLayer) Descriptor() (*v1.Descriptor, error) {
   262  	return partial.BlobDescriptor(rl, rl.digest)
   263  }
   264  
   265  // See partial.Exists.
   266  func (rl *remoteImageLayer) Exists() (bool, error) {
   267  	return rl.ri.fetcher.blobExists(rl.ri.ctx, rl.digest)
   268  }
   269  
   270  // LayerByDigest implements partial.CompressedLayer
   271  func (r *remoteImage) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error) {
   272  	return &remoteImageLayer{
   273  		ri:     r,
   274  		ctx:    r.ctx,
   275  		digest: h,
   276  	}, nil
   277  }
   278  

View as plain text