github.com/alibaba/sealer@v0.8.6-0.20220430115802-37a2bdaa8173/pkg/image/distributionutil/pull.go (about)

     1  // Copyright © 2021 Alibaba Group Holding Ltd.
     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 distributionutil
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  
    24  	distribution "github.com/distribution/distribution/v3"
    25  	"github.com/distribution/distribution/v3/manifest/schema2"
    26  	"github.com/docker/docker/pkg/progress"
    27  	"github.com/opencontainers/go-digest"
    28  	"golang.org/x/sync/errgroup"
    29  
    30  	"github.com/alibaba/sealer/pkg/image/reference"
    31  	"github.com/alibaba/sealer/pkg/image/store"
    32  	v1 "github.com/alibaba/sealer/types/api/v1"
    33  	"github.com/alibaba/sealer/utils/archive"
    34  )
    35  
    36  type Puller interface {
    37  	Pull(ctx context.Context, named reference.Named, manifest schema2.Manifest) (*v1.Image, error)
    38  }
    39  
    40  type ImagePuller struct {
    41  	config     Config
    42  	repository distribution.Repository
    43  }
    44  
    45  func (puller *ImagePuller) Pull(ctx context.Context, named reference.Named, manifest schema2.Manifest) (*v1.Image, error) {
    46  	var (
    47  		layerStore = puller.config.LayerStore
    48  		layers     = []v1.Layer{}
    49  		eg         *errgroup.Group
    50  	)
    51  
    52  	v1Image, err := puller.getRemoteImageMetadata(ctx, manifest.Config.Digest)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	eg, _ = errgroup.WithContext(ctx)
    58  	for _, l := range v1Image.Spec.Layers {
    59  		if l.ID == "" {
    60  			continue
    61  		}
    62  		layers = append(layers, l)
    63  	}
    64  	// number of non-empty layer and layer in distribution should be equal
    65  	if len(layers) != len(manifest.Layers) {
    66  		return nil, fmt.Errorf("the number layerIDs %d and LayerDescriptor %d are mismatch", len(layers), len(manifest.Layers))
    67  	}
    68  
    69  	for i, l := range manifest.Layers {
    70  		// local value to current scope, safe to pass into goroutine
    71  		var (
    72  			descriptor = l
    73  			layer      = layers[i]
    74  		)
    75  		// we take hash of layer as real layer id,  hash of descriptor is just
    76  		// an identifier for remote data
    77  		eg.Go(func() error {
    78  			// roLayer now does not exist, new one
    79  			// descriptor.Size is temp size for this layer
    80  			// real size will be set within downloadLayer
    81  			roLayer, layerErr := store.NewROLayer(layer.ID,
    82  				descriptor.Size,
    83  				map[string]digest.Digest{named.Domain() + "/" + named.Repo(): descriptor.Digest})
    84  			if layerErr != nil {
    85  				return layerErr
    86  			}
    87  
    88  			layerErr = puller.downloadLayer(ctx, roLayer, descriptor)
    89  			if layerErr != nil {
    90  				return layerErr
    91  			}
    92  
    93  			return layerStore.RegisterLayerIfNotPresent(roLayer)
    94  		})
    95  	}
    96  	err = eg.Wait()
    97  	if err != nil {
    98  		return nil, fmt.Errorf("failed to pull image %s, err: %s", named.Raw(), err)
    99  	}
   100  
   101  	return &v1Image, nil
   102  }
   103  
   104  func (puller *ImagePuller) downloadLayer(ctx context.Context, layer store.Layer, descriptor distribution.Descriptor) error {
   105  	var (
   106  		layerStore  = puller.config.LayerStore
   107  		progressOut = puller.config.ProgressOutput
   108  		repo        = puller.repository
   109  	)
   110  	backend, err := store.NewFSStoreBackend()
   111  	if err != nil {
   112  		return err
   113  	}
   114  	// descriptor is remote layer data, but its hash may not be the layer id, so we
   115  	// use layer.ID(hash of layer from v1.Image) to check layer existence.
   116  	roLayer := layerStore.Get(layer.ID())
   117  	if roLayer != nil {
   118  		progress.Message(progressOut, layer.SimpleID(), "already exists")
   119  		return nil
   120  	}
   121  
   122  	bs := repo.Blobs(ctx)
   123  	layerReader, err := bs.Open(ctx, descriptor.Digest)
   124  	if err != nil {
   125  		return err
   126  	}
   127  	defer layerReader.Close()
   128  
   129  	digester := digest.Canonical.Digester()
   130  	layerDownloadReader := ioutil.NopCloser(io.TeeReader(layerReader, digester.Hash()))
   131  	progressReader := progress.NewProgressReader(layerDownloadReader, progressOut, descriptor.Size, layer.SimpleID(), "pulling")
   132  	size, err := archive.Decompress(progressReader, backend.LayerDataDir(layer.ID().ToDigest()), archive.Options{Compress: true})
   133  	if err != nil {
   134  		progress.Update(progressOut, layer.SimpleID(), err.Error())
   135  		return err
   136  	}
   137  	// update rolayer size for storing the info under layerdb
   138  	layer.SetSize(size)
   139  	if digester.Digest() != descriptor.Digest {
   140  		return fmt.Errorf("digest verified failed for %s", layer.ID())
   141  	}
   142  	progress.Update(progressOut, layer.SimpleID(), "pull completed")
   143  	return nil
   144  }
   145  
   146  // not docker image, get sealer image metadata
   147  func (puller *ImagePuller) getRemoteImageMetadata(context context.Context, digest digest.Digest) (v1.Image, error) {
   148  	repo := puller.repository
   149  	bs := repo.Blobs(context)
   150  	manifestImageBytes, err := bs.Get(context, digest)
   151  	if err != nil {
   152  		return v1.Image{}, err
   153  	}
   154  
   155  	img := v1.Image{}
   156  	return img, json.Unmarshal(manifestImageBytes, &img)
   157  }
   158  
   159  func NewPuller(repo distribution.Repository, config Config) (Puller, error) {
   160  	return &ImagePuller{
   161  		repository: repo,
   162  		config:     config,
   163  	}, nil
   164  }