@@ -2,6 +2,7 @@ package container
22
33import (
44 "encoding/json"
5+ "fmt"
56 "os"
67 "path"
78 "strings"
@@ -53,15 +54,28 @@ type buildInfoBuilder struct {
5354 imageLayers []utils.ResultItem
5455}
5556
57+ type RepositoryDetails struct {
58+ key string
59+ isRemote bool
60+ repoType string // Add repo type to avoid additional API calls
61+ }
62+
5663// Create instance of docker build info builder.
5764func newBuildInfoBuilder (image * Image , repository , buildName , buildNumber , project string , serviceManager artifactory.ArtifactoryServicesManager ) (* buildInfoBuilder , error ) {
5865 var err error
5966 builder := & buildInfoBuilder {}
6067 builder .repositoryDetails .key = repository
61- builder .repositoryDetails .isRemote , err = artutils .IsRemoteRepo (repository , serviceManager )
68+
69+ // Get repository details in one API call to determine both isRemote and repoType
70+ repoDetails := & services.RepositoryDetails {}
71+ err = serviceManager .GetRepository (repository , & repoDetails )
6272 if err != nil {
63- return nil , err
73+ return nil , errorutils . CheckErrorf ( "failed to get details for repository '" + repository + "'. Error: \n " + err . Error ())
6474 }
75+
76+ builder .repositoryDetails .isRemote = repoDetails .GetRepoType () == "remote"
77+ builder .repositoryDetails .repoType = repoDetails .GetRepoType ()
78+
6579 builder .image = image
6680 builder .buildName = buildName
6781 builder .buildNumber = buildNumber
@@ -70,11 +84,6 @@ func newBuildInfoBuilder(image *Image, repository, buildName, buildNumber, proje
7084 return builder , nil
7185}
7286
73- type RepositoryDetails struct {
74- key string
75- isRemote bool
76- }
77-
7887func (builder * buildInfoBuilder ) setImageSha2 (imageSha2 string ) {
7988 builder .imageSha2 = imageSha2
8089}
@@ -91,7 +100,7 @@ func (builder *buildInfoBuilder) getSearchableRepo() string {
91100}
92101
93102// Set build properties on image layers in Artifactory.
94- func setBuildProperties (buildName , buildNumber , project string , imageLayers []utils.ResultItem , serviceManager artifactory.ArtifactoryServicesManager ) (err error ) {
103+ func setBuildProperties (buildName , buildNumber , project string , imageLayers []utils.ResultItem , serviceManager artifactory.ArtifactoryServicesManager , originalRepo string , repoDetails * RepositoryDetails ) (err error ) {
95104 // Skip if no build info is provided
96105 if buildName == "" || buildNumber == "" {
97106 log .Debug ("Skipping setting properties - build name and build number are required" )
@@ -109,7 +118,14 @@ func setBuildProperties(buildName, buildNumber, project string, imageLayers []ut
109118 return nil
110119 }
111120
112- pathToFile , err := writeLayersToFile (imageLayers )
121+ // Filter image layers for virtual repositories to only include layers from default deployment repository
122+ filteredLayers , err := filterLayersForVirtualRepository (imageLayers , serviceManager , originalRepo , repoDetails )
123+ if err != nil {
124+ log .Debug ("Failed to filter layers for virtual repository, proceeding with all layers:" , err .Error ())
125+ filteredLayers = imageLayers
126+ }
127+
128+ pathToFile , err := writeLayersToFile (filteredLayers )
113129 if err != nil {
114130 return
115131 }
@@ -119,6 +135,135 @@ func setBuildProperties(buildName, buildNumber, project string, imageLayers []ut
119135 return
120136}
121137
138+ // filterLayersForVirtualRepository filters image layers to only include those from the default deployment repository
139+ // when dealing with virtual repositories. For non-virtual repositories, it returns all layers unchanged.
140+ func filterLayersForVirtualRepository (imageLayers []utils.ResultItem , serviceManager artifactory.ArtifactoryServicesManager , originalRepo string , repoDetails * RepositoryDetails ) ([]utils.ResultItem , error ) {
141+ if len (imageLayers ) == 0 {
142+ return imageLayers , nil
143+ }
144+
145+ // Optimization: If we already know the repo type and it's not virtual, skip the API call
146+ if repoDetails != nil && repoDetails .repoType != "" && repoDetails .repoType != "virtual" {
147+ log .Info ("OPTIMIZATION: Repository" , originalRepo , "is not virtual (type:" , repoDetails .repoType + "), skipping API call and proceeding with all layers" )
148+ return imageLayers , nil
149+ }
150+
151+ // For backwards compatibility or when repoDetails is not available, fall back to API call
152+ if repoDetails == nil || repoDetails .repoType == "" {
153+ log .Debug ("Repository type not cached, making API call to determine repository configuration" )
154+ // Check if the original repository is a virtual repository by getting its configuration
155+ repoConfig , err := getRepositoryConfiguration (originalRepo , serviceManager )
156+ if err != nil {
157+ log .Debug ("Failed to get repository configuration for" , originalRepo , ":" , err .Error ())
158+ return imageLayers , err
159+ }
160+
161+ // If it's not a virtual repository, return all layers unchanged
162+ if repoConfig == nil || repoConfig .Rclass != "virtual" {
163+ log .Debug ("Repository" , originalRepo , "is not virtual, proceeding with all layers" )
164+ return imageLayers , nil
165+ }
166+
167+ // If it's a virtual repository but has no default deployment repo, return all layers
168+ if repoConfig .DefaultDeploymentRepo == "" {
169+ log .Debug ("Virtual repository" , originalRepo , "has no default deployment repository, proceeding with all layers" )
170+ return imageLayers , nil
171+ }
172+
173+ // Filter layers to only include those from the default deployment repository
174+ var filteredLayers []utils.ResultItem
175+ for _ , layer := range imageLayers {
176+ if layer .Repo == repoConfig .DefaultDeploymentRepo {
177+ filteredLayers = append (filteredLayers , layer )
178+ }
179+ }
180+
181+ if len (filteredLayers ) > 0 {
182+ log .Info ("Filtered" , len (imageLayers ), "layers to" , len (filteredLayers ), "layers from default deployment repository:" , repoConfig .DefaultDeploymentRepo )
183+ } else {
184+ log .Warn ("No layers found in default deployment repository" , repoConfig .DefaultDeploymentRepo , "for virtual repository" , originalRepo )
185+ log .Warn ("This may indicate that the image layers exist in other repositories but not in the default deployment repository." )
186+ log .Warn ("Properties will not be set to maintain consistency with virtual repository configuration." )
187+ log .Warn ("To fix this, consider pushing the image directly to the virtual repository to ensure it lands in the default deployment repository." )
188+ return []utils.ResultItem {}, nil // Return empty slice instead of all layers
189+ }
190+
191+ return filteredLayers , nil
192+ }
193+
194+ // We know it's a virtual repository, so we need to get its default deployment repo
195+ // We still need an API call here to get the DefaultDeploymentRepo, but only for virtual repos
196+ log .Info ("Making API call for virtual repository" , originalRepo , "to get DefaultDeploymentRepo" )
197+ repoConfig , err := getRepositoryConfiguration (originalRepo , serviceManager )
198+ if err != nil {
199+ log .Debug ("Failed to get repository configuration for virtual repository" , originalRepo , ":" , err .Error ())
200+ return imageLayers , err
201+ }
202+
203+ // If it's a virtual repository but has no default deployment repo, return all layers
204+ if repoConfig .DefaultDeploymentRepo == "" {
205+ log .Debug ("Virtual repository" , originalRepo , "has no default deployment repository, proceeding with all layers" )
206+ return imageLayers , nil
207+ }
208+
209+ // Filter layers to only include those from the default deployment repository
210+ var filteredLayers []utils.ResultItem
211+ for _ , layer := range imageLayers {
212+ if layer .Repo == repoConfig .DefaultDeploymentRepo {
213+ filteredLayers = append (filteredLayers , layer )
214+ }
215+ }
216+
217+ if len (filteredLayers ) > 0 {
218+ log .Info ("Filtered" , len (imageLayers ), "layers to" , len (filteredLayers ), "layers from default deployment repository:" , repoConfig .DefaultDeploymentRepo )
219+ } else {
220+ log .Warn ("No layers found in default deployment repository" , repoConfig .DefaultDeploymentRepo , "for virtual repository" , originalRepo )
221+ log .Warn ("This may indicate that the image layers exist in other repositories but not in the default deployment repository." )
222+ log .Warn ("Properties will not be set to maintain consistency with virtual repository configuration." )
223+ log .Warn ("To fix this, consider pushing the image directly to the virtual repository to ensure it lands in the default deployment repository." )
224+ return []utils.ResultItem {}, nil // Return empty slice instead of all layers
225+ }
226+
227+ return filteredLayers , nil
228+ }
229+
230+ // repositoryConfig represents the virtual repository configuration
231+ type repositoryConfig struct {
232+ Key string `json:"key"`
233+ Rclass string `json:"rclass"`
234+ DefaultDeploymentRepo string `json:"defaultDeploymentRepo"`
235+ }
236+
237+ // getRepositoryConfiguration fetches the repository configuration from Artifactory
238+ func getRepositoryConfiguration (repoKey string , serviceManager artifactory.ArtifactoryServicesManager ) (* repositoryConfig , error ) {
239+ // Create HTTP client details for the request
240+ httpClientDetails := serviceManager .GetConfig ().GetServiceDetails ().CreateHttpClientDetails ()
241+
242+ // Construct the API endpoint URL
243+ baseUrl := serviceManager .GetConfig ().GetServiceDetails ().GetUrl ()
244+ endpoint := "api/repositories/" + repoKey
245+ url := baseUrl + endpoint
246+
247+ // Make the HTTP GET request
248+ resp , body , _ , err := serviceManager .Client ().SendGet (url , true , & httpClientDetails )
249+ if err != nil {
250+ return nil , err
251+ }
252+
253+ // Check for successful response
254+ if resp .StatusCode != 200 {
255+ return nil , fmt .Errorf ("failed to get repository configuration: HTTP %d" , resp .StatusCode )
256+ }
257+
258+ // Parse the JSON response
259+ var config repositoryConfig
260+ if err := json .Unmarshal (body , & config ); err != nil {
261+ return nil , fmt .Errorf ("failed to parse repository configuration: %v" , err )
262+ }
263+
264+ return & config , nil
265+ }
266+
122267// Download the content of layer search result.
123268func downloadLayer (searchResult utils.ResultItem , result interface {}, serviceManager artifactory.ArtifactoryServicesManager , repo string ) error {
124269 // Search results may include artifacts from the remote-cache repository.
@@ -340,7 +485,7 @@ func (builder *buildInfoBuilder) createBuildInfo(commandType CommandType, manife
340485 return nil , err
341486 }
342487 if ! builder .skipTaggingLayers {
343- if err := setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager ); err != nil {
488+ if err := setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager , builder . repositoryDetails . key , & builder . repositoryDetails ); err != nil {
344489 return nil , err
345490 }
346491 }
@@ -399,7 +544,7 @@ func (builder *buildInfoBuilder) createMultiPlatformBuildInfo(fatManifest *FatMa
399544 Parent : imageLongNameWithoutRepo ,
400545 })
401546 }
402- return buildInfo , setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager )
547+ return buildInfo , setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager , builder . repositoryDetails . key , & builder . repositoryDetails )
403548}
404549
405550// Construct the manifest's module ID by its type (attestation) or its platform.
0 commit comments