github.com/alibaba/sealer@v0.8.6-0.20220430115802-37a2bdaa8173/pkg/image/default_image.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 image
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"os"
    22  	"path/filepath"
    23  
    24  	"github.com/distribution/distribution/v3"
    25  	"github.com/distribution/distribution/v3/manifest/schema2"
    26  	dockerstreams "github.com/docker/cli/cli/streams"
    27  	"github.com/docker/docker/api/types"
    28  	dockerioutils "github.com/docker/docker/pkg/ioutils"
    29  	dockerjsonmessage "github.com/docker/docker/pkg/jsonmessage"
    30  	dockerprogress "github.com/docker/docker/pkg/progress"
    31  	"github.com/docker/docker/pkg/streamformatter"
    32  
    33  	"github.com/alibaba/sealer/common"
    34  	"github.com/alibaba/sealer/logger"
    35  	"github.com/alibaba/sealer/pkg/image/distributionutil"
    36  	"github.com/alibaba/sealer/pkg/image/reference"
    37  	"github.com/alibaba/sealer/pkg/image/store"
    38  	v1 "github.com/alibaba/sealer/types/api/v1"
    39  	"github.com/alibaba/sealer/utils"
    40  )
    41  
    42  // DefaultImageService is the default service, which is used for image pull/push
    43  type DefaultImageService struct {
    44  	imageStore store.ImageStore
    45  }
    46  
    47  // PullIfNotExist is used to pull image if not exists locally
    48  func (d DefaultImageService) PullIfNotExist(imageName string, platforms []*v1.Platform) error {
    49  	var plats []*v1.Platform
    50  	for _, plat := range platforms {
    51  		img, err := d.GetImageByName(imageName, plat)
    52  
    53  		if err != nil {
    54  			return err
    55  		}
    56  		// image not found
    57  		if img == nil {
    58  			plats = append(plats, plat)
    59  		}
    60  	}
    61  
    62  	if len(plats) != 0 {
    63  		return d.Pull(imageName, plats)
    64  	}
    65  
    66  	return nil
    67  }
    68  
    69  func (d DefaultImageService) GetImageByName(imageName string, platform *v1.Platform) (*v1.Image, error) {
    70  	var img *v1.Image
    71  	img, err := d.imageStore.GetByName(imageName, platform)
    72  	if err == nil {
    73  		logger.Debug("image %s already exists", imageName)
    74  		return img, nil
    75  	}
    76  	return nil, nil
    77  }
    78  
    79  // Pull always do pull action
    80  func (d DefaultImageService) Pull(imageName string, platforms []*v1.Platform) error {
    81  	named, err := reference.ParseToNamed(imageName)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	var (
    86  		reader, writer  = io.Pipe()
    87  		writeFlusher    = dockerioutils.NewWriteFlusher(writer)
    88  		progressChanOut = streamformatter.NewJSONProgressOutput(writeFlusher, false)
    89  		streamOut       = dockerstreams.NewOut(common.StdOut)
    90  		ctx             = context.Background()
    91  	)
    92  	defer func() {
    93  		_ = reader.Close()
    94  		_ = writer.Close()
    95  		_ = writeFlusher.Close()
    96  	}()
    97  
    98  	layerStore, err := store.NewDefaultLayerStore()
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	repo, err := distributionutil.NewV2Repository(named, "pull")
   104  	if err != nil {
   105  		return err
   106  	}
   107  
   108  	manifest, err := repo.Manifests(ctx, make([]distribution.ManifestServiceOption, 0)...)
   109  	if err != nil {
   110  		return fmt.Errorf("get manifest service error: %v", err)
   111  	}
   112  	desc, err := repo.Tags(ctx).Get(ctx, named.Tag())
   113  	if err != nil {
   114  		return fmt.Errorf("get %s tag descriptor error: %v, try \"docker login\" if you are using a private registry", named.Repo(), err)
   115  	}
   116  
   117  	puller, err := distributionutil.NewPuller(repo, distributionutil.Config{
   118  		LayerStore:     layerStore,
   119  		ProgressOutput: progressChanOut,
   120  	})
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	go func() {
   126  		err := dockerjsonmessage.DisplayJSONMessagesToStream(reader, streamOut, nil)
   127  		if err != nil && err != io.ErrClosedPipe {
   128  			logger.Warn("error occurs in display progressing, err: %s", err)
   129  		}
   130  	}()
   131  
   132  	dockerprogress.Message(progressChanOut, "", fmt.Sprintf("Start to Pull Image %s", named.Raw()))
   133  	maniList, err := manifest.Get(ctx, desc.Digest, make([]distribution.ManifestServiceOption, 0)...)
   134  	if err != nil {
   135  		return fmt.Errorf("get image manifest error: %v", err)
   136  	}
   137  	_, p, err := maniList.Payload()
   138  	if err != nil {
   139  		return fmt.Errorf("failed to get image manifest list payload: %v", err)
   140  	}
   141  	for _, plat := range platforms {
   142  		m, err := d.handleManifest(ctx, manifest, p, *plat)
   143  		if err != nil {
   144  			return fmt.Errorf("get digest error: %v", err)
   145  		}
   146  
   147  		image, err := puller.Pull(ctx, named, m)
   148  		if err != nil {
   149  			return err
   150  		}
   151  
   152  		err = d.imageStore.Save(*image)
   153  		if err != nil {
   154  			return err
   155  		}
   156  	}
   157  	dockerprogress.Message(progressChanOut, "", fmt.Sprintf("Success to Pull Image %s", named.Raw()))
   158  	return nil
   159  }
   160  
   161  func (d DefaultImageService) handleManifest(ctx context.Context, manifest distribution.ManifestService, payload []byte, platform v1.Platform) (schema2.Manifest, error) {
   162  	dgest, err := distributionutil.GetImageManifestDigest(payload, platform)
   163  	if err != nil {
   164  		return schema2.Manifest{}, fmt.Errorf("get digest from manifest list error: %v", err)
   165  	}
   166  
   167  	m, err := manifest.Get(ctx, dgest, make([]distribution.ManifestServiceOption, 0)...)
   168  	if err != nil {
   169  		return schema2.Manifest{}, fmt.Errorf("get image manifest error: %v", err)
   170  	}
   171  
   172  	_, ok := m.(*schema2.DeserializedManifest)
   173  	if !ok {
   174  		return schema2.Manifest{}, fmt.Errorf("failed to parse manifest to DeserializedManifest")
   175  	}
   176  	return m.(*schema2.DeserializedManifest).Manifest, nil
   177  }
   178  
   179  // Push local image to remote registry
   180  func (d DefaultImageService) Push(imageName string) error {
   181  	named, err := reference.ParseToNamed(imageName)
   182  	if err != nil {
   183  		return err
   184  	}
   185  	var (
   186  		reader, writer  = io.Pipe()
   187  		writeFlusher    = dockerioutils.NewWriteFlusher(writer)
   188  		progressChanOut = streamformatter.NewJSONProgressOutput(writeFlusher, false)
   189  		streamOut       = dockerstreams.NewOut(common.StdOut)
   190  	)
   191  	defer func() {
   192  		_ = reader.Close()
   193  		_ = writer.Close()
   194  		_ = writeFlusher.Close()
   195  	}()
   196  
   197  	layerStore, err := store.NewDefaultLayerStore()
   198  	if err != nil {
   199  		return err
   200  	}
   201  
   202  	pusher, err := distributionutil.NewPusher(named,
   203  		distributionutil.Config{
   204  			LayerStore:     layerStore,
   205  			ProgressOutput: progressChanOut,
   206  		})
   207  	if err != nil {
   208  		return err
   209  	}
   210  	go func() {
   211  		err := dockerjsonmessage.DisplayJSONMessagesToStream(reader, streamOut, nil)
   212  		// reader may be closed in another goroutine
   213  		// so do not log warn when err == io.ErrClosedPipe
   214  		if err != nil && err != io.ErrClosedPipe {
   215  			logger.Warn("error occurs in display progressing, err: %s", err)
   216  		}
   217  	}()
   218  
   219  	dockerprogress.Message(progressChanOut, "", fmt.Sprintf("Start to Push Image %s", named.Raw()))
   220  	err = pusher.Push(context.Background(), named)
   221  	if err == nil {
   222  		dockerprogress.Message(progressChanOut, "", fmt.Sprintf("Success to Push Image %s", named.CompleteName()))
   223  	}
   224  	return err
   225  }
   226  
   227  // Login into a registry, for saving auth info in ~/.docker/config.json
   228  func (d DefaultImageService) Login(RegistryURL, RegistryUsername, RegistryPasswd string) error {
   229  	err := distributionutil.Login(context.Background(), &types.AuthConfig{ServerAddress: RegistryURL, Username: RegistryUsername, Password: RegistryPasswd})
   230  	if err != nil {
   231  		return fmt.Errorf("failed to authenticate %s: %v", RegistryURL, err)
   232  	}
   233  	if err := utils.SetDockerConfig(RegistryURL, RegistryUsername, RegistryPasswd); err != nil {
   234  		return err
   235  	}
   236  	logger.Info("%s login %s success", RegistryUsername, RegistryURL)
   237  	return nil
   238  }
   239  
   240  // Delete image layer, image meta, and local registry by id or name.
   241  // if only image id,will delete related image data.
   242  // if image name,and not specify platform,will delete all image data.
   243  // if image name and  platforms is not nil will delete all the related platform images.
   244  func (d DefaultImageService) Delete(imageNameOrID string, platforms []*v1.Platform) error {
   245  	var (
   246  		err               error
   247  		imageStore        = d.imageStore
   248  		isImageID         bool
   249  		deleteImageIDList []string
   250  	)
   251  
   252  	imageMetadataMap, err := imageStore.GetImageMetadataMap()
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	// detect if the input is image id.
   258  	for _, manifestList := range imageMetadataMap {
   259  		for _, m := range manifestList.Manifests {
   260  			if m.ID == imageNameOrID {
   261  				isImageID = true
   262  				break
   263  			}
   264  		}
   265  	}
   266  
   267  	if isImageID {
   268  		// delete image by id
   269  		err = imageStore.DeleteByID(imageNameOrID)
   270  		if err != nil {
   271  			return err
   272  		}
   273  		deleteImageIDList = append(deleteImageIDList, imageNameOrID)
   274  	} else {
   275  		// delete image by name
   276  		if len(platforms) == 0 {
   277  			// delete all platforms
   278  			manifestList, err := imageStore.GetImageManifestList(imageNameOrID)
   279  			if err != nil {
   280  				return err
   281  			}
   282  			for _, m := range manifestList {
   283  				deleteImageIDList = append(deleteImageIDList, m.ID)
   284  			}
   285  			if err = imageStore.DeleteByName(imageNameOrID, nil); err != nil {
   286  				return fmt.Errorf("failed to delete image %s, err: %v", imageNameOrID, err)
   287  			}
   288  		} else {
   289  			// delete user specify platform
   290  			for _, plat := range platforms {
   291  				img, err := imageStore.GetByName(imageNameOrID, plat)
   292  				if err != nil {
   293  					return fmt.Errorf("image %s not found %v", imageNameOrID, err)
   294  				}
   295  
   296  				if err = imageStore.DeleteByName(imageNameOrID, plat); err != nil {
   297  					return fmt.Errorf("failed to delete image %s, err: %v", imageNameOrID, err)
   298  				}
   299  				deleteImageIDList = append(deleteImageIDList, img.Spec.ID)
   300  			}
   301  		}
   302  	}
   303  
   304  	// delete image.yaml file which id not in current imageMetadataMap.
   305  	for _, id := range utils.RemoveDuplicate(deleteImageIDList) {
   306  		err = store.DeleteImageLocal(id)
   307  		if err != nil {
   308  			return err
   309  		}
   310  	}
   311  
   312  	err = d.deleteLayers()
   313  	if err != nil {
   314  		return err
   315  	}
   316  	logger.Info("image %s delete success", imageNameOrID)
   317  	return nil
   318  }
   319  
   320  //delete the unused Layer in the `DefaultLayerDir` directory
   321  func (d DefaultImageService) deleteLayers() error {
   322  	var (
   323  		//save a path with desired name as value.
   324  		pruneMap = make(map[string][]string)
   325  	)
   326  
   327  	allImageLayerIDList, err := d.getAllLayers()
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	pruneMap[common.DefaultLayerDir] = allImageLayerIDList
   333  	pruneMap[filepath.Join(common.DefaultLayerDBRoot, "sha256")] = allImageLayerIDList
   334  
   335  	for root, desired := range pruneMap {
   336  		subset, err := utils.GetDirNameListInDir(root, utils.FilterOptions{
   337  			All:          true,
   338  			WithFullPath: false,
   339  		})
   340  		if err != nil {
   341  			return err
   342  		}
   343  
   344  		trash := utils.RemoveStrSlice(subset, desired)
   345  		for _, name := range trash {
   346  			if err := os.RemoveAll(filepath.Join(root, name)); err != nil {
   347  				return err
   348  			}
   349  			_, err := common.StdOut.WriteString(fmt.Sprintf("%s deleted\n", name))
   350  			if err != nil {
   351  				return err
   352  			}
   353  		}
   354  	}
   355  
   356  	return nil
   357  }
   358  
   359  // getAllLayers return current image id and layers
   360  func (d DefaultImageService) getAllLayers() ([]string, error) {
   361  	imageMetadataMap, err := d.imageStore.GetImageMetadataMap()
   362  	var allImageLayerDirs []string
   363  
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	for _, imageMetadata := range imageMetadataMap {
   369  		for _, m := range imageMetadata.Manifests {
   370  			image, err := d.imageStore.GetByID(m.ID)
   371  			if err != nil {
   372  				return nil, err
   373  			}
   374  			for _, layer := range image.Spec.Layers {
   375  				if layer.ID != "" {
   376  					allImageLayerDirs = append(allImageLayerDirs, layer.ID.Hex())
   377  				}
   378  			}
   379  		}
   380  	}
   381  	return utils.RemoveDuplicate(allImageLayerDirs), err
   382  }