github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/utils/container/buildinfo.go (about)

     1  package container
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net/http"
     7  	"os"
     8  	"path"
     9  	"strings"
    10  
    11  	artutils "github.com/jfrog/jfrog-cli-core/artifactory/utils"
    12  	"github.com/jfrog/jfrog-client-go/artifactory"
    13  	"github.com/jfrog/jfrog-client-go/artifactory/buildinfo"
    14  	"github.com/jfrog/jfrog-client-go/artifactory/services"
    15  	"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
    16  	"github.com/jfrog/jfrog-client-go/utils/errorutils"
    17  	"github.com/jfrog/jfrog-client-go/utils/io/content"
    18  	"github.com/jfrog/jfrog-client-go/utils/log"
    19  )
    20  
    21  const (
    22  	Pull                      CommandType = "pull"
    23  	Push                      CommandType = "push"
    24  	foreignLayerMediaType     string      = "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip"
    25  	imageNotFoundErrorMessage string      = "Could not find docker image in Artifactory, expecting image tag: %s"
    26  	markerLayerSuffix         string      = ".marker"
    27  )
    28  
    29  // Docker image build info builder.
    30  type Builder interface {
    31  	Build(module string) (*buildinfo.BuildInfo, error)
    32  	UpdateArtifactsAndDependencies() error
    33  	GetLayers() *[]utils.ResultItem
    34  }
    35  
    36  // Create instance of docker build info builder.
    37  func newBuildInfoBuilder(builder *buildInfoBuilder, image *Image, repository, buildName, buildNumber, project string, serviceManager artifactory.ArtifactoryServicesManager, commandType CommandType, containerManager ContainerManager) (err error) {
    38  	builder.repositoryDetails.key = repository
    39  	builder.repositoryDetails.isRemote, err = artutils.IsRemoteRepo(repository, serviceManager)
    40  	if err != nil {
    41  		return err
    42  	}
    43  	builder.image = image
    44  	builder.buildName = buildName
    45  	builder.buildNumber = buildNumber
    46  	builder.project = project
    47  	builder.serviceManager = serviceManager
    48  	builder.commandType = commandType
    49  	builder.containerManager = containerManager
    50  	return nil
    51  }
    52  
    53  func NewBuildInfoBuilder(image *Image, repository, buildName, buildNumber, project string, serviceManager artifactory.ArtifactoryServicesManager, commandType CommandType, containerManager ContainerManager) (Builder, error) {
    54  	builder := &buildInfoBuilder{}
    55  	var err error
    56  	if err = newBuildInfoBuilder(builder, image, repository, buildName, buildNumber, project, serviceManager, commandType, containerManager); err != nil {
    57  		return nil, err
    58  	}
    59  	builder.imageId, err = builder.containerManager.Id(builder.image)
    60  	return builder, err
    61  }
    62  
    63  func NewKanikoBuildInfoBuilder(image *Image, repository, buildName, buildNumber, project string, serviceManager artifactory.ArtifactoryServicesManager, commandType CommandType, containerManager ContainerManager, manifestSha256 string) (Builder, error) {
    64  	builder := &buildInfoBuilder{}
    65  	var err error
    66  	if err = newBuildInfoBuilder(builder, image, repository, buildName, buildNumber, project, serviceManager, commandType, containerManager); err != nil {
    67  		return nil, err
    68  	}
    69  	builder.manifestSha256 = manifestSha256
    70  	return builder, err
    71  }
    72  
    73  type buildInfoBuilder struct {
    74  	image             *Image
    75  	containerManager  ContainerManager
    76  	repositoryDetails RepositoryDetails
    77  	buildName         string
    78  	buildNumber       string
    79  	project           string
    80  	serviceManager    artifactory.ArtifactoryServicesManager
    81  
    82  	// internal fields
    83  	imageId        string
    84  	manifestSha256 string
    85  	layers         []utils.ResultItem
    86  	artifacts      []buildinfo.Artifact
    87  	dependencies   []buildinfo.Dependency
    88  	commandType    CommandType
    89  }
    90  
    91  type RepositoryDetails struct {
    92  	key      string
    93  	isRemote bool
    94  }
    95  
    96  func (builder *buildInfoBuilder) GetLayers() *[]utils.ResultItem {
    97  	return &builder.layers
    98  }
    99  
   100  // Create build info for a docker image.
   101  func (builder *buildInfoBuilder) Build(module string) (*buildinfo.BuildInfo, error) {
   102  	if err := builder.UpdateArtifactsAndDependencies(); err != nil {
   103  		log.Warn(`Failed to collect build-info, couldn't find image "` + builder.image.tag + `" in Artifactory`)
   104  		// Don't generate an empty build-info for build-docker-create if the image manifest was not found in Artifactory.
   105  		if builder.containerManager.GetContainerManagerType() == Kaniko {
   106  			return nil, err
   107  		} else {
   108  			log.Error("Failed populating the build-info module with docker artifacts and dependencies. Reason: " + err.Error())
   109  		}
   110  	}
   111  	// Set build properties only when pushing image.
   112  	if builder.commandType == Push {
   113  		if _, err := builder.setBuildProperties(); err != nil {
   114  			return nil, err
   115  		}
   116  	}
   117  	return builder.createBuildInfo(module)
   118  }
   119  
   120  func (builder *buildInfoBuilder) getSearchableRepo() string {
   121  	if builder.repositoryDetails.isRemote {
   122  		return builder.repositoryDetails.key + "-cache"
   123  	}
   124  	return builder.repositoryDetails.key
   125  }
   126  
   127  // Search, validate and create image's artifacts and dependencies.
   128  func (builder *buildInfoBuilder) UpdateArtifactsAndDependencies() error {
   129  	// Search for image's manifest and layers.
   130  	manifestLayers, manifestContent, err := builder.getManifestAndLayersDetails()
   131  	if err != nil {
   132  		return err
   133  	}
   134  	log.Debug("Found manifest.json. Proceeding to collect build-info.")
   135  	// Manifest may hold 'empty layers'. As a result, promotion will fail to promote the same layer more than once.
   136  	manifestContent.Layers = removeDuplicateLayers(manifestContent.Layers)
   137  	manifestArtifact, manifestDependency := getManifestArtifact(manifestLayers), getManifestDependency(manifestLayers)
   138  	configLayer, configLayerArtifact, configLayerDependency, err := builder.getConfigLayer(manifestLayers)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	if builder.commandType == Push {
   143  		return builder.handlePush(manifestArtifact, configLayerArtifact, manifestContent, configLayer, manifestLayers)
   144  	}
   145  	return builder.handlePull(manifestDependency, configLayerDependency, manifestContent, manifestLayers)
   146  }
   147  
   148  // Return all the search patterns in which manifest can be found.
   149  func getManifestPaths(imagePath, repo string, commandType CommandType) []string {
   150  	// pattern 1: reverse proxy e.g. ecosysjfrog-docker-local.jfrog.io.
   151  	paths := []string{path.Join(repo, imagePath, "*")}
   152  	// pattern 2: proxy-less e.g. orgab.jfrog.team/docker-local.
   153  	endOfRepoNameIndex := strings.Index(imagePath[1:], "/")
   154  	proxylessTag := imagePath[endOfRepoNameIndex+1:]
   155  	paths = append(paths, path.Join(repo, proxylessTag, "*"))
   156  	// If image path includes more than 3 slashes, Artifactory doesn't store this image under 'library', thus we should not look further.
   157  	if commandType != Push && strings.Count(imagePath, "/") <= 3 {
   158  		// pattern 3: reverse proxy - this time with 'library' as part of the path.
   159  		paths = append(paths, path.Join(repo, "library", imagePath, "*"))
   160  		// pattern 4: Assume proxy-less - this time with 'library' as part of the path.
   161  		paths = append(paths, path.Join(repo, "library", proxylessTag, "*"))
   162  	}
   163  	return paths
   164  }
   165  
   166  // Search for image manifest and layers in Artifactory.
   167  func (builder *buildInfoBuilder) getManifestAndLayersDetails() (layers map[string]*utils.ResultItem, manifestContent *manifest, err error) {
   168  	imagePath, err := builder.image.Path()
   169  	if err != nil {
   170  		return nil, nil, err
   171  	}
   172  	manifestPathsCandidates := getManifestPaths(imagePath, builder.getSearchableRepo(), builder.commandType)
   173  	log.Debug("Start searching for image manifest.json")
   174  	for _, path := range manifestPathsCandidates {
   175  		log.Debug(`Searching in:"` + path + `"`)
   176  		layers, manifestContent, err = searchManifestAndLayersDetails(builder, path)
   177  		if err != nil || manifestContent != nil {
   178  			return layers, manifestContent, err
   179  		}
   180  	}
   181  	return nil, nil, errorutils.CheckError(errors.New(fmt.Sprintf(imageNotFoundErrorMessage, builder.image.tag)))
   182  }
   183  
   184  func (builder *buildInfoBuilder) handlePull(manifestDependency, configLayerDependency buildinfo.Dependency, imageManifest *manifest, searchResults map[string]*utils.ResultItem) error {
   185  	// Add dependencies.
   186  	builder.dependencies = append(builder.dependencies, manifestDependency)
   187  	builder.dependencies = append(builder.dependencies, configLayerDependency)
   188  	// Add image layers as dependencies.
   189  	for i := 0; i < len(imageManifest.Layers); i++ {
   190  		layerFileName := digestToLayer(imageManifest.Layers[i].Digest)
   191  		item, layerExists := searchResults[layerFileName]
   192  		if !layerExists {
   193  			err := builder.handleMissingLayer(imageManifest.Layers[i].MediaType, layerFileName)
   194  			if err != nil {
   195  				return err
   196  			}
   197  			continue
   198  		}
   199  		builder.dependencies = append(builder.dependencies, item.ToDependency())
   200  	}
   201  	return nil
   202  }
   203  
   204  func (builder *buildInfoBuilder) handlePush(manifestArtifact, configLayerArtifact buildinfo.Artifact, imageManifest *manifest, configurationLayer *configLayer, searchResults map[string]*utils.ResultItem) error {
   205  	// Add artifacts.
   206  	builder.artifacts = append(builder.artifacts, manifestArtifact)
   207  	builder.artifacts = append(builder.artifacts, configLayerArtifact)
   208  	// Add layers.
   209  	builder.layers = append(builder.layers, *searchResults["manifest.json"])
   210  	builder.layers = append(builder.layers, *searchResults[digestToLayer(builder.imageId)])
   211  	totalLayers := len(imageManifest.Layers)
   212  	totalDependencies := configurationLayer.getNumberOfDependentLayers()
   213  	// Add image layers as artifacts and dependencies.
   214  	for i := 0; i < totalLayers; i++ {
   215  		layerFileName := digestToLayer(imageManifest.Layers[i].Digest)
   216  		item, layerExists := searchResults[layerFileName]
   217  		if !layerExists {
   218  			err := builder.handleMissingLayer(imageManifest.Layers[i].MediaType, layerFileName)
   219  			if err != nil {
   220  				return err
   221  			}
   222  			continue
   223  		}
   224  		// Decide if the layer is also a dependency.
   225  		if i < totalDependencies {
   226  			builder.dependencies = append(builder.dependencies, item.ToDependency())
   227  		}
   228  		builder.artifacts = append(builder.artifacts, item.ToArtifact())
   229  		builder.layers = append(builder.layers, *item)
   230  	}
   231  	return nil
   232  }
   233  
   234  func (builder *buildInfoBuilder) handleMissingLayer(layerMediaType, layerFileName string) error {
   235  	// Allow missing layer to be of a foreign type.
   236  	if layerMediaType == foreignLayerMediaType {
   237  		log.Info(fmt.Sprintf("Foreign layer: %s is missing in Artifactory and therefore will not be added to the build-info.", layerFileName))
   238  		return nil
   239  	}
   240  	return errorutils.CheckError(errors.New("Could not find layer: " + layerFileName + " in Artifactory"))
   241  }
   242  
   243  // Set build properties on image layers in Artifactory.
   244  func (builder *buildInfoBuilder) setBuildProperties() (int, error) {
   245  	props, err := artutils.CreateBuildProperties(builder.buildName, builder.buildNumber, builder.project)
   246  	if err != nil {
   247  		return 0, err
   248  	}
   249  	pathToFile, err := writeLayersToFile(builder.layers)
   250  	if err != nil {
   251  		return 0, err
   252  	}
   253  	reader := content.NewContentReader(pathToFile, content.DefaultKey)
   254  	defer reader.Close()
   255  	return builder.serviceManager.SetProps(services.PropsParams{Reader: reader, Props: props})
   256  }
   257  
   258  // Download the content of layer search result.
   259  func (builder *buildInfoBuilder) downloadLayer(searchResult utils.ResultItem, result interface{}) error {
   260  	// Search results may include artifacts from the remote-cache repository.
   261  	// When artifact is expired, it cannot be downloaded from the remote-cache.
   262  	// To solve this, change back the search results' repository, to its origin remote/virtual.
   263  	searchResult.Repo = builder.repositoryDetails.key
   264  	path := searchResult.GetItemRelativePath()
   265  	return artutils.RemoteUnmarshal(builder.serviceManager, path, result)
   266  }
   267  
   268  func writeLayersToFile(layers []utils.ResultItem) (filePath string, err error) {
   269  	writer, err := content.NewContentWriter("results", true, false)
   270  	if err != nil {
   271  		return
   272  	}
   273  	defer func() {
   274  		err = writer.Close()
   275  	}()
   276  	for _, layer := range layers {
   277  		writer.Write(layer)
   278  	}
   279  	filePath = writer.GetFilePath()
   280  	return
   281  }
   282  
   283  // Create a docker build info.
   284  func (builder *buildInfoBuilder) createBuildInfo(module string) (*buildinfo.BuildInfo, error) {
   285  	imageProperties := map[string]string{}
   286  	imageProperties["docker.image.id"] = builder.imageId
   287  	imageProperties["docker.image.tag"] = builder.image.Tag()
   288  	if module == "" {
   289  		imageName, err := builder.image.Name()
   290  		if err != nil {
   291  			return nil, err
   292  		}
   293  		module = imageName
   294  	}
   295  	buildInfo := &buildinfo.BuildInfo{Modules: []buildinfo.Module{{
   296  		Id:           module,
   297  		Type:         buildinfo.Docker,
   298  		Properties:   imageProperties,
   299  		Artifacts:    builder.artifacts,
   300  		Dependencies: builder.dependencies,
   301  	}}}
   302  	return buildInfo, nil
   303  }
   304  
   305  // Return - manifest artifacts as buildinfo.Artifact struct.
   306  func getManifestArtifact(searchResults map[string]*utils.ResultItem) (artifact buildinfo.Artifact) {
   307  	item := searchResults["manifest.json"]
   308  	return buildinfo.Artifact{Name: "manifest.json", Type: "json", Checksum: buildinfo.Checksum{Sha1: item.Actual_Sha1, Md5: item.Actual_Md5}, Path: path.Join(item.Repo, item.Path, item.Name)}
   309  }
   310  
   311  // Return - manifest dependency as buildinfo.Dependency struct.
   312  func getManifestDependency(searchResults map[string]*utils.ResultItem) (dependency buildinfo.Dependency) {
   313  	item := searchResults["manifest.json"]
   314  	return buildinfo.Dependency{Id: "manifest.json", Type: "json", Checksum: buildinfo.Checksum{Sha1: item.Actual_Sha1, Md5: item.Actual_Md5}}
   315  }
   316  
   317  // Download and read the config layer from Artifactory.
   318  // Returned values:
   319  // configurationLayer - pointer to the configuration layer struct, retrieved from Artifactory.
   320  // artifact - configuration layer as buildinfo.Artifact struct.
   321  // dependency - configuration layer as buildinfo.Dependency struct.
   322  func (builder *buildInfoBuilder) getConfigLayer(searchResults map[string]*utils.ResultItem) (configurationLayer *configLayer, artifact buildinfo.Artifact, dependency buildinfo.Dependency, err error) {
   323  	item := searchResults[digestToLayer(builder.imageId)]
   324  	configurationLayer = new(configLayer)
   325  	if err := builder.downloadLayer(*item, &configurationLayer); err != nil {
   326  		return nil, buildinfo.Artifact{}, buildinfo.Dependency{}, err
   327  	}
   328  	artifact = buildinfo.Artifact{Name: digestToLayer(builder.imageId), Checksum: buildinfo.Checksum{Sha1: item.Actual_Sha1, Md5: item.Actual_Md5}, Path: path.Join(item.Repo, item.Path, item.Name)}
   329  	dependency = buildinfo.Dependency{Id: digestToLayer(builder.imageId), Checksum: buildinfo.Checksum{Sha1: item.Actual_Sha1, Md5: item.Actual_Md5}}
   330  	return
   331  }
   332  
   333  // Search for manifest in Artifactory, If not found, returns 'manifestContent' as nil.
   334  func searchManifestAndLayersDetails(builder *buildInfoBuilder, imagePathPattern string) (resultMap map[string]*utils.ResultItem, manifestContent *manifest, err error) {
   335  	resultMap, err = searchHandler(imagePathPattern, builder)
   336  	if err != nil || len(resultMap) == 0 {
   337  		log.Debug("Couldn't find manifest.json. Image path pattern: ", imagePathPattern, ".")
   338  		return
   339  	}
   340  	// Check if search results contain manifest.json
   341  	searchesult, ok := resultMap["manifest.json"]
   342  	if ok {
   343  		// Found a manifest. Verify manifest is the same as the builder image.
   344  		if builder.containerManager.GetContainerManagerType() == Kaniko {
   345  			manifestContent, err = verifyManifestBySha256(*searchesult, builder)
   346  		} else {
   347  			manifestContent, err = verifyManifestByDigest(*searchesult, builder)
   348  		}
   349  	} else {
   350  		// Check if search results contain fat-manifest.
   351  		if searchResult, ok := resultMap["list.manifest.json"]; ok {
   352  			// In case of a fat-manifest, Artifactory will create two folders.
   353  			// One folder named as the image tag, which contains the fat manifest.
   354  			// The second folder, named as image's manifest digest, contains the image layers and the image's manifest.
   355  			log.Debug("Found list.manifest.json (fat-manifest). Searching for the image manifest digest in list.manifest.json")
   356  			var digest string
   357  			digest, err = getImageDigestFromFatManifest(*searchResult, builder)
   358  			if err == nil && digest != "" {
   359  				// Remove tag from pattern, place the manifest digest instead.
   360  				imagePathPattern = strings.Replace(imagePathPattern, "/*", "", 1)
   361  				imagePathPattern = path.Join(imagePathPattern[:strings.LastIndex(imagePathPattern, "/")], strings.Replace(digest, ":", "__", 1), "*")
   362  				// Retry search.
   363  				return searchManifestAndLayersDetails(builder, imagePathPattern)
   364  			}
   365  			log.Debug("Couldn't find maching digest in list.manifest.json")
   366  		}
   367  	}
   368  	return
   369  }
   370  
   371  func getImageDigestFromFatManifest(fatManifest utils.ResultItem, builder *buildInfoBuilder) (string, error) {
   372  	var fatManifestContent *FatManifest
   373  	if err := builder.downloadLayer(fatManifest, &fatManifestContent); err != nil {
   374  		log.Debug(`failed to unmarshal fat-manifest`)
   375  		return "", err
   376  	}
   377  	imageOs, imageArch, err := builder.containerManager.OsCompatibility(builder.image)
   378  	if err != nil {
   379  		return "", err
   380  	}
   381  	return searchManifestDigest(imageOs, imageArch, fatManifestContent.Manifests), nil
   382  }
   383  
   384  // Verify manifest contains the builder image digest. If there is no match, return nil.
   385  func verifyManifestBySha256(manifestSearchResult utils.ResultItem, builder *buildInfoBuilder) (imageManifest *manifest, err error) {
   386  	if manifestSearchResult.GetProperty("docker.manifest.digest") != builder.manifestSha256 {
   387  		log.Debug(`Found incorrect manifest.json file. Expects sha256 "` + builder.manifestSha256 + `" found "` + manifestSearchResult.GetProperty("sha256"))
   388  		return
   389  	}
   390  	log.Debug(`Found manifest.json with expected sha256: "` + builder.manifestSha256)
   391  	if err = builder.downloadLayer(manifestSearchResult, &imageManifest); err != nil || imageManifest == nil {
   392  		return
   393  	}
   394  	builder.imageId = imageManifest.Config.Digest
   395  	return
   396  }
   397  
   398  // Verify manifest by comparing config digest, which references to the image digest. If there is no match, return nil.
   399  func verifyManifestByDigest(manifestSearchResult utils.ResultItem, builder *buildInfoBuilder) (imageManifest *manifest, err error) {
   400  	if err = builder.downloadLayer(manifestSearchResult, &imageManifest); err != nil {
   401  		return
   402  	}
   403  	if imageManifest.Config.Digest != builder.imageId {
   404  		log.Debug(`Found incorrect manifest.json file. Expects digest "` + builder.imageId + `" found "` + imageManifest.Config.Digest)
   405  		imageManifest = nil
   406  	}
   407  	return
   408  }
   409  
   410  // Read the file which contains the following format: 'IMAGE-TAG-IN-ARTIFACTORY'@sha256'SHA256-OF-THE-IMAGE-MANIFEST'.
   411  func GetImageTagWithDigest(filePath string) (tag string, sha256 string, err error) {
   412  	data, err := os.ReadFile(filePath)
   413  	if err != nil {
   414  		log.Debug("os.ReadFile failed with '%s'\n", err)
   415  		err = errorutils.CheckError(err)
   416  		return
   417  	}
   418  	splittedData := strings.Split(string(data), `@`)
   419  	if len(splittedData) != 2 {
   420  		err = errorutils.CheckError(errors.New(`unexpected file format "` + filePath + `". The file should include one line in the following format: image-tag@sha256`))
   421  		return
   422  	}
   423  	tag, sha256 = splittedData[0], strings.Trim(splittedData[1], "\n")
   424  	if tag == "" || sha256 == "" {
   425  		err = errorutils.CheckError(errors.New(`missing image-tag/sha256 in file: "` + filePath + `"`))
   426  	}
   427  	return
   428  }
   429  
   430  // Search for manifest digest in fat manifest, which contains specific platforms.
   431  func searchManifestDigest(imageOs, imageArch string, manifestList []ManifestDetails) (digest string) {
   432  	for _, manifest := range manifestList {
   433  		if manifest.Platform.Os == imageOs && manifest.Platform.Architecture == imageArch {
   434  			digest = manifest.Digest
   435  			break
   436  		}
   437  	}
   438  	return
   439  }
   440  
   441  func searchHandler(imagePathPattern string, builder *buildInfoBuilder) (resultMap map[string]*utils.ResultItem, err error) {
   442  	resultMap, err = performSearch(imagePathPattern, builder.serviceManager)
   443  	if err != nil {
   444  		return
   445  	}
   446  	// Validate there are no .marker layers.
   447  	if totalDownloaded, err := downloadMarkerLayersToRemoteCache(resultMap, builder); err != nil || totalDownloaded == 0 {
   448  		return resultMap, err
   449  	}
   450  	log.Debug("Marker layers were found, updating search results.")
   451  	return performSearch(imagePathPattern, builder.serviceManager)
   452  }
   453  
   454  // return a map of: layer-digest -> layer-search-result
   455  func performSearch(imagePathPattern string, serviceManager artifactory.ArtifactoryServicesManager) (resultMap map[string]*utils.ResultItem, err error) {
   456  	searchParams := services.NewSearchParams()
   457  	searchParams.ArtifactoryCommonParams = &utils.ArtifactoryCommonParams{}
   458  	searchParams.Pattern = imagePathPattern
   459  	reader, err := serviceManager.SearchFiles(searchParams)
   460  	if err != nil {
   461  		return nil, err
   462  	}
   463  	defer func() {
   464  		if deferErr := reader.Close(); err == nil {
   465  			err = deferErr
   466  		}
   467  	}()
   468  	resultMap = map[string]*utils.ResultItem{}
   469  	for resultItem := new(utils.ResultItem); reader.NextRecord(resultItem) == nil; resultItem = new(utils.ResultItem) {
   470  		resultMap[resultItem.Name] = resultItem
   471  	}
   472  	return resultMap, reader.GetError()
   473  }
   474  
   475  // Digest of type sha256:30daa5c11544632449b01f450bebfef6b89644e9e683258ed05797abe7c32a6e to
   476  // sha256__30daa5c11544632449b01f450bebfef6b89644e9e683258ed05797abe7c32a6e
   477  func digestToLayer(digest string) string {
   478  	return strings.Replace(digest, ":", "__", 1)
   479  }
   480  
   481  // Get the number of dependencies layers from the config.
   482  func (configLayer *configLayer) getNumberOfDependentLayers() int {
   483  	layersNum := len(configLayer.History)
   484  	newImageLayers := true
   485  	for i := len(configLayer.History) - 1; i >= 0; i-- {
   486  		if newImageLayers {
   487  			layersNum--
   488  		}
   489  		if !newImageLayers && configLayer.History[i].EmptyLayer {
   490  			layersNum--
   491  		}
   492  		createdBy := configLayer.History[i].CreatedBy
   493  		if strings.Contains(createdBy, "ENTRYPOINT") || strings.Contains(createdBy, "MAINTAINER") {
   494  			newImageLayers = false
   495  		}
   496  	}
   497  	return layersNum
   498  }
   499  
   500  func removeDuplicateLayers(imageMLayers []layer) []layer {
   501  	res := imageMLayers[:0]
   502  	// Use map to record duplicates as we find them.
   503  	encountered := map[string]bool{}
   504  	for _, v := range imageMLayers {
   505  		if !encountered[v.Digest] {
   506  			res = append(res, v)
   507  			encountered[v.Digest] = true
   508  		}
   509  	}
   510  	return res
   511  }
   512  
   513  // When a client tries to pull an image from a remote repository in Artifactory and the client has some the layers cached locally on the disk,
   514  // then Artifactory will not download these layers into the remote repository cache. Instead, it will mark the layer artifacts with .marker suffix files in the remote cache.
   515  // This function download all the marker layers into the remote cache repository.
   516  func downloadMarkerLayersToRemoteCache(resultMap map[string]*utils.ResultItem, builder *buildInfoBuilder) (int, error) {
   517  	if !builder.repositoryDetails.isRemote || len(resultMap) == 0 {
   518  		return 0, nil
   519  	}
   520  	totalDownloaded := 0
   521  	remoteRepo := builder.repositoryDetails.key
   522  	imageName := getImageName(builder.image.Tag())
   523  	clientDetails := builder.serviceManager.GetConfig().GetServiceDetails().CreateHttpClientDetails()
   524  	// Search for marker layers
   525  	for _, layerData := range resultMap {
   526  		if strings.HasSuffix(layerData.Name, markerLayerSuffix) {
   527  			log.Debug(fmt.Sprintf("Downloading %s layer into remote repository cache...", layerData.Name))
   528  			baseUrl := builder.serviceManager.GetConfig().GetServiceDetails().GetUrl()
   529  			endpoint := "api/docker/" + remoteRepo + "/v2/" + imageName + "/blobs/" + toNoneMarkerLayer(layerData.Name)
   530  			resp, body, err := builder.serviceManager.Client().SendHead(baseUrl+endpoint, &clientDetails)
   531  			if err != nil {
   532  				return totalDownloaded, err
   533  			}
   534  			if resp.StatusCode != http.StatusOK {
   535  				return totalDownloaded, errorutils.CheckError(errors.New("Artifactory response: " + resp.Status + "for" + string(body)))
   536  			}
   537  			totalDownloaded++
   538  		}
   539  	}
   540  	return totalDownloaded, nil
   541  }
   542  
   543  func getImageName(image string) string {
   544  	imageId, tag := strings.LastIndex(image, "/"), strings.LastIndex(image, ":")
   545  	if imageId == -1 || tag == -1 {
   546  		return ""
   547  	}
   548  	return image[imageId+1 : tag]
   549  }
   550  
   551  func toNoneMarkerLayer(layer string) string {
   552  	imageId := strings.Replace(layer, "__", ":", 1)
   553  	return strings.Replace(imageId, ".marker", "", 1)
   554  }
   555  
   556  // To unmarshal config layer file
   557  type configLayer struct {
   558  	History []history `json:"history,omitempty"`
   559  }
   560  
   561  type history struct {
   562  	Created    string `json:"created,omitempty"`
   563  	CreatedBy  string `json:"created_by,omitempty"`
   564  	EmptyLayer bool   `json:"empty_layer,omitempty"`
   565  }
   566  
   567  // To unmarshal manifest.json file
   568  type manifest struct {
   569  	Config manifestConfig `json:"config,omitempty"`
   570  	Layers []layer        `json:"layers,omitempty"`
   571  }
   572  
   573  type manifestConfig struct {
   574  	Digest string `json:"digest,omitempty"`
   575  }
   576  
   577  type layer struct {
   578  	Digest    string `json:"digest,omitempty"`
   579  	MediaType string `json:"mediaType,omitempty"`
   580  }
   581  
   582  type CommandType string