mirror of https://github.com/k3d-io/k3d
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
256 lines
6.9 KiB
256 lines
6.9 KiB
// Copyright 2018 Google LLC All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package remote
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"sync"
|
|
|
|
"github.com/google/go-containerregistry/internal/redact"
|
|
"github.com/google/go-containerregistry/internal/verify"
|
|
"github.com/google/go-containerregistry/pkg/name"
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
"github.com/google/go-containerregistry/pkg/v1/partial"
|
|
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
|
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
|
)
|
|
|
|
var acceptableImageMediaTypes = []types.MediaType{
|
|
types.DockerManifestSchema2,
|
|
types.OCIManifestSchema1,
|
|
}
|
|
|
|
// remoteImage accesses an image from a remote registry
|
|
type remoteImage struct {
|
|
fetcher
|
|
manifestLock sync.Mutex // Protects manifest
|
|
manifest []byte
|
|
configLock sync.Mutex // Protects config
|
|
config []byte
|
|
mediaType types.MediaType
|
|
descriptor *v1.Descriptor
|
|
}
|
|
|
|
func (r *remoteImage) ArtifactType() (string, error) {
|
|
// kind of a hack, but RawManifest does appropriate locking/memoization
|
|
// and makes sure r.descriptor is populated.
|
|
if _, err := r.RawManifest(); err != nil {
|
|
return "", err
|
|
}
|
|
return r.descriptor.ArtifactType, nil
|
|
}
|
|
|
|
var _ partial.CompressedImageCore = (*remoteImage)(nil)
|
|
|
|
// Image provides access to a remote image reference.
|
|
func Image(ref name.Reference, options ...Option) (v1.Image, error) {
|
|
desc, err := Get(ref, options...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return desc.Image()
|
|
}
|
|
|
|
func (r *remoteImage) MediaType() (types.MediaType, error) {
|
|
if string(r.mediaType) != "" {
|
|
return r.mediaType, nil
|
|
}
|
|
return types.DockerManifestSchema2, nil
|
|
}
|
|
|
|
func (r *remoteImage) RawManifest() ([]byte, error) {
|
|
r.manifestLock.Lock()
|
|
defer r.manifestLock.Unlock()
|
|
if r.manifest != nil {
|
|
return r.manifest, nil
|
|
}
|
|
|
|
// NOTE(jonjohnsonjr): We should never get here because the public entrypoints
|
|
// do type-checking via remote.Descriptor. I've left this here for tests that
|
|
// directly instantiate a remoteImage.
|
|
manifest, desc, err := r.fetchManifest(r.Ref, acceptableImageMediaTypes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if r.descriptor == nil {
|
|
r.descriptor = desc
|
|
}
|
|
r.mediaType = desc.MediaType
|
|
r.manifest = manifest
|
|
return r.manifest, nil
|
|
}
|
|
|
|
func (r *remoteImage) RawConfigFile() ([]byte, error) {
|
|
r.configLock.Lock()
|
|
defer r.configLock.Unlock()
|
|
if r.config != nil {
|
|
return r.config, nil
|
|
}
|
|
|
|
m, err := partial.Manifest(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if m.Config.Data != nil {
|
|
if err := verify.Descriptor(m.Config); err != nil {
|
|
return nil, err
|
|
}
|
|
r.config = m.Config.Data
|
|
return r.config, nil
|
|
}
|
|
|
|
body, err := r.fetchBlob(r.context, m.Config.Size, m.Config.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer body.Close()
|
|
|
|
r.config, err = io.ReadAll(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.config, nil
|
|
}
|
|
|
|
// Descriptor retains the original descriptor from an index manifest.
|
|
// See partial.Descriptor.
|
|
func (r *remoteImage) Descriptor() (*v1.Descriptor, error) {
|
|
// kind of a hack, but RawManifest does appropriate locking/memoization
|
|
// and makes sure r.descriptor is populated.
|
|
_, err := r.RawManifest()
|
|
return r.descriptor, err
|
|
}
|
|
|
|
// remoteImageLayer implements partial.CompressedLayer
|
|
type remoteImageLayer struct {
|
|
ri *remoteImage
|
|
digest v1.Hash
|
|
}
|
|
|
|
// Digest implements partial.CompressedLayer
|
|
func (rl *remoteImageLayer) Digest() (v1.Hash, error) {
|
|
return rl.digest, nil
|
|
}
|
|
|
|
// Compressed implements partial.CompressedLayer
|
|
func (rl *remoteImageLayer) Compressed() (io.ReadCloser, error) {
|
|
urls := []url.URL{rl.ri.url("blobs", rl.digest.String())}
|
|
|
|
// Add alternative layer sources from URLs (usually none).
|
|
d, err := partial.BlobDescriptor(rl, rl.digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if d.Data != nil {
|
|
return verify.ReadCloser(io.NopCloser(bytes.NewReader(d.Data)), d.Size, d.Digest)
|
|
}
|
|
|
|
// We don't want to log binary layers -- this can break terminals.
|
|
ctx := redact.NewContext(rl.ri.context, "omitting binary blobs from logs")
|
|
|
|
for _, s := range d.URLs {
|
|
u, err := url.Parse(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
urls = append(urls, *u)
|
|
}
|
|
|
|
// The lastErr for most pulls will be the same (the first error), but for
|
|
// foreign layers we'll want to surface the last one, since we try to pull
|
|
// from the registry first, which would often fail.
|
|
// TODO: Maybe we don't want to try pulling from the registry first?
|
|
var lastErr error
|
|
for _, u := range urls {
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := rl.ri.Client.Do(req.WithContext(ctx))
|
|
if err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
|
|
if err := transport.CheckError(resp, http.StatusOK); err != nil {
|
|
resp.Body.Close()
|
|
lastErr = err
|
|
continue
|
|
}
|
|
|
|
return verify.ReadCloser(resp.Body, d.Size, rl.digest)
|
|
}
|
|
|
|
return nil, lastErr
|
|
}
|
|
|
|
// Manifest implements partial.WithManifest so that we can use partial.BlobSize below.
|
|
func (rl *remoteImageLayer) Manifest() (*v1.Manifest, error) {
|
|
return partial.Manifest(rl.ri)
|
|
}
|
|
|
|
// MediaType implements v1.Layer
|
|
func (rl *remoteImageLayer) MediaType() (types.MediaType, error) {
|
|
bd, err := partial.BlobDescriptor(rl, rl.digest)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return bd.MediaType, nil
|
|
}
|
|
|
|
// Size implements partial.CompressedLayer
|
|
func (rl *remoteImageLayer) Size() (int64, error) {
|
|
// Look up the size of this digest in the manifest to avoid a request.
|
|
return partial.BlobSize(rl, rl.digest)
|
|
}
|
|
|
|
// ConfigFile implements partial.WithManifestAndConfigFile so that we can use partial.BlobToDiffID below.
|
|
func (rl *remoteImageLayer) ConfigFile() (*v1.ConfigFile, error) {
|
|
return partial.ConfigFile(rl.ri)
|
|
}
|
|
|
|
// DiffID implements partial.WithDiffID so that we don't recompute a DiffID that we already have
|
|
// available in our ConfigFile.
|
|
func (rl *remoteImageLayer) DiffID() (v1.Hash, error) {
|
|
return partial.BlobToDiffID(rl, rl.digest)
|
|
}
|
|
|
|
// Descriptor retains the original descriptor from an image manifest.
|
|
// See partial.Descriptor.
|
|
func (rl *remoteImageLayer) Descriptor() (*v1.Descriptor, error) {
|
|
return partial.BlobDescriptor(rl, rl.digest)
|
|
}
|
|
|
|
// See partial.Exists.
|
|
func (rl *remoteImageLayer) Exists() (bool, error) {
|
|
return rl.ri.blobExists(rl.digest)
|
|
}
|
|
|
|
// LayerByDigest implements partial.CompressedLayer
|
|
func (r *remoteImage) LayerByDigest(h v1.Hash) (partial.CompressedLayer, error) {
|
|
return &remoteImageLayer{
|
|
ri: r,
|
|
digest: h,
|
|
}, nil
|
|
}
|
|
|