github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/build/buildimage/differ.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 buildimage
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io/fs"
    21  	"os"
    22  	"path/filepath"
    23  	"strings"
    24  
    25  	reference2 "github.com/docker/distribution/reference"
    26  	"github.com/pkg/errors"
    27  	"golang.org/x/sync/errgroup"
    28  
    29  	"github.com/sealerio/sealer/build/layerutils/charts"
    30  	manifest "github.com/sealerio/sealer/build/layerutils/manifests"
    31  	"github.com/sealerio/sealer/common"
    32  	"github.com/sealerio/sealer/pkg/define/application"
    33  	v12 "github.com/sealerio/sealer/pkg/define/image/v1"
    34  	"github.com/sealerio/sealer/pkg/image/save"
    35  	"github.com/sealerio/sealer/pkg/rootfs"
    36  	v1 "github.com/sealerio/sealer/types/api/v1"
    37  	osi "github.com/sealerio/sealer/utils/os"
    38  )
    39  
    40  // TODO: update the variable name
    41  var (
    42  	copyToManifests   = "manifests"
    43  	copyToChart       = "charts"
    44  	copyToImageList   = "imageList"
    45  	copyToApplication = "application"
    46  )
    47  
    48  type parseContainerImageStringSliceFunc func(srcPath string) ([]string, error)
    49  type parseContainerImageListFunc func(srcPath string) ([]*v12.ContainerImage, error)
    50  
    51  var parseContainerImageStringSliceFuncMap = map[string]func(srcPath string) ([]string, error){
    52  	copyToManifests:   parseYamlImages,
    53  	copyToChart:       parseChartImages,
    54  	copyToImageList:   parseRawImageList,
    55  	copyToApplication: WrapParseContainerImageList2StringSlice(parseApplicationImages),
    56  }
    57  
    58  var parseContainerImageListFuncMap = map[string]func(srcPath string) ([]*v12.ContainerImage, error){
    59  	copyToManifests:   WrapParseStringSlice2ContainerImageList(parseYamlImages),
    60  	copyToChart:       WrapParseStringSlice2ContainerImageList(parseChartImages),
    61  	copyToImageList:   WrapParseStringSlice2ContainerImageList(parseRawImageList),
    62  	copyToApplication: parseApplicationImages,
    63  }
    64  
    65  type Registry struct {
    66  	platform v1.Platform
    67  	puller   save.ImageSave
    68  }
    69  
    70  func NewRegistry(platform v1.Platform) *Registry {
    71  	ctx := context.Background()
    72  	return &Registry{
    73  		platform: platform,
    74  		puller:   save.NewImageSaver(ctx),
    75  	}
    76  }
    77  
    78  func (r *Registry) SaveImages(rootfs string, containerImages []string) error {
    79  	return r.puller.SaveImages(containerImages, filepath.Join(rootfs, common.RegistryDirName), r.platform)
    80  }
    81  
    82  func WrapParseStringSlice2ContainerImageList(parseFunc parseContainerImageStringSliceFunc) func(srcPath string) ([]*v12.ContainerImage, error) {
    83  	return func(srcPath string) ([]*v12.ContainerImage, error) {
    84  		images, err := parseFunc(srcPath)
    85  		if err != nil {
    86  			return nil, err
    87  		}
    88  		var containerImageList []*v12.ContainerImage
    89  		for _, image := range images {
    90  			containerImageList = append(containerImageList, &v12.ContainerImage{
    91  				Image:   image,
    92  				AppName: "",
    93  			})
    94  		}
    95  		return containerImageList, nil
    96  	}
    97  }
    98  
    99  func WrapParseContainerImageList2StringSlice(parseFunc parseContainerImageListFunc) func(srcPath string) ([]string, error) {
   100  	return func(srcPath string) ([]string, error) {
   101  		containerImageList, err := parseFunc(srcPath)
   102  		if err != nil {
   103  			return nil, err
   104  		}
   105  		return v12.GetImageSliceFromContainerImageList(containerImageList), nil
   106  	}
   107  }
   108  
   109  func ParseContainerImageList(srcPath string) ([]*v12.ContainerImage, error) {
   110  	eg, _ := errgroup.WithContext(context.Background())
   111  
   112  	var containerImageList []*v12.ContainerImage
   113  	for t, p := range parseContainerImageListFuncMap {
   114  		dispatchType := t
   115  		parse := p
   116  		eg.Go(func() error {
   117  			parsedImageList, err := parse(srcPath)
   118  			if err != nil {
   119  				return fmt.Errorf("failed to parse images from %s: %v", dispatchType, err)
   120  			}
   121  			for _, image := range parsedImageList {
   122  				img, err := reference2.ParseNormalizedNamed(image.Image)
   123  				if err != nil {
   124  					continue
   125  				}
   126  				containerImageList = append(containerImageList, &v12.ContainerImage{
   127  					Image:    img.String(),
   128  					AppName:  image.AppName,
   129  					Platform: image.Platform,
   130  				})
   131  			}
   132  			return nil
   133  		})
   134  	}
   135  	if err := eg.Wait(); err != nil {
   136  		return nil, err
   137  	}
   138  	return containerImageList, nil
   139  }
   140  
   141  func ParseContainerImageSlice(srcPath string) ([]string, error) {
   142  	eg, _ := errgroup.WithContext(context.Background())
   143  
   144  	var images []string
   145  	for t, p := range parseContainerImageStringSliceFuncMap {
   146  		dispatchType := t
   147  		parse := p
   148  		eg.Go(func() error {
   149  			ima, err := parse(srcPath)
   150  			if err != nil {
   151  				return fmt.Errorf("failed to parse images from %s: %v", dispatchType, err)
   152  			}
   153  			images = append(images, ima...)
   154  			return nil
   155  		})
   156  	}
   157  	if err := eg.Wait(); err != nil {
   158  		return nil, err
   159  	}
   160  	return images, nil
   161  }
   162  
   163  func parseApplicationImages(srcPath string) ([]*v12.ContainerImage, error) {
   164  	applicationPath := filepath.Clean(filepath.Join(srcPath, rootfs.GlobalManager.App().Root()))
   165  
   166  	if !osi.IsFileExist(applicationPath) {
   167  		return nil, nil
   168  	}
   169  
   170  	var (
   171  		containerImageList []*v12.ContainerImage
   172  		err                error
   173  	)
   174  
   175  	entries, err := os.ReadDir(applicationPath)
   176  	if err != nil {
   177  		return nil, errors.Wrap(err, "error in readdir in parseApplicationImages")
   178  	}
   179  	for _, entry := range entries {
   180  		name := entry.Name()
   181  		appPath := filepath.Join(applicationPath, name)
   182  		if entry.IsDir() {
   183  			if !isChartArtifactEnough(appPath) {
   184  				imagesTmp, err := parseApplicationKubeImages(appPath)
   185  				if err != nil {
   186  					return nil, errors.Wrapf(err, "failed to parse container image list for app(%s) with type(%s)",
   187  						name, application.KubeApp)
   188  				}
   189  				for _, image := range imagesTmp {
   190  					containerImageList = append(containerImageList, &v12.ContainerImage{
   191  						Image:   image,
   192  						AppName: name,
   193  					})
   194  				}
   195  				continue
   196  			}
   197  
   198  			imagesTmp, err := parseApplicationHelmImages(appPath)
   199  			if err != nil {
   200  				return nil, errors.Wrapf(err, "failed to parse container image list for app(%s) with type(%s)",
   201  					name, application.HelmApp)
   202  			}
   203  			for _, image := range imagesTmp {
   204  				containerImageList = append(containerImageList, &v12.ContainerImage{
   205  					Image:   image,
   206  					AppName: name,
   207  				})
   208  			}
   209  		}
   210  	}
   211  
   212  	return containerImageList, nil
   213  }
   214  
   215  func parseApplicationHelmImages(helmPath string) ([]string, error) {
   216  	if !osi.IsFileExist(helmPath) {
   217  		return nil, nil
   218  	}
   219  
   220  	var images []string
   221  
   222  	imageSearcher, err := charts.NewCharts()
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	err = filepath.Walk(helmPath, func(path string, f fs.FileInfo, err error) error {
   228  		if err != nil {
   229  			return err
   230  		}
   231  
   232  		if !f.IsDir() {
   233  			return nil
   234  		}
   235  
   236  		if isChartArtifactEnough(path) {
   237  			imgs, err := imageSearcher.ListImages(path)
   238  			if err != nil {
   239  				return err
   240  			}
   241  
   242  			images = append(images, imgs...)
   243  			return filepath.SkipDir
   244  		}
   245  		return nil
   246  	})
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  	return FormatImages(images), nil
   251  }
   252  
   253  func parseApplicationKubeImages(kubePath string) ([]string, error) {
   254  	if !osi.IsFileExist(kubePath) {
   255  		return nil, nil
   256  	}
   257  	var images []string
   258  	imageSearcher, err := manifest.NewManifests()
   259  	if err != nil {
   260  		return nil, err
   261  	}
   262  
   263  	err = filepath.Walk(kubePath, func(path string, f fs.FileInfo, err error) error {
   264  		if err != nil {
   265  			return err
   266  		}
   267  		if f.IsDir() {
   268  			return nil
   269  		}
   270  
   271  		ext := strings.ToLower(filepath.Ext(f.Name()))
   272  		if ext != ".yaml" && ext != ".yml" && ext != ".tmpl" {
   273  			return nil
   274  		}
   275  
   276  		ima, err := imageSearcher.ListImages(path)
   277  
   278  		if err != nil {
   279  			return err
   280  		}
   281  		images = append(images, ima...)
   282  		return nil
   283  	})
   284  
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  	return FormatImages(images), nil
   289  }
   290  
   291  func parseChartImages(srcPath string) ([]string, error) {
   292  	chartsPath := filepath.Join(srcPath, copyToChart)
   293  	if !osi.IsFileExist(chartsPath) {
   294  		return nil, nil
   295  	}
   296  
   297  	var images []string
   298  	imageSearcher, err := charts.NewCharts()
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  
   303  	err = filepath.Walk(chartsPath, func(path string, f fs.FileInfo, err error) error {
   304  		if err != nil {
   305  			return err
   306  		}
   307  
   308  		if !f.IsDir() {
   309  			return nil
   310  		}
   311  
   312  		if isChartArtifactEnough(path) {
   313  			ima, err := imageSearcher.ListImages(path)
   314  			if err != nil {
   315  				return err
   316  			}
   317  			images = append(images, ima...)
   318  			return filepath.SkipDir
   319  		}
   320  		return nil
   321  	})
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	return FormatImages(images), nil
   326  }
   327  
   328  func parseYamlImages(srcPath string) ([]string, error) {
   329  	manifestsPath := filepath.Join(srcPath, copyToManifests)
   330  	if !osi.IsFileExist(manifestsPath) {
   331  		return nil, nil
   332  	}
   333  	var images []string
   334  
   335  	imageSearcher, err := manifest.NewManifests()
   336  	if err != nil {
   337  		return nil, err
   338  	}
   339  
   340  	err = filepath.Walk(manifestsPath, func(path string, f fs.FileInfo, err error) error {
   341  		if err != nil {
   342  			return err
   343  		}
   344  		if f.IsDir() {
   345  			return nil
   346  		}
   347  
   348  		ext := strings.ToLower(filepath.Ext(f.Name()))
   349  		if ext != ".yaml" && ext != ".yml" && ext != ".tmpl" {
   350  			return nil
   351  		}
   352  
   353  		ima, err := imageSearcher.ListImages(path)
   354  
   355  		if err != nil {
   356  			return err
   357  		}
   358  		images = append(images, ima...)
   359  		return nil
   360  	})
   361  
   362  	if err != nil {
   363  		return nil, err
   364  	}
   365  	return FormatImages(images), nil
   366  }
   367  
   368  func parseRawImageList(srcPath string) ([]string, error) {
   369  	imageListFilePath := filepath.Join(srcPath, copyToManifests, copyToImageList)
   370  	if !osi.IsFileExist(imageListFilePath) {
   371  		return nil, nil
   372  	}
   373  
   374  	images, err := osi.NewFileReader(imageListFilePath).ReadLines()
   375  	if err != nil {
   376  		return nil, fmt.Errorf("failed to read file content %s:%v", imageListFilePath, err)
   377  	}
   378  	return FormatImages(images), nil
   379  }
   380  
   381  var isChartArtifactEnough = func(path string) bool {
   382  	return osi.IsFileExist(filepath.Join(path, "Chart.yaml")) &&
   383  		osi.IsFileExist(filepath.Join(path, "values.yaml")) &&
   384  		osi.IsFileExist(filepath.Join(path, "templates"))
   385  }