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