From da488ef26725a90767574861d09a8a426f3aedcf Mon Sep 17 00:00:00 2001 From: Kanishk Date: Wed, 8 Oct 2025 20:22:31 +0530 Subject: [PATCH] deleted common container code --- .../utils/commandsummary/buildinfosummary.go | 17 +- artifactory/utils/container/buildinfo.go | 640 ------------------ artifactory/utils/container/buildinfo_test.go | 163 ----- .../utils/container/containermanager.go | 257 ------- artifactory/utils/container/image.go | 183 ----- artifactory/utils/container/image_test.go | 208 ------ artifactory/utils/container/localagent.go | 196 ------ artifactory/utils/container/manifest.go | 98 --- artifactory/utils/container/remoteagent.go | 149 ---- .../testdata/container/imageTagWithDigest | 1 - common/tests/docker.go | 192 ------ 11 files changed, 9 insertions(+), 2095 deletions(-) delete mode 100644 artifactory/utils/container/buildinfo.go delete mode 100644 artifactory/utils/container/buildinfo_test.go delete mode 100644 artifactory/utils/container/containermanager.go delete mode 100644 artifactory/utils/container/image.go delete mode 100644 artifactory/utils/container/image_test.go delete mode 100644 artifactory/utils/container/localagent.go delete mode 100644 artifactory/utils/container/manifest.go delete mode 100644 artifactory/utils/container/remoteagent.go delete mode 100644 artifactory/utils/testdata/container/imageTagWithDigest delete mode 100644 common/tests/docker.go 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)) -}