github.com/alibaba/sealer@v0.8.6-0.20220430115802-37a2bdaa8173/pkg/image/merge.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  
    21  	"github.com/alibaba/sealer/pkg/image/reference"
    22  
    23  	"github.com/alibaba/sealer/utils"
    24  
    25  	"github.com/alibaba/sealer/common"
    26  	"github.com/opencontainers/go-digest"
    27  	"golang.org/x/sync/errgroup"
    28  	"sigs.k8s.io/yaml"
    29  
    30  	"github.com/alibaba/sealer/pkg/image/store"
    31  	v1 "github.com/alibaba/sealer/types/api/v1"
    32  )
    33  
    34  func save(imageName string, image *v1.Image) error {
    35  	var (
    36  		imageBytes []byte
    37  		imageStore store.ImageStore
    38  		err        error
    39  	)
    40  	named, err := reference.ParseToNamed(imageName)
    41  	if err != nil {
    42  		return err
    43  	}
    44  	image.Name = named.CompleteName()
    45  	imageStore, err = store.NewDefaultImageStore()
    46  	if err != nil {
    47  		return err
    48  	}
    49  
    50  	imageBytes, err = yaml.Marshal(image)
    51  	if err != nil {
    52  		return err
    53  	}
    54  	imageID := digest.FromBytes(imageBytes).Hex()
    55  	image.Spec.ID = imageID
    56  	err = setClusterFile(named.CompleteName(), image)
    57  	if err != nil {
    58  		return err
    59  	}
    60  	return imageStore.Save(*image)
    61  }
    62  
    63  func Merge(imageName string, images []string, platform *v1.Platform) error {
    64  	if imageName == "" {
    65  		return fmt.Errorf("target image name should not be nil")
    66  	}
    67  	var (
    68  		err        error
    69  		newIma     *v1.Image
    70  		imageStore store.ImageStore
    71  	)
    72  	imageStore, err = store.NewDefaultImageStore()
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	d := DefaultImageService{imageStore: imageStore}
    78  	eg, _ := errgroup.WithContext(context.Background())
    79  
    80  	for _, ima := range images {
    81  		im := ima
    82  		plats := []*v1.Platform{platform}
    83  		eg.Go(func() error {
    84  			err = d.PullIfNotExist(im, plats)
    85  			if err != nil {
    86  				return err
    87  			}
    88  			return nil
    89  		})
    90  	}
    91  	if err := eg.Wait(); err != nil {
    92  		return err
    93  	}
    94  
    95  	for i, v := range images {
    96  		img, err := d.GetImageByName(v, platform)
    97  		if err != nil {
    98  			return err
    99  		}
   100  		if i == 0 {
   101  			newIma = img
   102  			continue
   103  		} else {
   104  			newIma, err = merge(newIma, img)
   105  			if err != nil {
   106  				return err
   107  			}
   108  		}
   109  	}
   110  	return save(imageName, newIma)
   111  }
   112  
   113  func merge(base, ima *v1.Image) (*v1.Image, error) {
   114  	if base == nil || ima == nil {
   115  		return nil, fmt.Errorf(" merge base or new can not be nil")
   116  	}
   117  	// merge image platform
   118  	if base.Spec.Platform.OS != ima.Spec.Platform.OS ||
   119  		base.Spec.Platform.Architecture != ima.Spec.Platform.Architecture ||
   120  		base.Spec.Platform.Variant != ima.Spec.Platform.Variant {
   121  		return nil, fmt.Errorf("can not merge different platform")
   122  	}
   123  
   124  	var (
   125  		isApp = base.Spec.ImageConfig.ImageType == common.AppImage &&
   126  			ima.Spec.ImageConfig.ImageType == common.AppImage
   127  	)
   128  	// merge image type;only if two image is application image we can determine this new image is application image.
   129  	if isApp {
   130  		base.Spec.ImageConfig.ImageType = common.AppImage
   131  	}
   132  
   133  	// merge image config arg and remove duplicate value
   134  	base.Spec.ImageConfig.Args = mergeImageArg(base.Spec.ImageConfig.Args, ima.Spec.ImageConfig.Args, isApp)
   135  	// merge image config cmd and remove duplicate value
   136  	base.Spec.ImageConfig.Cmd = mergeImageCmd(base.Spec.ImageConfig.Cmd, ima.Spec.ImageConfig.Cmd, isApp)
   137  
   138  	// merge image layer
   139  	res := append(base.Spec.Layers, ima.Spec.Layers...)
   140  	base.Spec.Layers = removeDuplicateLayers(res)
   141  	return base, nil
   142  }
   143  
   144  func mergeImageCmd(base, ima v1.ImageCmd, isApp bool) v1.ImageCmd {
   145  	current := utils.MergeSlice(base.Current, ima.Current)
   146  	if isApp {
   147  		return v1.ImageCmd{
   148  			Current: current,
   149  		}
   150  	}
   151  	return v1.ImageCmd{
   152  		Current: current,
   153  		Parent:  utils.MergeSlice(base.Parent, ima.Parent),
   154  	}
   155  }
   156  
   157  func mergeImageArg(base, ima v1.ImageArg, isApp bool) v1.ImageArg {
   158  	for k, v := range ima.Current {
   159  		base.Current[k] = v
   160  	}
   161  
   162  	if isApp {
   163  		return v1.ImageArg{
   164  			Current: base.Current,
   165  		}
   166  	}
   167  
   168  	for k, v := range ima.Parent {
   169  		base.Parent[k] = v
   170  	}
   171  
   172  	return v1.ImageArg{
   173  		Parent:  base.Parent,
   174  		Current: base.Current,
   175  	}
   176  }
   177  
   178  func removeDuplicateLayers(list []v1.Layer) []v1.Layer {
   179  	var result []v1.Layer
   180  	flagMap := map[string]struct{}{}
   181  	for _, v := range list {
   182  		// if id is not nil,remove duplicate id,this covers run and copy instruction.
   183  		if v.ID.String() != "" {
   184  			if _, ok := flagMap[v.ID.String()]; !ok {
   185  				flagMap[v.ID.String()] = struct{}{}
   186  				result = append(result, v)
   187  			}
   188  		}
   189  	}
   190  	return result
   191  }