diff --git a/artifactory/utils/commandsummary/buildinfosummary.go b/artifactory/utils/commandsummary/buildinfosummary.go
index 8071b7694..8871a5d78 100644
--- a/artifactory/utils/commandsummary/buildinfosummary.go
+++ b/artifactory/utils/commandsummary/buildinfosummary.go
@@ -8,16 +8,17 @@ import (
buildInfo "github.com/jfrog/build-info-go/entities"
"github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
- "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/container"
"github.com/jfrog/jfrog-client-go/utils/log"
)
const (
- basicSummaryUpgradeNotice = "🐸 Enable the linkage to Artifactory\n\n"
- modulesTitle = "📦 Modules published to Artifactory by this workflow"
- minTableColumnLength = 400
- markdownSpaceFiller = " "
- NonScannedResult = "non-scanned"
+ basicSummaryUpgradeNotice = "🐸 Enable the linkage to Artifactory\n\n"
+ modulesTitle = "📦 Modules published to Artifactory by this workflow"
+ minTableColumnLength = 400
+ markdownSpaceFiller = " "
+ NonScannedResult = "non-scanned"
+ ManifestJsonFile = "manifest.json"
+ AttestationsModuleIdPrefix string = "attestations"
)
var (
@@ -290,7 +291,7 @@ func isSupportedModule(module *buildInfo.Module) bool {
return false
}
if module.Type == buildInfo.Docker {
- return !strings.HasPrefix(module.Id, container.AttestationsModuleIdPrefix)
+ return !strings.HasPrefix(module.Id, AttestationsModuleIdPrefix)
}
return true
}
@@ -300,7 +301,7 @@ func createDockerMultiArchTitle(module *buildInfo.Module) string {
parentImageName := strings.Split(module.Parent, ":")[0]
var sha256 string
for _, artifact := range module.Artifacts {
- if artifact.Name == container.ManifestJsonFile {
+ if artifact.Name == ManifestJsonFile {
sha256 = artifact.Sha256
break
}
diff --git a/artifactory/utils/container/buildinfo.go b/artifactory/utils/container/buildinfo.go
deleted file mode 100644
index 74b69fe67..000000000
--- a/artifactory/utils/container/buildinfo.go
+++ /dev/null
@@ -1,640 +0,0 @@
-package container
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "os"
- "path"
- "strings"
-
- ioutils "github.com/jfrog/gofrog/io"
-
- buildinfo "github.com/jfrog/build-info-go/entities"
-
- artutils "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils"
- "github.com/jfrog/jfrog-cli-core/v2/common/build"
- "github.com/jfrog/jfrog-client-go/artifactory"
- "github.com/jfrog/jfrog-client-go/artifactory/services"
- "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
- "github.com/jfrog/jfrog-client-go/utils/errorutils"
- "github.com/jfrog/jfrog-client-go/utils/io/content"
- "github.com/jfrog/jfrog-client-go/utils/log"
-)
-
-const (
- Pull CommandType = "pull"
- Push CommandType = "push"
- foreignLayerMediaType string = "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip"
- imageNotFoundErrorMessage string = "Could not find docker image in Artifactory, expecting image tag: %s"
- markerLayerSuffix string = ".marker"
- attestationManifestRefType string = "attestation-manifest"
- unknownPlatformPlaceholder string = "unknown"
-
- ManifestJsonFile = "manifest.json"
- AttestationsModuleIdPrefix string = "attestations"
-)
-
-// Docker image build info builder.
-type Builder interface {
- Build(module string) (*buildinfo.BuildInfo, error)
- UpdateArtifactsAndDependencies() error
- GetLayers() *[]utils.ResultItem
-}
-
-type buildInfoBuilder struct {
- image *Image
- repositoryDetails RepositoryDetails
- buildName string
- buildNumber string
- project string
- serviceManager artifactory.ArtifactoryServicesManager
- imageSha2 string
- // If true, don't set layers props in Artifactory.
- skipTaggingLayers bool
- imageLayers []utils.ResultItem
-}
-
-type RepositoryDetails struct {
- key string
- isRemote bool
- repoType string
-}
-
-// Create instance of docker build info builder.
-func newBuildInfoBuilder(image *Image, repository, buildName, buildNumber, project string, serviceManager artifactory.ArtifactoryServicesManager) (*buildInfoBuilder, error) {
- var err error
- builder := &buildInfoBuilder{}
- builder.repositoryDetails.key = repository
-
- // Get repository details in one API call to determine both isRemote and repoType
- repoDetails := &services.RepositoryDetails{}
- err = serviceManager.GetRepository(repository, &repoDetails)
- if err != nil {
- return nil, errorutils.CheckErrorf("failed to get details for repository '%s'. Error:\n%s", repository, err.Error())
- }
-
- builder.repositoryDetails.isRemote = repoDetails.GetRepoType() == "remote"
- builder.repositoryDetails.repoType = repoDetails.GetRepoType()
-
- builder.image = image
- builder.buildName = buildName
- builder.buildNumber = buildNumber
- builder.project = project
- builder.serviceManager = serviceManager
- return builder, nil
-}
-
-func (builder *buildInfoBuilder) setImageSha2(imageSha2 string) {
- builder.imageSha2 = imageSha2
-}
-
-func (builder *buildInfoBuilder) GetLayers() *[]utils.ResultItem {
- return &builder.imageLayers
-}
-
-func (builder *buildInfoBuilder) getSearchableRepo() string {
- if builder.repositoryDetails.isRemote {
- return builder.repositoryDetails.key + "-cache"
- }
- return builder.repositoryDetails.key
-}
-
-// Set build properties on image layers in Artifactory.
-func setBuildProperties(buildName, buildNumber, project string, imageLayers []utils.ResultItem, serviceManager artifactory.ArtifactoryServicesManager, originalRepo string, repoDetails *RepositoryDetails) (err error) {
- if buildName == "" || buildNumber == "" {
- log.Debug("Skipping setting properties - build name and build number are required")
- return nil
- }
-
- props, err := build.CreateBuildProperties(buildName, buildNumber, project)
- if err != nil {
- return
- }
-
- if len(props) == 0 {
- log.Debug("Skipping setting properties - no properties created")
- return nil
- }
-
- filteredLayers, err := filterLayersForVirtualRepository(imageLayers, serviceManager, originalRepo, repoDetails)
- if err != nil {
- log.Debug("Failed to filter layers for virtual repository, proceeding with all layers:", err.Error())
- filteredLayers = imageLayers
- }
-
- if len(filteredLayers) == 0 {
- log.Debug("No layers to set properties on, skipping property setting")
- return nil
- }
-
- pathToFile, err := writeLayersToFile(filteredLayers)
- if err != nil {
- return
- }
- reader := content.NewContentReader(pathToFile, content.DefaultKey)
- defer ioutils.Close(reader, &err)
- _, err = serviceManager.SetProps(services.PropsParams{Reader: reader, Props: props})
- return
-}
-
-// filterLayersForVirtualRepository filters image layers to only include those from the default deployment repository
-// when dealing with virtual repositories. For non-virtual repositories, it returns all layers unchanged.
-func filterLayersForVirtualRepository(imageLayers []utils.ResultItem, serviceManager artifactory.ArtifactoryServicesManager, originalRepo string, repoDetails *RepositoryDetails) ([]utils.ResultItem, error) {
- if len(imageLayers) == 0 {
- return imageLayers, nil
- }
-
- // Optimization: If we already know the repo type and it's not virtual, skip the API call
- if repoDetails != nil && repoDetails.repoType != "" && repoDetails.repoType != "virtual" {
- log.Debug("Repository ", originalRepo, "is not virtual (type:", repoDetails.repoType+"), skipping determining default deployment config")
- return imageLayers, nil
- }
-
- // For backwards compatibility or when repoDetails is not available, fall back to API call
- if repoDetails == nil || repoDetails.repoType == "" {
- log.Debug("Repository type not cached, making API call to determine repository configuration")
- repoConfig, err := getRepositoryConfiguration(originalRepo, serviceManager)
- if err != nil {
- return imageLayers, errorutils.CheckErrorf("failed to get repository configuration for '%s': %w", originalRepo, err)
- }
-
- // If it's not a virtual repository, return all layers unchanged
- if repoConfig == nil || repoConfig.Rclass != "virtual" {
- log.Debug("Repository", originalRepo, "is not virtual, proceeding with all layers")
- return imageLayers, nil
- }
-
- // If it's a virtual repository but has no default deployment repo, return all layers
- if repoConfig.DefaultDeploymentRepo == "" {
- log.Debug("Virtual repository", originalRepo, "has no default deployment repository, proceeding with all layers")
- return imageLayers, nil
- }
-
- // Filter layers to only include those from the default deployment repository
- var filteredLayers []utils.ResultItem
- for _, layer := range imageLayers {
- if layer.Repo == repoConfig.DefaultDeploymentRepo {
- filteredLayers = append(filteredLayers, layer)
- }
- }
-
- if len(filteredLayers) == 0 {
- log.Warn(fmt.Sprintf(`No layers found in default deployment repository '%s' for virtual repository '%s'.
-This may indicate that image layers exist in other repositories but not in the default deployment repository.
-Properties will not be set to maintain consistency with virtual repository configuration.
-To fix this, consider pushing the image directly to the virtual repository to ensure it lands in the default deployment repository.`, repoConfig.DefaultDeploymentRepo, originalRepo))
- return []utils.ResultItem{}, nil
- }
- log.Info("Filtered", len(imageLayers), "layers to", len(filteredLayers), "layers from default deployment repository:", repoConfig.DefaultDeploymentRepo)
-
- return filteredLayers, nil
- }
-
- log.Info("Determining virtual repository", originalRepo, "config to determine default deployment repository")
- repoConfig, err := getRepositoryConfiguration(originalRepo, serviceManager)
- if err != nil {
- return imageLayers, errorutils.CheckErrorf("failed to get repository configuration for virtual repository '%s': %w", originalRepo, err)
- }
-
- // If it's a virtual repository but has no default deployment repo, return all layers
- if repoConfig.DefaultDeploymentRepo == "" {
- log.Debug("Virtual repository", originalRepo, "has no default deployment repository, proceeding with all layers")
- return imageLayers, nil
- }
-
- // Filter layers to only include those from the default deployment repository
- var filteredLayers []utils.ResultItem
- for _, layer := range imageLayers {
- if layer.Repo == repoConfig.DefaultDeploymentRepo {
- filteredLayers = append(filteredLayers, layer)
- }
- }
-
- if len(filteredLayers) == 0 {
- log.Warn(fmt.Sprintf(`No layers found in default deployment repository '%s' for virtual repository '%s'.
-This may indicate that image layers exist in other repositories but not in the default deployment repository.
-Properties will not be set to maintain consistency with virtual repository configuration.
-To fix this, consider pushing the image directly to the virtual repository to ensure it lands in the default deployment repository.`, repoConfig.DefaultDeploymentRepo, originalRepo))
- return []utils.ResultItem{}, nil
- }
- log.Info("Filtered", len(imageLayers), "layers to", len(filteredLayers), "layers from default deployment repository:", repoConfig.DefaultDeploymentRepo)
-
- return filteredLayers, nil
-}
-
-// repositoryConfig represents the virtual repository configuration
-type repositoryConfig struct {
- Key string `json:"key"`
- Rclass string `json:"rclass"`
- DefaultDeploymentRepo string `json:"defaultDeploymentRepo"`
-}
-
-// getRepositoryConfiguration fetches the repository configuration from Artifactory
-func getRepositoryConfiguration(repoKey string, serviceManager artifactory.ArtifactoryServicesManager) (*repositoryConfig, error) {
- httpClientDetails := serviceManager.GetConfig().GetServiceDetails().CreateHttpClientDetails()
-
- baseUrl := serviceManager.GetConfig().GetServiceDetails().GetUrl()
- endpoint := "api/repositories/" + repoKey
- url := baseUrl + endpoint
- resp, body, _, err := serviceManager.Client().SendGet(url, true, &httpClientDetails)
- if err != nil {
- return nil, err
- }
- if resp.StatusCode != http.StatusOK {
- return nil, fmt.Errorf("failed to get repository configuration: HTTP %d", resp.StatusCode)
- }
- var config repositoryConfig
- if err := json.Unmarshal(body, &config); err != nil {
- return nil, fmt.Errorf("failed to parse repository configuration: %v", err)
- }
-
- return &config, nil
-}
-
-// Download the content of layer search result.
-func downloadLayer(searchResult utils.ResultItem, result interface{}, serviceManager artifactory.ArtifactoryServicesManager, repo string) error {
- // Search results may include artifacts from the remote-cache repository.
- // When artifact is expired, it cannot be downloaded from the remote-cache.
- // To solve this, change back the search results' repository, to its origin remote/virtual.
- searchResult.Repo = repo
- return artutils.RemoteUnmarshal(serviceManager, searchResult.GetItemRelativePath(), result)
-}
-
-func writeLayersToFile(layers []utils.ResultItem) (filePath string, err error) {
- writer, err := content.NewContentWriter("results", true, false)
- if err != nil {
- return
- }
- defer ioutils.Close(writer, &err)
- for _, layer := range layers {
- writer.Write(layer)
- }
- filePath = writer.GetFilePath()
- return
-}
-
-// Return - manifest artifacts as buildinfo.Artifact struct.
-func getManifestArtifact(manifest *utils.ResultItem) (artifact buildinfo.Artifact) {
- return buildinfo.Artifact{
- Name: ManifestJsonFile,
- Type: "json",
- Checksum: buildinfo.Checksum{Sha1: manifest.Actual_Sha1, Md5: manifest.Actual_Md5, Sha256: manifest.Sha256},
- Path: path.Join(manifest.Path, manifest.Name),
- OriginalDeploymentRepo: manifest.Repo,
- }
-}
-
-// Return - fat manifest artifacts as buildinfo.Artifact struct.
-func getFatManifestArtifact(fatManifest *utils.ResultItem) (artifact buildinfo.Artifact) {
- return buildinfo.Artifact{
- Name: "list.manifest.json",
- Type: "json",
- Checksum: buildinfo.Checksum{Sha1: fatManifest.Actual_Sha1, Md5: fatManifest.Actual_Md5, Sha256: fatManifest.Sha256},
- Path: path.Join(fatManifest.Path, fatManifest.Name),
- OriginalDeploymentRepo: fatManifest.Repo,
- }
-}
-
-// Return - manifest dependency as buildinfo.Dependency struct.
-func getManifestDependency(searchResults *utils.ResultItem) (dependency buildinfo.Dependency) {
- return buildinfo.Dependency{
- Id: ManifestJsonFile,
- Type: "json",
- Checksum: buildinfo.Checksum{Sha1: searchResults.Actual_Sha1, Md5: searchResults.Actual_Md5, Sha256: searchResults.Sha256},
- }
-}
-
-// Read the file which contains the following format: 'IMAGE-TAG-IN-ARTIFACTORY'@sha256'SHA256-OF-THE-IMAGE-MANIFEST'.
-func GetImageTagWithDigest(filePath string) (*Image, string, error) {
- var buildxMetaData buildxMetaData
- data, err := os.ReadFile(filePath)
- if errorutils.CheckError(err) != nil {
- log.Debug("os.ReadFile failed with '%s'\n", err)
- return nil, "", err
- }
- err = json.Unmarshal(data, &buildxMetaData)
- if err != nil {
- log.Debug("failed unmarshalling buildxMetaData file with error: " + err.Error() + ". falling back to Kanico/OC file format...")
- }
- // Try to read buildx metadata file.
- if buildxMetaData.ImageName != "" && buildxMetaData.ImageSha256 != "" {
- return NewImage(buildxMetaData.ImageName), buildxMetaData.ImageSha256, nil
- }
- // Try read Kaniko/oc file.
- splittedData := strings.Split(string(data), `@`)
- if len(splittedData) != 2 {
- return nil, "", errorutils.CheckErrorf(`unexpected file format "%s". The file should include one line in the following format: image-tag@sha256`, filePath)
- }
- tag, sha256 := splittedData[0], strings.Trim(splittedData[1], "\n")
- if tag == "" || sha256 == "" {
- err = errorutils.CheckErrorf(`missing image-tag/sha256 in file: "%s"`, filePath)
- if err != nil {
- return nil, "", err
- }
- }
- return NewImage(tag), sha256, nil
-}
-
-type buildxMetaData struct {
- ImageName string `json:"image.name"`
- ImageSha256 string `json:"containerimage.digest"`
-}
-
-// Search for manifest digest in fat manifest, which contains specific platforms.
-func searchManifestDigest(imageOs, imageArch string, manifestList []ManifestDetails) (digest string) {
- for _, manifest := range manifestList {
- if manifest.Platform.Os == imageOs && manifest.Platform.Architecture == imageArch {
- digest = manifest.Digest
- break
- }
- }
- return
-}
-
-// Returns a map of: layer-digest -> layer-search-result
-func performSearch(imagePathPattern string, serviceManager artifactory.ArtifactoryServicesManager) (resultMap map[string]*utils.ResultItem, err error) {
- searchParams := services.NewSearchParams()
- searchParams.CommonParams = &utils.CommonParams{}
- searchParams.Pattern = imagePathPattern
- var reader *content.ContentReader
- reader, err = serviceManager.SearchFiles(searchParams)
- if err != nil {
- return nil, err
- }
- defer ioutils.Close(reader, &err)
- resultMap = make(map[string]*utils.ResultItem)
- for resultItem := new(utils.ResultItem); reader.NextRecord(resultItem) == nil; resultItem = new(utils.ResultItem) {
- resultMap[resultItem.Name] = resultItem
- }
- err = reader.GetError()
- return
-}
-
-// Returns a map of: image-sha2 -> image-layers
-func performMultiPlatformImageSearch(imagePathPattern string, serviceManager artifactory.ArtifactoryServicesManager) (resultMap map[string][]*utils.ResultItem, err error) {
- searchParams := services.NewSearchParams()
- searchParams.CommonParams = &utils.CommonParams{}
- searchParams.Pattern = imagePathPattern
- searchParams.Recursive = true
- var reader *content.ContentReader
- reader, err = serviceManager.SearchFiles(searchParams)
- if err != nil {
- return nil, err
- }
- defer ioutils.Close(reader, &err)
- pathToSha2 := make(map[string]string)
- pathToImageLayers := make(map[string][]*utils.ResultItem)
- resultMap = make(map[string][]*utils.ResultItem)
- for resultItem := new(utils.ResultItem); reader.NextRecord(resultItem) == nil; resultItem = new(utils.ResultItem) {
- pathToImageLayers[resultItem.Path] = append(pathToImageLayers[resultItem.Path], resultItem)
- if resultItem.Name == ManifestJsonFile {
- pathToSha2[resultItem.Path] = "sha256:" + resultItem.Sha256
- }
- }
- for k, v := range pathToSha2 {
- resultMap[v] = append(resultMap[v], pathToImageLayers[k]...)
- }
- err = reader.GetError()
- return
-}
-
-// Digest of type sha256:30daa5c11544632449b01f450bebfef6b89644e9e683258ed05797abe7c32a6e to
-// sha256__30daa5c11544632449b01f450bebfef6b89644e9e683258ed05797abe7c32a6e
-func digestToLayer(digest string) string {
- return strings.Replace(digest, ":", "__", 1)
-}
-
-// Get the number of dependencies layers from the config.
-func (configLayer *configLayer) getNumberOfDependentLayers() int {
- layersNum := len(configLayer.History)
- newImageLayers := true
- for i := len(configLayer.History) - 1; i >= 0; i-- {
- if newImageLayers {
- layersNum--
- }
- if !newImageLayers && configLayer.History[i].EmptyLayer {
- layersNum--
- }
- createdBy := configLayer.History[i].CreatedBy
- if strings.Contains(createdBy, "ENTRYPOINT") || strings.Contains(createdBy, "MAINTAINER") {
- newImageLayers = false
- }
- }
- return layersNum
-}
-
-func removeDuplicateLayers(imageMLayers []layer) []layer {
- res := imageMLayers[:0]
- // Use map to record duplicates as we find them.
- encountered := map[string]bool{}
- for _, v := range imageMLayers {
- if !encountered[v.Digest] {
- res = append(res, v)
- encountered[v.Digest] = true
- }
- }
- return res
-}
-
-func toNoneMarkerLayer(layer string) string {
- imageId := strings.Replace(layer, "__", ":", 1)
- return strings.Replace(imageId, ".marker", "", 1)
-}
-
-type CommandType string
-
-// Create an image's build info from manifest.json.
-func (builder *buildInfoBuilder) createBuildInfo(commandType CommandType, manifest *manifest, candidateLayers map[string]*utils.ResultItem, module string) (*buildinfo.BuildInfo, error) {
- if manifest == nil {
- return nil, nil
- }
- imageProperties := map[string]string{
- "docker.image.id": builder.imageSha2,
- "docker.image.tag": builder.image.Name(),
- }
- if module == "" {
- var err error
- if module, err = builder.image.GetImageShortNameWithTag(); err != nil {
- return nil, err
- }
- }
- // Manifest may hold 'empty layers'. As a result, promotion will fail to promote the same layer more than once.
- manifest.Layers = removeDuplicateLayers(manifest.Layers)
- var artifacts []buildinfo.Artifact
- var dependencies []buildinfo.Dependency
- var err error
- switch commandType {
- case Pull:
- dependencies = builder.createPullBuildProperties(manifest, candidateLayers)
- case Push:
- artifacts, dependencies, builder.imageLayers, err = builder.createPushBuildProperties(manifest, candidateLayers)
- if err != nil {
- return nil, err
- }
- if !builder.skipTaggingLayers {
- if err := setBuildProperties(builder.buildName, builder.buildNumber, builder.project, builder.imageLayers, builder.serviceManager, builder.repositoryDetails.key, &builder.repositoryDetails); err != nil {
- return nil, err
- }
- }
- }
- buildInfo := &buildinfo.BuildInfo{Modules: []buildinfo.Module{{
- Id: module,
- Type: buildinfo.Docker,
- Properties: imageProperties,
- Artifacts: artifacts,
- Dependencies: dependencies,
- }}}
- return buildInfo, nil
-}
-
-// Create the image's build info from list.manifest.json.
-func (builder *buildInfoBuilder) createMultiPlatformBuildInfo(fatManifest *FatManifest, searchResultFatManifest *utils.ResultItem, candidateImages map[string][]*utils.ResultItem, baseModuleId string) (*buildinfo.BuildInfo, error) {
- imageProperties := map[string]string{
- "docker.image.tag": builder.image.Name(),
- }
- if baseModuleId == "" {
- imageName, err := builder.image.GetImageShortNameWithTag()
- if err != nil {
- return nil, err
- }
- baseModuleId = imageName
- }
- // Add layers.
- builder.imageLayers = append(builder.imageLayers, *searchResultFatManifest)
- // Create fat-manifest module
- buildInfo := &buildinfo.BuildInfo{Modules: []buildinfo.Module{{
- Id: baseModuleId,
- Type: buildinfo.Docker,
- Properties: imageProperties,
- Artifacts: []buildinfo.Artifact{getFatManifestArtifact(searchResultFatManifest)},
- }}}
- imageLongNameWithoutRepo, err := builder.image.GetImageLongNameWithoutRepoWithTag()
- if err != nil {
- return nil, err
- }
- // Create all image arch modules
- for _, manifest := range fatManifest.Manifests {
- image := candidateImages[manifest.Digest]
- var artifacts []buildinfo.Artifact
- for _, layer := range image {
- builder.imageLayers = append(builder.imageLayers, *layer)
- if layer.Name == ManifestJsonFile {
- artifacts = append(artifacts, getManifestArtifact(layer))
- } else {
- artifacts = append(artifacts, layer.ToArtifact())
- }
- }
- buildInfo.Modules = append(buildInfo.Modules, buildinfo.Module{
- Id: getModuleIdByManifest(manifest, baseModuleId),
- Type: buildinfo.Docker,
- Artifacts: artifacts,
- Parent: imageLongNameWithoutRepo,
- })
- }
- return buildInfo, setBuildProperties(builder.buildName, builder.buildNumber, builder.project, builder.imageLayers, builder.serviceManager, builder.repositoryDetails.key, &builder.repositoryDetails)
-}
-
-// Construct the manifest's module ID by its type (attestation) or its platform.
-func getModuleIdByManifest(manifest ManifestDetails, baseModuleId string) string {
- if manifest.Annotations.ReferenceType == attestationManifestRefType {
- return path.Join(AttestationsModuleIdPrefix, baseModuleId)
- }
- if manifest.Platform.Os != unknownPlatformPlaceholder && manifest.Platform.Architecture != unknownPlatformPlaceholder {
- return path.Join(manifest.Platform.Os, manifest.Platform.Architecture, baseModuleId)
- }
- return baseModuleId
-}
-
-func (builder *buildInfoBuilder) createPushBuildProperties(imageManifest *manifest, candidateLayers map[string]*utils.ResultItem) (artifacts []buildinfo.Artifact, dependencies []buildinfo.Dependency, imageLayers []utils.ResultItem, err error) {
- // Add artifacts.
- artifacts = append(artifacts, getManifestArtifact(candidateLayers[ManifestJsonFile]))
- artifacts = append(artifacts, candidateLayers[digestToLayer(builder.imageSha2)].ToArtifact())
-
- // Add layers.
- imageLayers = append(imageLayers, *candidateLayers[ManifestJsonFile])
- imageLayers = append(imageLayers, *candidateLayers[digestToLayer(builder.imageSha2)])
-
- totalLayers := len(imageManifest.Layers)
- totalDependencies, err := builder.totalDependencies(candidateLayers[digestToLayer(builder.imageSha2)])
- if err != nil {
- return nil, nil, nil, err
- }
-
- // Add image layers as artifacts and dependencies.
- for i := 0; i < totalLayers; i++ {
- layerFileName := digestToLayer(imageManifest.Layers[i].Digest)
- item, layerExists := candidateLayers[layerFileName]
- if !layerExists {
- err := handleForeignLayer(imageManifest.Layers[i].MediaType, layerFileName)
- if err != nil {
- return nil, nil, nil, err
- }
- continue
- }
-
- // Decide if the layer is also a dependency.
- if i < totalDependencies {
- dependencies = append(dependencies, item.ToDependency())
- }
- artifacts = append(artifacts, item.ToArtifact())
- imageLayers = append(imageLayers, *item)
- }
- return
-}
-
-func (builder *buildInfoBuilder) createPullBuildProperties(imageManifest *manifest, imageLayers map[string]*utils.ResultItem) []buildinfo.Dependency {
- configDependencies, err := getDependenciesFromManifestConfig(imageLayers, builder.imageSha2)
- if err != nil {
- log.Debug(err.Error())
- return nil
- }
-
- layerDependencies, err := getDependenciesFromManifestLayer(imageLayers, imageManifest)
- if err != nil {
- log.Debug(err.Error())
- return nil
- }
-
- return append(configDependencies, layerDependencies...)
-}
-
-func getDependenciesFromManifestConfig(candidateLayers map[string]*utils.ResultItem, imageSha2 string) ([]buildinfo.Dependency, error) {
- var dependencies []buildinfo.Dependency
- manifestSearchResults, found := candidateLayers[ManifestJsonFile]
- if !found {
- return nil, errorutils.CheckErrorf("failed to collect build-info. The manifest.json was not found in Artifactory")
- }
-
- dependencies = append(dependencies, getManifestDependency(manifestSearchResults))
- imageDetails, found := candidateLayers[digestToLayer(imageSha2)]
- if !found {
- return nil, errorutils.CheckErrorf("failed to collect build-info. Image '%s' was not found in Artifactory", imageSha2)
- }
-
- return append(dependencies, imageDetails.ToDependency()), nil
-}
-
-func getDependenciesFromManifestLayer(layers map[string]*utils.ResultItem, imageManifest *manifest) ([]buildinfo.Dependency, error) {
- var dependencies []buildinfo.Dependency
- for i := 0; i < len(imageManifest.Layers); i++ {
- layerFileName := digestToLayer(imageManifest.Layers[i].Digest)
- item, layerExists := layers[layerFileName]
- if !layerExists {
- if err := handleForeignLayer(imageManifest.Layers[i].MediaType, layerFileName); err != nil {
- return nil, err
- }
- continue
- }
- dependencies = append(dependencies, item.ToDependency())
- }
- return dependencies, nil
-}
-
-func (builder *buildInfoBuilder) totalDependencies(image *utils.ResultItem) (int, error) {
- configurationLayer := new(configLayer)
- if err := downloadLayer(*image, &configurationLayer, builder.serviceManager, builder.repositoryDetails.key); err != nil {
- return 0, err
- }
- return configurationLayer.getNumberOfDependentLayers(), nil
-}
diff --git a/artifactory/utils/container/buildinfo_test.go b/artifactory/utils/container/buildinfo_test.go
deleted file mode 100644
index 2b9bc5939..000000000
--- a/artifactory/utils/container/buildinfo_test.go
+++ /dev/null
@@ -1,163 +0,0 @@
-package container
-
-import (
- "path/filepath"
- "testing"
-
- "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
- "github.com/stretchr/testify/assert"
-)
-
-func TestGetManifestPaths(t *testing.T) {
- results := getManifestPaths("/hello-world/latest", "docker-local", Push)
- assert.Len(t, results, 2)
- expected := []string{"docker-local/hello-world/latest/*", "docker-local/latest/*"}
- for i, result := range results {
- assert.Equal(t, expected[i], result)
- }
-
- results = getManifestPaths("/hello-world/latest", "docker-local", Pull)
- assert.Len(t, results, 4)
- expected = append(expected, "docker-local/library/hello-world/latest/*", "docker-local/library/latest/*")
- for i, result := range results {
- assert.Equal(t, expected[i], result)
- }
-
- results = getManifestPaths("/docker-remote/hello-world/latest", "docker-remote", Push)
- assert.Len(t, results, 2)
- expected = []string{"docker-remote/docker-remote/hello-world/latest/*", "docker-remote/hello-world/latest/*"}
- for i, result := range results {
- assert.Equal(t, expected[i], result)
- }
-
- results = getManifestPaths("/docker-remote/hello-world/latest", "docker-remote", Pull)
- assert.Len(t, results, 4)
- expected = append(expected, "docker-remote/library/docker-remote/hello-world/latest/*", "docker-remote/library/hello-world/latest/*")
- for i, result := range results {
- assert.Equal(t, expected[i], result)
- }
-}
-
-func TestGetImageWithDigest(t *testing.T) {
- filePath := filepath.Join("..", "testdata", "container", "imageTagWithDigest")
- tag, sha256, err := GetImageTagWithDigest(filePath)
- assert.NoError(t, err)
- assert.Equal(t, "my-image-tag", tag.name)
- assert.Equal(t, "sha256:12345", sha256)
-}
-
-var dummySearchResults = &utils.ResultItem{
- Type: "json",
- Actual_Md5: "md5",
- Actual_Sha1: "sha1",
- Sha256: "sha2",
- Size: 1,
-}
-
-func TestManifestConfig(t *testing.T) {
- dependencies, err := getDependenciesFromManifestConfig(createManifestConfig())
- assert.NoError(t, err)
- assert.Len(t, dependencies, 2)
-}
-
-func createManifestConfig() (map[string]*utils.ResultItem, string) {
- config := make(map[string]*utils.ResultItem)
- config["manifest.json"] = dummySearchResults
- config["sha__123"] = dummySearchResults
- return config, "sha:123"
-}
-
-func TestManifestConfigNoManifestFound(t *testing.T) {
- _, err := getDependenciesFromManifestConfig(createEmptyManifestConfig())
- assert.ErrorContains(t, err, "The manifest.json was not found in Artifactory")
-}
-
-func createEmptyManifestConfig() (map[string]*utils.ResultItem, string) {
- config := make(map[string]*utils.ResultItem)
- return config, "sha:123"
-}
-
-func TestManifestConfigNoLayer(t *testing.T) {
- _, err := getDependenciesFromManifestConfig(createManifestConfigWithNoLayer())
- assert.ErrorContains(t, err, "Image 'sha:123' was not found in Artifactory")
-}
-
-func createManifestConfigWithNoLayer() (map[string]*utils.ResultItem, string) {
- config := make(map[string]*utils.ResultItem)
- config["manifest.json"] = dummySearchResults
- return config, "sha:123"
-}
-
-func TestGetDependenciesFromManifestLayer(t *testing.T) {
- searchResults, manifest := createManifestConfigWithLayer()
- dependencies, err := getDependenciesFromManifestLayer(searchResults, manifest)
- assert.NoError(t, err)
- assert.Len(t, dependencies, 1)
-}
-
-func createManifestConfigWithLayer() (map[string]*utils.ResultItem, *manifest) {
- manifest := &manifest{
- Layers: []layer{{
- Digest: "sha:1",
- MediaType: "MediaType",
- }},
- }
- searchResults := make(map[string]*utils.ResultItem)
- searchResults["manifest.json"] = dummySearchResults
- searchResults["sha__1"] = dummySearchResults
- searchResults["sha__2"] = dummySearchResults
- return searchResults, manifest
-}
-
-func TestMissingDependenciesInManifestLayer(t *testing.T) {
- searchResults, manifest := createManifestConfigWithMissingLayer()
- _, err := getDependenciesFromManifestLayer(searchResults, manifest)
- assert.ErrorContains(t, err, "Could not find layer: sha__2 in Artifactory")
-}
-
-func createManifestConfigWithMissingLayer() (map[string]*utils.ResultItem, *manifest) {
- manifest := &manifest{
- Layers: []layer{
- {
- Digest: "sha:1",
- MediaType: "MediaType",
- },
- // Missing layer
- {
- Digest: "sha:2",
- MediaType: "type",
- },
- },
- }
- searchResults := make(map[string]*utils.ResultItem)
- searchResults["manifest.json"] = dummySearchResults
- searchResults["sha__1"] = dummySearchResults
- return searchResults, manifest
-}
-
-func TestForeignDependenciesInManifestLayer(t *testing.T) {
- searchResults, manifest := createManifestConfigWithForeignLayer()
- dependencies, err := getDependenciesFromManifestLayer(searchResults, manifest)
- assert.NoError(t, err)
- assert.Len(t, dependencies, 1)
-}
-
-func createManifestConfigWithForeignLayer() (map[string]*utils.ResultItem, *manifest) {
- manifest := &manifest{
- Layers: []layer{
- {
- Digest: "sha:1",
- MediaType: "MediaType",
- },
- // Foreign layer
- {
- Digest: "sha:2",
- MediaType: foreignLayerMediaType,
- },
- },
- }
- searchResults := make(map[string]*utils.ResultItem)
- searchResults["manifest.json"] = dummySearchResults
- searchResults["sha__1"] = dummySearchResults
- return searchResults, manifest
-}
diff --git a/artifactory/utils/container/containermanager.go b/artifactory/utils/container/containermanager.go
deleted file mode 100644
index a9ec54405..000000000
--- a/artifactory/utils/container/containermanager.go
+++ /dev/null
@@ -1,257 +0,0 @@
-package container
-
-import (
- "bytes"
- "fmt"
- "os"
- "os/exec"
- "regexp"
- "strings"
-
- "github.com/jfrog/jfrog-cli-core/v2/utils/config"
- "github.com/jfrog/jfrog-cli-core/v2/utils/coreutils"
- "github.com/jfrog/jfrog-client-go/auth"
- "github.com/jfrog/jfrog-client-go/utils"
- "github.com/jfrog/jfrog-client-go/utils/errorutils"
- "github.com/jfrog/jfrog-client-go/utils/log"
-)
-
-// Search for docker API version format pattern e.g. 1.40
-var ApiVersionRegex = regexp.MustCompile(`^(\d+)\.(\d+)$`)
-
-// Docker API version 1.31 is compatible with Docker version 17.07.0, according to https://docs.docker.com/engine/api/#api-version-matrix
-const MinSupportedApiVersion string = "1.31"
-
-// Docker login error message
-const LoginFailureMessage string = "%s login failed for: %s.\n%s image must be in the form: registry-domain/path-in-repository/image-name:version."
-
-func NewManager(containerManagerType ContainerManagerType) ContainerManager {
- return &containerManager{Type: containerManagerType}
-}
-
-type ContainerManagerType int
-
-const (
- DockerClient ContainerManagerType = iota
- Podman
-)
-
-func (cmt ContainerManagerType) String() string {
- return [...]string{"docker", "podman"}[cmt]
-}
-
-// Container image
-type ContainerManager interface {
- // Image ID is basically the image's SHA256
- Id(image *Image) (string, error)
- OsCompatibility(image *Image) (string, string, error)
- RunNativeCmd(cmdParams []string) error
- GetContainerManagerType() ContainerManagerType
-}
-
-type containerManager struct {
- Type ContainerManagerType
-}
-
-type ContainerManagerLoginConfig struct {
- ServerDetails *config.ServerDetails
-}
-
-// Run native command of the container buildtool
-func (containerManager *containerManager) RunNativeCmd(cmdParams []string) error {
- cmd := &nativeCmd{cmdParams: cmdParams, containerManager: containerManager.Type}
- return cmd.RunCmd()
-}
-
-// Get image ID
-func (containerManager *containerManager) Id(image *Image) (string, error) {
- cmd := &getImageIdCmd{image: image, containerManager: containerManager.Type}
- content, err := cmd.RunCmd()
- if err != nil {
- return "", err
- }
- return strings.Split(content, "\n")[0], nil
-}
-
-// Return the OS and architecture on which the image runs e.g. (linux, amd64, nil).
-func (containerManager *containerManager) OsCompatibility(image *Image) (string, string, error) {
- cmd := &getImageSystemCompatibilityCmd{image: image, containerManager: containerManager.Type}
- log.Debug("Running image inspect...")
- content, err := cmd.RunCmd()
- if err != nil {
- return "", "", err
- }
- content = strings.Trim(content, "\n")
- firstSeparator := strings.Index(content, ",")
- if firstSeparator == -1 {
- return "", "", errorutils.CheckErrorf("couldn't find OS and architecture of image: %s", image.name)
- }
- return content[:firstSeparator], content[firstSeparator+1:], err
-}
-
-func (containerManager *containerManager) GetContainerManagerType() ContainerManagerType {
- return containerManager.Type
-}
-
-// Image push command
-type nativeCmd struct {
- cmdParams []string
- containerManager ContainerManagerType
-}
-
-func (nc *nativeCmd) GetCmd() *exec.Cmd {
- return exec.Command(nc.containerManager.String(), nc.cmdParams...)
-}
-
-func (nc *nativeCmd) RunCmd() error {
- command := nc.GetCmd()
- command.Stderr = os.Stderr
- command.Stdout = os.Stderr
- if nc.containerManager == DockerClient {
- command.Env = append(os.Environ(), "DOCKER_SCAN_SUGGEST=false")
- }
- return command.Run()
-}
-
-// Image get image id command
-type getImageIdCmd struct {
- image *Image
- containerManager ContainerManagerType
-}
-
-func (getImageId *getImageIdCmd) GetCmd() *exec.Cmd {
- var cmd []string
- cmd = append(cmd, "images")
- cmd = append(cmd, "--format", "{{.ID}}")
- cmd = append(cmd, "--no-trunc")
- cmd = append(cmd, getImageId.image.name)
- return exec.Command(getImageId.containerManager.String(), cmd...)
-}
-
-func (getImageId *getImageIdCmd) RunCmd() (string, error) {
- command := getImageId.GetCmd()
- buffer := bytes.NewBuffer([]byte{})
- command.Stderr = buffer
- command.Stdout = buffer
- err := command.Run()
- return buffer.String(), err
-}
-
-// Get image system compatibility details
-type getImageSystemCompatibilityCmd struct {
- image *Image
- containerManager ContainerManagerType
-}
-
-func (getImageSystemCompatibilityCmd *getImageSystemCompatibilityCmd) GetCmd() *exec.Cmd {
- var cmd []string
- cmd = append(cmd, "image")
- cmd = append(cmd, "inspect")
- cmd = append(cmd, getImageSystemCompatibilityCmd.image.name)
- cmd = append(cmd, "--format")
- cmd = append(cmd, "{{ .Os}},{{ .Architecture}}")
- return exec.Command(getImageSystemCompatibilityCmd.containerManager.String(), cmd...)
-}
-
-func (getImageSystemCompatibilityCmd *getImageSystemCompatibilityCmd) RunCmd() (string, error) {
- command := getImageSystemCompatibilityCmd.GetCmd()
- buffer := bytes.NewBuffer([]byte{})
- command.Stderr = buffer
- command.Stdout = buffer
- err := command.Run()
- return buffer.String(), err
-}
-
-// Login command
-type LoginCmd struct {
- DockerRegistry string
- Username string
- Password string
- containerManager ContainerManagerType
-}
-
-func (loginCmd *LoginCmd) GetCmd() *exec.Cmd {
- if coreutils.IsWindows() {
- return exec.Command("cmd", "/C", "echo", "%CONTAINER_MANAGER_PASS%|", "docker", "login", loginCmd.DockerRegistry, "--username", loginCmd.Username, "--password-stdin")
- }
- cmd := "echo $CONTAINER_MANAGER_PASS " + fmt.Sprintf(`| `+loginCmd.containerManager.String()+` login %s --username="%s" --password-stdin`, loginCmd.DockerRegistry, loginCmd.Username)
- return exec.Command("sh", "-c", cmd)
-}
-
-func (loginCmd *LoginCmd) RunCmd() error {
- command := loginCmd.GetCmd()
- command.Stderr = os.Stderr
- command.Stdout = os.Stderr
- command.Env = os.Environ()
- command.Env = append(command.Env, "CONTAINER_MANAGER_PASS="+loginCmd.Password)
- return command.Run()
-}
-
-// First we'll try to log in assuming a proxy-less tag (e.g. "registry-address/docker-repo/image:ver").
-// If fails, we will try assuming a reverse proxy tag (e.g. "registry-address-docker-repo/image:ver").
-func ContainerManagerLogin(imageRegistry string, config *ContainerManagerLoginConfig, containerManager ContainerManagerType) error {
- username := config.ServerDetails.User
- password := config.ServerDetails.Password
- // If access-token exists, perform login with it.
- if config.ServerDetails.AccessToken != "" {
- if username == "" {
- username = auth.ExtractUsernameFromAccessToken(config.ServerDetails.AccessToken)
- }
- password = config.ServerDetails.AccessToken
- }
- // Perform login.
- cmd := &LoginCmd{DockerRegistry: imageRegistry, Username: username, Password: password, containerManager: containerManager}
- err := cmd.RunCmd()
- if exitCode := coreutils.GetExitCode(err, 0, 0, false); exitCode == coreutils.ExitCodeNoError {
- // Login succeeded
- return nil
- }
- log.Debug(containerManager.String()+" login while assuming proxy-less failed:", err)
- indexOfSlash := strings.Index(imageRegistry, "/")
- if indexOfSlash < 0 {
- return errorutils.CheckErrorf(LoginFailureMessage, containerManager.String(), imageRegistry, containerManager.String())
- }
- cmd = &LoginCmd{DockerRegistry: imageRegistry[:indexOfSlash], Username: config.ServerDetails.User, Password: config.ServerDetails.Password}
- err = cmd.RunCmd()
- if err != nil {
- // Login failed for both attempts
- return errorutils.CheckErrorf(LoginFailureMessage,
- containerManager.String(), fmt.Sprintf("%s, %s", imageRegistry, imageRegistry[:indexOfSlash]), containerManager.String()+" "+err.Error())
- }
- // Login succeeded
- return nil
-}
-
-// Version command
-// Docker-client provides an API for interacting with the Docker daemon. This cmd should be used for docker client only.
-type VersionCmd struct{}
-
-func (versionCmd *VersionCmd) GetCmd() *exec.Cmd {
- var cmd []string
- cmd = append(cmd, "docker")
- cmd = append(cmd, "version")
- cmd = append(cmd, "--format", "{{.Client.APIVersion}}")
- return exec.Command(cmd[0], cmd[1:]...)
-}
-
-func (versionCmd *VersionCmd) RunCmd() (string, error) {
- command := versionCmd.GetCmd()
- buffer := bytes.NewBuffer([]byte{})
- command.Stderr = buffer
- command.Stdout = buffer
- err := command.Run()
- return buffer.String(), err
-}
-
-func ValidateClientApiVersion() error {
- cmd := &VersionCmd{}
- // 'docker version' may return 1 in case of errors from daemon. We should ignore this kind of error.
- content, err := cmd.RunCmd()
- content = strings.TrimSpace(content)
- if !ApiVersionRegex.Match([]byte(content)) {
- // The Api version is expected to be 'major.minor'. Anything else should return an error.
- log.Error("The Docker client Api version is expected to be 'major.minor'. The actual output is:", content)
- return errorutils.CheckError(err)
- }
- return utils.ValidateMinimumVersion(utils.DockerApi, content, MinSupportedApiVersion)
-}
diff --git a/artifactory/utils/container/image.go b/artifactory/utils/container/image.go
deleted file mode 100644
index 7738f9113..000000000
--- a/artifactory/utils/container/image.go
+++ /dev/null
@@ -1,183 +0,0 @@
-package container
-
-import (
- "errors"
- "net/http"
- "path"
- "strconv"
- "strings"
-
- "github.com/jfrog/jfrog-client-go/artifactory"
- "github.com/jfrog/jfrog-client-go/utils/errorutils"
- "github.com/jfrog/jfrog-client-go/utils/log"
-)
-
-type Image struct {
- // Image name includes the registry domain, image base name and image tag e.g.: my-registry:port/docker-local/hello-world:latest.
- name string
-}
-
-func NewImage(imageTag string) *Image {
- return &Image{name: imageTag}
-}
-
-// Get image name
-func (image *Image) Name() string {
- return image.name
-}
-
-// Get image name from tag by removing the prefixed registry hostname.
-// e.g.: https://my-registry/docker-local/hello-world:latest. -> docker-local/hello-world:latest
-func (image *Image) GetImageLongNameWithTag() (string, error) {
- if err := image.validateTag(); err != nil {
- return "", err
- }
- indexOfLastSlash := strings.Index(image.name, "/")
- indexOfLastColon := strings.LastIndex(image.name, ":")
- if indexOfLastColon < 0 || indexOfLastColon < indexOfLastSlash {
- log.Info("The image '" + image.name + "' does not include tag. Using the 'latest' tag.")
- image.name += ":latest"
- }
- return image.name[indexOfLastSlash+1:], nil
-}
-
-// Get image base name by removing the prefixed registry hostname and the tag.
-// e.g.: https://my-registry/docker-local/hello-world:latest. -> docker-local/hello-world
-func (image *Image) GetImageLongName() (string, error) {
- imageName, err := image.GetImageLongNameWithTag()
- if err != nil {
- return "", err
- }
- tagIndex := strings.Index(imageName, ":")
- return imageName[:tagIndex], nil
-}
-
-func (image *Image) validateTag() error {
- if !strings.Contains(image.name, "/") {
- return errorutils.CheckErrorf("The image '%s' is missing '/' which indicates the image name/tag", image.name)
- }
- return nil
-}
-
-// Get image base name by removing the prefixed registry hostname and the tag.
-// e.g.: https://my-registry/docker-local/hello-world:latest. -> hello-world
-func (image *Image) GetImageShortName() (string, error) {
- imageName, err := image.GetImageShortNameWithTag()
- if err != nil {
- return "", err
- }
- tagIndex := strings.LastIndex(imageName, ":")
- if tagIndex != -1 {
- return imageName[:tagIndex], nil
- }
- return imageName, nil
-}
-
-// Get image base name by removing the prefixed registry hostname.
-// e.g.: https://my-registry/docker-local/hello-world:latest. -> hello-world:latest
-func (image *Image) GetImageShortNameWithTag() (string, error) {
- imageName, err := image.GetImageLongNameWithTag()
- if err != nil {
- return "", err
- }
- indexOfSlash := strings.LastIndex(imageName, "/")
- if indexOfSlash != -1 {
- return imageName[indexOfSlash+1:], nil
-
- }
- return imageName, nil
-}
-
-// GetImageLongNameWithoutRepoWithTag removes the registry hostname and repository name, returning the organization and image name with the tag.
-// e.g., "docker-local/myorg/hello-world:latest" -> "myorg/hello-world:latest"
-// e.g., "docker-local/hello-world:latest" -> "hello-world:latest"
-func (image *Image) GetImageLongNameWithoutRepoWithTag() (string, error) {
- longName, err := image.GetImageLongNameWithTag()
- if err != nil {
- return "", err
- }
- parts := strings.Split(longName, "/")
- if len(parts) > 1 {
- return strings.Join(parts[1:], "/"), nil
- }
- return longName, nil
-}
-
-// Get image tag name of an image.
-// e.g.: https://my-registry/docker-local/hello-world:latest. -> latest
-func (image *Image) GetImageTag() (string, error) {
- imageName, err := image.GetImageLongNameWithTag()
- if err != nil {
- return "", err
- }
- tagIndex := strings.Index(imageName, ":")
- if tagIndex == -1 {
- return "", errorutils.CheckErrorf("unexpected image name '%s'. Failed to get image tag.", image.Name())
- }
- return imageName[tagIndex+1:], nil
-}
-
-func (image *Image) GetRegistry() (string, error) {
- if err := image.validateTag(); err != nil {
- return "", err
- }
- indexOfLastSlash := strings.Index(image.name, "/")
- if indexOfLastSlash == -1 {
- return "", errorutils.CheckErrorf("unexpected image name '%s'. Failed to get registry.", image.Name())
- }
- return image.name[:indexOfLastSlash], nil
-}
-
-// Returns the physical Artifactory repository name of the pulled/pushed image, by reading a response header from Artifactory.
-func (image *Image) GetRemoteRepo(serviceManager artifactory.ArtifactoryServicesManager) (string, error) {
- containerRegistryUrl, err := image.GetRegistry()
- if err != nil {
- return "", err
- }
- longImageName, err := image.GetImageLongName()
- if err != nil {
- return "", err
- }
- imageTag, err := image.GetImageTag()
- if err != nil {
- return "", err
- }
- var isSecure bool
- if rtUrl := serviceManager.GetConfig().GetServiceDetails().GetUrl(); strings.HasPrefix(rtUrl, "https") {
- isSecure = true
- }
- // Build the request URL.
- endpoint := buildRequestUrl(longImageName, imageTag, containerRegistryUrl, isSecure)
- artHttpDetails := serviceManager.GetConfig().GetServiceDetails().CreateHttpClientDetails()
- artHttpDetails.Headers["accept"] = "application/vnd.docker.distribution.manifest.v1+prettyjws, application/json, application/vnd.oci.image.manifest.v1+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json"
- resp, _, err := serviceManager.Client().SendHead(endpoint, &artHttpDetails)
- if err != nil {
- return "", err
- }
- if resp.StatusCode == http.StatusForbidden {
- return "", errors.New(getStatusForbiddenErrorMessage())
- }
- if resp.StatusCode != http.StatusOK {
- return "", errorutils.CheckErrorf("error while getting docker repository name. Artifactory response: %s", resp.Status)
- }
- if dockerRepo := resp.Header["X-Artifactory-Docker-Registry"]; len(dockerRepo) != 0 {
- return dockerRepo[0], nil
- }
- return "", errors.New("couldn't find 'X-Artifactory-Docker-Registry' header docker repository in artifactory")
-}
-
-// Returns the name of the repository containing the image in Artifactory.
-func buildRequestUrl(longImageName, imageTag, containerRegistryUrl string, https bool) string {
- endpoint := path.Join(containerRegistryUrl, "v2", longImageName, "manifests", imageTag)
- if https {
- return "https://" + endpoint
- }
- return "http://" + endpoint
-}
-
-func getStatusForbiddenErrorMessage() string {
- errorMessage := "error while getting docker repository name. Artifactory response: " + strconv.Itoa(http.StatusForbidden) +
- ", Possible causes include: \n- Xray scan in progress \n- Xray policy violations \n- insufficient permissions\n- invalid authentication method\n- disabled anonymous access\n- missing Docker manifests." +
- "\nPlease verify the above factors to resolve the issue."
- return errorMessage
-}
diff --git a/artifactory/utils/container/image_test.go b/artifactory/utils/container/image_test.go
deleted file mode 100644
index f11b35c5d..000000000
--- a/artifactory/utils/container/image_test.go
+++ /dev/null
@@ -1,208 +0,0 @@
-package container
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestGetImageLongName(t *testing.T) {
- var imageTags = []struct {
- in string
- expected string
- }{
- {"domain:8080/path:1.0", "path"},
- {"domain:8080/path/in/artifactory:1.0", "path/in/artifactory"},
- {"domain:8080/path/in/artifactory", "path/in/artifactory"},
- {"domain/path:1.0", "path"},
- {"domain/path/in/artifactory:1.0", "path/in/artifactory"},
- {"domain/path/in/artifactory", "path/in/artifactory"},
- }
-
- for _, v := range imageTags {
- result, err := NewImage(v.in).GetImageLongName()
- assert.NoError(t, err)
- if result != v.expected {
- t.Errorf("GetImageLongName(\"%s\") => '%s', want '%s'", v.in, result, v.expected)
- }
- }
- // Validate failure upon missing image name
- _, err := NewImage("domain").GetImageLongName()
- assert.Error(t, err)
-
-}
-
-func TestGetImageShortName(t *testing.T) {
- var imageTags = []struct {
- in string
- expected string
- }{
- {"domain:8080/path:1.0", "path"},
- {"domain:8080/path/in/artifactory:1.0", "artifactory"},
- {"domain:8080/path/in/artifactory", "artifactory"},
- {"domain/path:1.0", "path"},
- {"domain/path/in/artifactory:1.0", "artifactory"},
- {"domain/path/in/artifactory", "artifactory"},
- }
-
- for _, v := range imageTags {
- result, err := NewImage(v.in).GetImageShortName()
- assert.NoError(t, err)
- if result != v.expected {
- t.Errorf("GetImageShortName(\"%s\") => '%s', want '%s'", v.in, result, v.expected)
- }
- }
- // Validate failure upon missing image name
- _, err := NewImage("domain").GetImageShortName()
- assert.Error(t, err)
-
-}
-
-func TestGetImageLongNameWithTag(t *testing.T) {
- var imageTags = []struct {
- in string
- expected string
- }{
- {"domain:8080/path:1.0", "path:1.0"},
- {"domain:8080/path/in/artifactory:1.0", "path/in/artifactory:1.0"},
- {"domain:8080/path/in/artifactory", "path/in/artifactory:latest"},
- {"domain/path:1.0", "path:1.0"},
- {"domain/path/in/artifactory:1.0", "path/in/artifactory:1.0"},
- {"domain/path/in/artifactory", "path/in/artifactory:latest"},
- }
-
- for _, v := range imageTags {
- result, err := NewImage(v.in).GetImageLongNameWithTag()
- assert.NoError(t, err)
- if result != v.expected {
- t.Errorf("GetImageLongNameWithTag(\"%s\") => '%s', want '%s'", v.in, result, v.expected)
- }
- }
- // Validate failure upon missing image name
- _, err := NewImage("domain").GetImageLongNameWithTag()
- assert.Error(t, err)
-}
-
-func TestGetImageLongNameWithoutRepoWithTag(t *testing.T) {
- var imageTags = []struct {
- in string
- expected string
- }{
- {"domain:8080/repo-name/hello-world:latest", "hello-world:latest"},
- {"domain/repo-name/hello-world:latest", "hello-world:latest"},
- {"domain/repo-name/org-name/hello-world:latest", "org-name/hello-world:latest"},
- {"domain/repo-name/org-name/hello-world", "org-name/hello-world:latest"},
- }
-
- for _, v := range imageTags {
- result, err := NewImage(v.in).GetImageLongNameWithoutRepoWithTag()
- assert.NoError(t, err)
- assert.Equal(t, v.expected, result)
- }
- // Validate failure upon missing image name
- _, err := NewImage("domain").GetImageLongNameWithoutRepoWithTag()
- assert.Error(t, err)
-}
-
-func TestGetImageShortNameWithTag(t *testing.T) {
- var imageTags = []struct {
- in string
- expected string
- }{
- {"domain:8080/path:1.0", "path:1.0"},
- {"domain:8080/path/in/artifactory:1.0", "artifactory:1.0"},
- {"domain:8080/path/in/artifactory", "artifactory:latest"},
- {"domain/path:1.0", "path:1.0"},
- {"domain/path/in/artifactory:1.0", "artifactory:1.0"},
- {"domain/path/in/artifactory", "artifactory:latest"},
- }
-
- for _, v := range imageTags {
- result, err := NewImage(v.in).GetImageShortNameWithTag()
- assert.NoError(t, err)
- if result != v.expected {
- t.Errorf("GetImageShortNameWithTag(\"%s\") => '%s', want '%s'", v.in, result, v.expected)
- }
- }
- // Validate failure upon missing image name
- _, err := NewImage("domain").GetImageLongNameWithTag()
- assert.Error(t, err)
-}
-
-func TestResolveRegistryFromTag(t *testing.T) {
- var imageTags = []struct {
- in string
- expected string
- expectingError bool
- }{
- {"domain:8080/path:1.0", "domain:8080", false},
- {"domain:8080/path/in/artifactory:1.0", "domain:8080", false},
- {"domain:8080/path/in/artifactory", "domain:8080", false},
- {"domain/path:1.0", "domain", false},
- {"domain/path/in/artifactory:1.0", "domain", false},
- {"domain/path/in/artifactory", "domain", false},
- {"domain:8081", "", true},
- }
-
- for _, v := range imageTags {
- result, err := NewImage(v.in).GetRegistry()
- if err != nil && !v.expectingError {
- t.Error(err.Error())
- }
- if result != v.expected {
- t.Errorf("ResolveRegistryFromTag(\"%s\") => '%s', expected '%s'", v.in, result, v.expected)
- }
- }
-}
-
-func TestDockerClientApiVersionRegex(t *testing.T) {
- var versionStrings = []struct {
- in string
- expected bool
- }{
- {"1", false},
- {"1.1", true},
- {"1.11", true},
- {"12.12", true},
- {"1.1.11", false},
- {"1.illegal", false},
- {"1 11", false},
- }
-
- for _, v := range versionStrings {
- result := ApiVersionRegex.Match([]byte(v.in))
- if result != v.expected {
- t.Errorf("Version(\"%s\") => '%v', want '%v'", v.in, result, v.expected)
- }
- }
-}
-
-func TestBuildRemoteRepoUrl(t *testing.T) {
- var data = []struct {
- image string
- isSecure bool
- expectedRepo string
- }{
- {"localhost:8082/docker-local/hello-world:123", true, "https://localhost:8082/v2/docker-local/hello-world/manifests/123"},
- {"localhost:8082/docker-local/hello-world:latest", true, "https://localhost:8082/v2/docker-local/hello-world/manifests/latest"},
- {"localhost:8082/docker-local/hello-world:latest", false, "http://localhost:8082/v2/docker-local/hello-world/manifests/latest"},
- // With proxy
- {"jfrog-docker-local.jfrog.io/hello-world:123", true, "https://jfrog-docker-local.jfrog.io/v2/hello-world/manifests/123"},
- {"jfrog-docker-local.jfrog.io/hello-world:latest", true, "https://jfrog-docker-local.jfrog.io/v2/hello-world/manifests/latest"},
- {"jfrog-docker-local.jfrog.io/hello-world:123", false, "http://jfrog-docker-local.jfrog.io/v2/hello-world/manifests/123"},
- }
- for _, v := range data {
- testImae := NewImage(v.image)
- containerRegistryUrl, err := testImae.GetRegistry()
- assert.NoError(t, err)
-
- longImageName, err := testImae.GetImageLongName()
- assert.NoError(t, err)
-
- imageTag, err := testImae.GetImageTag()
- assert.NoError(t, err)
-
- actualRepo := buildRequestUrl(longImageName, imageTag, containerRegistryUrl, v.isSecure)
- assert.Equal(t, v.expectedRepo, actualRepo)
- }
-}
diff --git a/artifactory/utils/container/localagent.go b/artifactory/utils/container/localagent.go
deleted file mode 100644
index 7d44734a1..000000000
--- a/artifactory/utils/container/localagent.go
+++ /dev/null
@@ -1,196 +0,0 @@
-package container
-
-import (
- "fmt"
- "net/http"
- "path"
- "strings"
-
- buildinfo "github.com/jfrog/build-info-go/entities"
- "github.com/jfrog/jfrog-client-go/artifactory"
- "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
- "github.com/jfrog/jfrog-client-go/utils/errorutils"
- "github.com/jfrog/jfrog-client-go/utils/log"
-)
-
-// Build-info builder for local agents tools such as: Docker or Podman.
-type localAgentBuildInfoBuilder struct {
- buildInfoBuilder *buildInfoBuilder
- // Name of the container CLI tool e.g. docker
- containerManager ContainerManager
- commandType CommandType
-}
-
-// Create new build info builder container CLI tool
-func NewLocalAgentBuildInfoBuilder(image *Image, repository, buildName, buildNumber, project string, serviceManager artifactory.ArtifactoryServicesManager, commandType CommandType, containerManager ContainerManager) (*localAgentBuildInfoBuilder, error) {
- imageSha2, err := containerManager.Id(image)
- if err != nil {
- return nil, err
- }
- builder, err := newBuildInfoBuilder(image, repository, buildName, buildNumber, project, serviceManager)
- if err != nil {
- return nil, err
- }
- builder.setImageSha2(imageSha2)
- return &localAgentBuildInfoBuilder{
- buildInfoBuilder: builder,
- containerManager: containerManager,
- commandType: commandType,
- }, err
-}
-
-func (labib *localAgentBuildInfoBuilder) GetLayers() *[]utils.ResultItem {
- return &labib.buildInfoBuilder.imageLayers
-}
-
-func (labib *localAgentBuildInfoBuilder) SetSkipTaggingLayers(skipTaggingLayers bool) {
- labib.buildInfoBuilder.skipTaggingLayers = skipTaggingLayers
-}
-
-// Create build-info for a docker image.
-func (labib *localAgentBuildInfoBuilder) Build(module string) (*buildinfo.BuildInfo, error) {
- // Search for image build-info.
- candidateLayers, manifest, err := labib.searchImage()
- if err != nil {
- log.Warn("Failed to collect build-info. No layer(s) was found for image:'" + labib.buildInfoBuilder.image.name + "'. Hint, try to delete the image from the local cache and rerun the command")
- log.Debug(err.Error())
- return nil, nil
- } else {
- log.Debug("Found manifest.json with the following layers to create build-info:", candidateLayers)
- }
- // Create build-info from search results.
- return labib.buildInfoBuilder.createBuildInfo(labib.commandType, manifest, candidateLayers, module)
-}
-
-// Search an image in Artifactory and validate its sha2 with local image.
-func (labib *localAgentBuildInfoBuilder) searchImage() (map[string]*utils.ResultItem, *manifest, error) {
- longImageName, err := labib.buildInfoBuilder.image.GetImageLongNameWithTag()
- if err != nil {
- return nil, nil, err
- }
- imagePath := strings.Replace(longImageName, ":", "/", 1)
- manifestPathsCandidates := getManifestPaths(imagePath, labib.buildInfoBuilder.getSearchableRepo(), labib.commandType)
- log.Debug("Start searching for image manifest.json")
- for _, path := range manifestPathsCandidates {
- log.Debug(`Searching in:"` + path + `"`)
- resultMap, err := labib.search(path)
- if err != nil {
- return nil, nil, err
- }
- manifest, err := getManifest(resultMap, labib.buildInfoBuilder.serviceManager, labib.buildInfoBuilder.repositoryDetails.key)
- if err != nil {
- return nil, nil, err
- }
- if manifest != nil && labib.isVerifiedManifest(manifest) {
- return resultMap, manifest, nil
- }
- }
- return nil, nil, errorutils.CheckErrorf(imageNotFoundErrorMessage, labib.buildInfoBuilder.image.name)
-}
-
-// Search image layers in artifactory by the provided image path in artifactory.
-// If fat-manifest is found, use it to find our image in Artifactory.
-func (labib *localAgentBuildInfoBuilder) search(imagePathPattern string) (resultMap map[string]*utils.ResultItem, err error) {
- resultMap, err = performSearch(imagePathPattern, labib.buildInfoBuilder.serviceManager)
- if err != nil {
- log.Debug("Failed to search marker layer. Error:", err.Error())
- return
- }
- // Validate there are no .marker layers.
- totalDownloaded, err := downloadMarkerLayersToRemoteCache(resultMap, labib.buildInfoBuilder)
- if err != nil {
- log.Debug("Failed to download marker layer. Error:", err.Error())
- return nil, err
- }
- if totalDownloaded > 0 {
- // Search again after .marker layer were downloaded.
- if resultMap, err = performSearch(imagePathPattern, labib.buildInfoBuilder.serviceManager); err != nil {
- log.Debug("Failed to research layers after download marker layers. Error:", err.Error())
- return
- }
- }
- // Check if search results contain multi-architecture images (fat-manifest).
- if searchResult, ok := resultMap["list.manifest.json"]; labib.commandType == Pull && ok {
- // In case of a fat-manifest, Artifactory will create two folders.
- // One folder named as the image tag, which contains the fat manifest.
- // The second folder, named as image's manifest digest, contains the image layers and the image's manifest.
- log.Debug("Found list.manifest.json (fat-manifest). Searching for the image manifest digest in list.manifest.json")
- var digest string
- digest, err = labib.getImageDigestFromFatManifest(*searchResult)
- if err == nil && digest != "" {
- // Remove tag from pattern, place the manifest digest instead.
- imagePathPattern = strings.Replace(imagePathPattern, "/*", "", 1)
- imagePathPattern = path.Join(imagePathPattern[:strings.LastIndex(imagePathPattern, "/")], strings.Replace(digest, ":", "__", 1), "*")
- // Retry search.
- return labib.search(imagePathPattern)
- }
- log.Debug("Couldn't find matching digest in list.manifest.json")
- }
- return resultMap, err
-}
-
-// Verify manifest by comparing sha256, which references to the image digest. If there is no match, return nil.
-func (labib *localAgentBuildInfoBuilder) isVerifiedManifest(imageManifest *manifest) bool {
- if imageManifest.Config.Digest != labib.buildInfoBuilder.imageSha2 {
- log.Debug(`Found incorrect manifest.json file. Expects digest "` + labib.buildInfoBuilder.imageSha2 + `" found "` + imageManifest.Config.Digest)
- return false
- }
- return true
-}
-
-func (labib *localAgentBuildInfoBuilder) getImageDigestFromFatManifest(fatManifest utils.ResultItem) (string, error) {
- var fatManifestContent *FatManifest
- if err := downloadLayer(fatManifest, &fatManifestContent, labib.buildInfoBuilder.serviceManager, labib.buildInfoBuilder.repositoryDetails.key); err != nil {
- log.Debug(`failed to unmarshal fat-manifest`)
- return "", err
- }
- imageOs, imageArch, err := labib.containerManager.OsCompatibility(labib.buildInfoBuilder.image)
- if err != nil {
- return "", err
- }
- return searchManifestDigest(imageOs, imageArch, fatManifestContent.Manifests), nil
-}
-
-// 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,
-// 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.
-// This function download all the marker layers into the remote cache repository.
-func downloadMarkerLayersToRemoteCache(resultMap map[string]*utils.ResultItem, builder *buildInfoBuilder) (int, error) {
- if !builder.repositoryDetails.isRemote || len(resultMap) == 0 {
- return 0, nil
- }
- totalDownloaded := 0
- remoteRepo := builder.repositoryDetails.key
- imageName, err := builder.image.GetImageShortName()
- if err != nil {
- return 0, err
- }
- clientDetails := builder.serviceManager.GetConfig().GetServiceDetails().CreateHttpClientDetails()
- // Search for marker layers
- for _, layerData := range resultMap {
- if strings.HasSuffix(layerData.Name, markerLayerSuffix) {
- log.Debug(fmt.Sprintf("Downloading %s layer into remote repository cache...", layerData.Name))
- baseUrl := builder.serviceManager.GetConfig().GetServiceDetails().GetUrl()
- endpoint := "api/docker/" + remoteRepo + "/v2/" + imageName + "/blobs/" + toNoneMarkerLayer(layerData.Name)
- resp, body, err := builder.serviceManager.Client().SendHead(baseUrl+endpoint, &clientDetails)
- if err != nil {
- log.Debug("Failed to download marker layer. Error:", err.Error())
- return totalDownloaded, err
- }
- if err = errorutils.CheckResponseStatusWithBody(resp, body, http.StatusOK); err != nil {
- log.Debug("Failed to download marker layer. HTTP stats code:", resp.StatusCode)
- return totalDownloaded, err
- }
- totalDownloaded++
- }
- }
- return totalDownloaded, nil
-}
-
-func handleForeignLayer(layerMediaType, layerFileName string) error {
- // Allow missing layer to be of a foreign type.
- if layerMediaType == foreignLayerMediaType {
- log.Info(fmt.Sprintf("Foreign layer: %s is missing in Artifactory and therefore will not be added to the build-info.", layerFileName))
- return nil
- }
- return errorutils.CheckErrorf("Could not find layer: %s in Artifactory", layerFileName)
-}
diff --git a/artifactory/utils/container/manifest.go b/artifactory/utils/container/manifest.go
deleted file mode 100644
index 210d5916d..000000000
--- a/artifactory/utils/container/manifest.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package container
-
-import (
- "path"
- "strings"
-
- "github.com/jfrog/jfrog-client-go/artifactory"
- "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
-)
-
-// To unmarshal config layer file
-type configLayer struct {
- History []history `json:"history,omitempty"`
-}
-
-type history struct {
- Created string `json:"created,omitempty"`
- CreatedBy string `json:"created_by,omitempty"`
- EmptyLayer bool `json:"empty_layer,omitempty"`
-}
-
-// To unmarshal manifest.json file
-type manifest struct {
- Config manifestConfig `json:"config,omitempty"`
- Layers []layer `json:"layers,omitempty"`
-}
-
-type manifestConfig struct {
- Digest string `json:"digest,omitempty"`
-}
-
-type layer struct {
- Digest string `json:"digest,omitempty"`
- MediaType string `json:"mediaType,omitempty"`
-}
-
-type FatManifest struct {
- Manifests []ManifestDetails `json:"manifests"`
-}
-
-type ManifestDetails struct {
- Digest string `json:"digest"`
- Platform Platform `json:"platform"`
- Annotations Annotations `json:"annotations"`
-}
-
-type Platform struct {
- Architecture string `json:"architecture"`
- Os string `json:"os"`
-}
-
-// Annotations for attestation manifests.
-type Annotations struct {
- ReferenceDigest string `json:"vnd.docker.reference.digest"`
- ReferenceType string `json:"vnd.docker.reference.type"`
-}
-
-// Return all the search patterns in which manifest can be found.
-func getManifestPaths(imagePath, repo string, commandType CommandType) []string {
- // pattern 1: reverse proxy e.g. ecosysjfrog-docker-local.jfrog.io.
- paths := []string{path.Join(repo, imagePath, "*")}
- // pattern 2: proxy-less e.g. orgab.jfrog.team/docker-local.
- endOfRepoNameIndex := strings.Index(imagePath[1:], "/")
- proxylessTag := imagePath[endOfRepoNameIndex+1:]
- paths = append(paths, path.Join(repo, proxylessTag, "*"))
- // If image path includes more than 3 slashes, Artifactory doesn't store this image under 'library', thus we should not look further.
- if commandType != Push && strings.Count(imagePath, "/") <= 3 {
- // pattern 3: reverse proxy - this time with 'library' as part of the path.
- paths = append(paths, path.Join(repo, "library", imagePath, "*"))
- // pattern 4: Assume proxy-less - this time with 'library' as part of the path.
- paths = append(paths, path.Join(repo, "library", proxylessTag, "*"))
- }
- return paths
-}
-
-func getManifest(resultMap map[string]*utils.ResultItem, serviceManager artifactory.ArtifactoryServicesManager, repo string) (imageManifest *manifest, err error) {
- if len(resultMap) == 0 {
- return
- }
- manifestSearchResult, ok := resultMap["manifest.json"]
- if !ok {
- return
- }
- err = downloadLayer(*manifestSearchResult, &imageManifest, serviceManager, repo)
- return
-}
-
-func getFatManifest(resultMap map[string]*utils.ResultItem, serviceManager artifactory.ArtifactoryServicesManager, repo string) (imageFatManifest *FatManifest, err error) {
- if len(resultMap) == 0 {
- return
- }
- manifestSearchResult, ok := resultMap["list.manifest.json"]
- if !ok {
- return
- }
- err = downloadLayer(*manifestSearchResult, &imageFatManifest, serviceManager, repo)
- return
-}
diff --git a/artifactory/utils/container/remoteagent.go b/artifactory/utils/container/remoteagent.go
deleted file mode 100644
index 3bb6dbf22..000000000
--- a/artifactory/utils/container/remoteagent.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package container
-
-import (
- "strings"
-
- buildinfo "github.com/jfrog/build-info-go/entities"
- "github.com/jfrog/jfrog-client-go/artifactory"
- "github.com/jfrog/jfrog-client-go/artifactory/services/utils"
- "github.com/jfrog/jfrog-client-go/utils/errorutils"
- "github.com/jfrog/jfrog-client-go/utils/log"
-)
-
-// Build-info builder for remote agents tools such as: Kaniko, OpenShift CLI (oc), or buildx.
-type RemoteAgentBuildInfoBuilder struct {
- buildInfoBuilder *buildInfoBuilder
- manifestSha2 string
-}
-
-func NewRemoteAgentBuildInfoBuilder(image *Image, repository, buildName, buildNumber, project string, serviceManager artifactory.ArtifactoryServicesManager, manifestSha256 string) (*RemoteAgentBuildInfoBuilder, error) {
- builder, err := newBuildInfoBuilder(image, repository, buildName, buildNumber, project, serviceManager)
- return &RemoteAgentBuildInfoBuilder{
- buildInfoBuilder: builder,
- manifestSha2: manifestSha256,
- }, err
-}
-
-func (rabib *RemoteAgentBuildInfoBuilder) GetLayers() *[]utils.ResultItem {
- return &rabib.buildInfoBuilder.imageLayers
-}
-
-func (rabib *RemoteAgentBuildInfoBuilder) Build(module string) (*buildinfo.BuildInfo, error) {
- // Search for and image in Artifactory.
- results, err := rabib.searchImage()
- if err != nil {
- return nil, err
- }
- // Create build-info based on image manifest.
- if results["manifest.json"] != nil {
- searchResults, manifest, err := rabib.handleManifest(results)
- if err != nil {
- return nil, err
- }
- return rabib.buildInfoBuilder.createBuildInfo(Push, manifest, searchResults, module)
- }
- // Create build-info based on image fat-manifest.
- multiPlatformImages, fatManifestDetails, fatManifest, err := rabib.handleFatManifestImage(results)
- if err != nil {
- return nil, err
- }
- return rabib.buildInfoBuilder.createMultiPlatformBuildInfo(fatManifest, fatManifestDetails, multiPlatformImages, module)
-}
-
-// Search for image manifest and layers in Artifactory.
-func (rabib *RemoteAgentBuildInfoBuilder) handleManifest(resultMap map[string]*utils.ResultItem) (map[string]*utils.ResultItem, *manifest, error) {
- if manifest, ok := resultMap["manifest.json"]; ok {
- if !rabib.isVerifiedManifest(manifest) {
- log.Debug("Manifest verification failed, continuing with SHA-based validation...")
- }
- manifest, err := getManifest(resultMap, rabib.buildInfoBuilder.serviceManager, rabib.buildInfoBuilder.repositoryDetails.key)
- if err != nil {
- return nil, nil, err
- }
- // Manifest may hold 'empty layers'. As a result, promotion will fail to promote the same layer more than once.
- rabib.buildInfoBuilder.imageSha2 = manifest.Config.Digest
- log.Debug("Found manifest.json. Proceeding to create build-info.")
- return resultMap, manifest, nil
- }
- return nil, nil, errorutils.CheckErrorf(`couldn't find image "%s" manifest in Artifactory`, rabib.buildInfoBuilder.image.name)
-}
-
-func (rabib *RemoteAgentBuildInfoBuilder) handleFatManifestImage(results map[string]*utils.ResultItem) (map[string][]*utils.ResultItem, *utils.ResultItem, *FatManifest, error) {
- if fatManifestResult, ok := results["list.manifest.json"]; ok {
- log.Debug("Found list.manifest.json. Proceeding to create build-info.")
- fatManifestRootPath := getFatManifestRoot(fatManifestResult.GetItemRelativeLocation()) + "/*"
- fatManifest, err := getFatManifest(results, rabib.buildInfoBuilder.serviceManager, rabib.buildInfoBuilder.repositoryDetails.key)
- if err != nil {
- return nil, nil, nil, err
- }
- multiPlatformImages, err := performMultiPlatformImageSearch(fatManifestRootPath, rabib.buildInfoBuilder.serviceManager)
- return multiPlatformImages, fatManifestResult, fatManifest, err
- }
- return nil, nil, nil, errorutils.CheckErrorf(`couldn't find image "%s" fat manifest in Artifactory`, rabib.buildInfoBuilder.image.name)
-}
-
-// Search image manifest or fat-manifest of and image.
-func (rabib *RemoteAgentBuildInfoBuilder) searchImage() (resultMap map[string]*utils.ResultItem, err error) {
- longImageName, err := rabib.buildInfoBuilder.image.GetImageLongNameWithTag()
- if err != nil {
- return nil, err
- }
- imagePath := strings.Replace(longImageName, ":", "/", 1)
-
- // Search image's manifest.
- manifestPathsCandidates := getManifestPaths(imagePath, rabib.buildInfoBuilder.getSearchableRepo(), Push)
- log.Debug("Start searching for image manifest.json")
-
- // First try standard tag-based search
- for _, path := range manifestPathsCandidates {
- log.Debug(`Searching in:"` + path + `"`)
- resultMap, err = performSearch(path, rabib.buildInfoBuilder.serviceManager)
- if err != nil {
- return nil, err
- }
- if resultMap == nil {
- continue
- }
- if resultMap["list.manifest.json"] != nil || resultMap["manifest.json"] != nil {
- return resultMap, nil
- }
- }
-
- // If tag-based search failed and we have a SHA, try SHA-based search
- if rabib.manifestSha2 != "" {
- log.Debug("Tag-based search failed. Trying SHA-based search with: " + rabib.manifestSha2)
- // Extract repository path without tag
- repoPath := imagePath[:strings.LastIndex(imagePath, "/")]
- // Convert SHA format from sha256:xxx to sha256__xxx for Artifactory path format
- shaPath := strings.Replace(rabib.manifestSha2, ":", "__", 1)
- // Search for the image using SHA path
- shaSearchPath := repoPath + "/" + shaPath + "/*"
- log.Debug(`Searching by SHA in:"` + shaSearchPath + `"`)
- resultMap, err = performSearch(shaSearchPath, rabib.buildInfoBuilder.serviceManager)
- if err != nil {
- return nil, err
- }
- if resultMap != nil && (resultMap["list.manifest.json"] != nil || resultMap["manifest.json"] != nil) {
- log.Info("Found image by SHA digest in repository")
- return resultMap, nil
- }
- }
-
- return nil, errorutils.CheckErrorf(imageNotFoundErrorMessage, rabib.buildInfoBuilder.image.name)
-}
-
-// Verify manifest's sha256. Returns true if manifest is verified, false otherwise.
-func (rabib *RemoteAgentBuildInfoBuilder) isVerifiedManifest(imageManifest *utils.ResultItem) bool {
- if imageManifest.GetProperty("docker.manifest.digest") != rabib.manifestSha2 {
- manifestDigest := imageManifest.GetProperty("docker.manifest.digest")
- log.Warn("Manifest digest mismatch detected. Local image digest: " + rabib.manifestSha2 + ", Repository digest: " + manifestDigest)
- log.Info("Proceeding with SHA-based validation to ensure correct image identification...")
- return false
- }
- return true
-}
-
-func getFatManifestRoot(fatManifestPath string) string {
- fatManifestPath = strings.TrimSuffix(fatManifestPath, "/")
- return fatManifestPath[:strings.LastIndex(fatManifestPath, "/")]
-}
diff --git a/artifactory/utils/testdata/container/imageTagWithDigest b/artifactory/utils/testdata/container/imageTagWithDigest
deleted file mode 100644
index 4c2cfb3a3..000000000
--- a/artifactory/utils/testdata/container/imageTagWithDigest
+++ /dev/null
@@ -1 +0,0 @@
-my-image-tag@sha256:12345
\ No newline at end of file
diff --git a/common/tests/docker.go b/common/tests/docker.go
deleted file mode 100644
index 69fb55340..000000000
--- a/common/tests/docker.go
+++ /dev/null
@@ -1,192 +0,0 @@
-package tests
-
-import (
- "io"
- "os/exec"
- "path"
- "testing"
-
- gofrogcmd "github.com/jfrog/gofrog/io"
- "github.com/jfrog/jfrog-cli-core/v2/artifactory/utils/container"
- "github.com/stretchr/testify/assert"
-)
-
-// Image get parent image id command
-type BuildDockerImage struct {
- // The build command builds images from a Dockerfile and a context.
- // A build's context is the set of files located in the specified PATH.
- // The build process can refer to any of the files in the context.
- // For example, The build can use a COPY instruction to reference a file in the context.
- buildContext string
- dockerFileName string
- imageName string
- containerManager container.ContainerManagerType
-}
-
-func NewBuildDockerImage(imageTag, dockerFilePath string, containerManager container.ContainerManagerType) *BuildDockerImage {
- return &BuildDockerImage{imageName: imageTag, buildContext: dockerFilePath, containerManager: containerManager}
-}
-
-func (image *BuildDockerImage) SetDockerFileName(name string) *BuildDockerImage {
- image.dockerFileName = name
- return image
-}
-
-func (image *BuildDockerImage) GetCmd() *exec.Cmd {
- var cmd []string
- cmd = append(cmd, "build")
- cmd = append(cmd, "--tag", image.imageName)
- if image.dockerFileName != "" {
- cmd = append(cmd, "--file", path.Join(image.buildContext, image.dockerFileName))
-
- }
- cmd = append(cmd, image.buildContext)
- return exec.Command(image.containerManager.String(), cmd...)
-}
-
-func (image *BuildDockerImage) GetEnv() map[string]string {
- return map[string]string{}
-}
-
-func (image *BuildDockerImage) GetStdWriter() io.WriteCloser {
- return nil
-}
-
-func (image *BuildDockerImage) GetErrWriter() io.WriteCloser {
- return nil
-}
-
-// The ExecDockerImage command runs a new command in a running container.
-type ExecDockerImage struct {
- Args []string
- errCloser io.WriteCloser
- stdWriter io.WriteCloser
- containerManager container.ContainerManagerType
-}
-
-func NewExecDockerImage(containerManager container.ContainerManagerType, args ...string) *ExecDockerImage {
- return &ExecDockerImage{Args: args, containerManager: containerManager}
-}
-
-func (e *ExecDockerImage) GetCmd() *exec.Cmd {
- var cmd []string
- cmd = append(cmd, "exec")
- cmd = append(cmd, e.Args...)
- return exec.Command(e.containerManager.String(), cmd...)
-}
-
-func (e *ExecDockerImage) GetEnv() map[string]string {
- return map[string]string{}
-}
-
-func (e *ExecDockerImage) GetStdWriter() io.WriteCloser {
- return e.stdWriter
-}
-
-func (e *ExecDockerImage) SetStdWriter(writer io.WriteCloser) {
- e.stdWriter = writer
-}
-
-func (e *ExecDockerImage) GetErrWriter() io.WriteCloser {
- return e.errCloser
-}
-
-func (e *ExecDockerImage) SetErrWriter(writer io.WriteCloser) {
- e.errCloser = writer
-}
-
-type RunDockerImage struct {
- Args []string
- containerManager container.ContainerManagerType
-}
-
-func NewRunDockerImage(containerManager container.ContainerManagerType, args ...string) *RunDockerImage {
- return &RunDockerImage{Args: args, containerManager: containerManager}
-}
-
-func (run *RunDockerImage) GetCmd() *exec.Cmd {
- var cmd []string
- cmd = append(cmd, "run")
- cmd = append(cmd, run.Args...)
- return exec.Command(run.containerManager.String(), cmd...)
-}
-
-func (run *RunDockerImage) GetEnv() map[string]string {
- return map[string]string{}
-}
-
-func (run *RunDockerImage) GetStdWriter() io.WriteCloser {
- return nil
-}
-
-func (run *RunDockerImage) GetErrWriter() io.WriteCloser {
- return nil
-}
-
-type DeleteDockerImage struct {
- imageTag string
- containerManager container.ContainerManagerType
-}
-
-func NewDeleteDockerImage(imageTag string, containerManager container.ContainerManagerType) *DeleteDockerImage {
- return &DeleteDockerImage{imageTag: imageTag, containerManager: containerManager}
-}
-
-func (image *DeleteDockerImage) GetCmd() *exec.Cmd {
- var cmd []string
- cmd = append(cmd, "image")
- cmd = append(cmd, "rm")
- cmd = append(cmd, image.imageTag)
- return exec.Command(image.containerManager.String(), cmd...)
-}
-
-func (image *DeleteDockerImage) GetEnv() map[string]string {
- return map[string]string{}
-}
-
-func (image *DeleteDockerImage) GetStdWriter() io.WriteCloser {
- return nil
-}
-
-func (image *DeleteDockerImage) GetErrWriter() io.WriteCloser {
- return nil
-}
-
-type DeleteContainer struct {
- containerName string
- containerManager container.ContainerManagerType
-}
-
-func NewDeleteContainer(containerName string, containerManager container.ContainerManagerType) *DeleteContainer {
- return &DeleteContainer{containerName: containerName, containerManager: containerManager}
-}
-
-func (image *DeleteContainer) GetCmd() *exec.Cmd {
- var cmd []string
- cmd = append(cmd, "rm")
- cmd = append(cmd, "--force")
- cmd = append(cmd, image.containerName)
- return exec.Command(image.containerManager.String(), cmd...)
-}
-
-func (image *DeleteContainer) GetEnv() map[string]string {
- return map[string]string{}
-}
-
-func (image *DeleteContainer) GetStdWriter() io.WriteCloser {
- return nil
-}
-
-func (image *DeleteContainer) GetErrWriter() io.WriteCloser {
- return nil
-}
-
-func DeleteTestImage(t *testing.T, imageTag string, containerManagerType container.ContainerManagerType) {
- imageBuilder := NewDeleteDockerImage(imageTag, containerManagerType)
- assert.NoError(t, gofrogcmd.RunCmd(imageBuilder))
-}
-
-func DeleteTestContainer(t *testing.T, containerName string, containerManagerType container.ContainerManagerType) {
- containerDelete := NewDeleteContainer(containerName, containerManagerType)
- assert.NoError(t, gofrogcmd.RunCmd(containerDelete))
-}