@@ -2,6 +2,8 @@ package container
22
33import (
44 "encoding/json"
5+ "fmt"
6+ "net/http"
57 "os"
68 "path"
79 "strings"
@@ -53,15 +55,28 @@ type buildInfoBuilder struct {
5355 imageLayers []utils.ResultItem
5456}
5557
58+ type RepositoryDetails struct {
59+ key string
60+ isRemote bool
61+ repoType string // Add repo type to avoid additional API calls
62+ }
63+
5664// Create instance of docker build info builder.
5765func newBuildInfoBuilder (image * Image , repository , buildName , buildNumber , project string , serviceManager artifactory.ArtifactoryServicesManager ) (* buildInfoBuilder , error ) {
5866 var err error
5967 builder := & buildInfoBuilder {}
6068 builder .repositoryDetails .key = repository
61- builder .repositoryDetails .isRemote , err = artutils .IsRemoteRepo (repository , serviceManager )
69+
70+ // Get repository details in one API call to determine both isRemote and repoType
71+ repoDetails := & services.RepositoryDetails {}
72+ err = serviceManager .GetRepository (repository , & repoDetails )
6273 if err != nil {
63- return nil , err
74+ return nil , errorutils . CheckErrorf ( "failed to get details for repository '" + repository + "'. Error: \n " + err . Error ())
6475 }
76+
77+ builder .repositoryDetails .isRemote = repoDetails .GetRepoType () == "remote"
78+ builder .repositoryDetails .repoType = repoDetails .GetRepoType ()
79+
6580 builder .image = image
6681 builder .buildName = buildName
6782 builder .buildNumber = buildNumber
@@ -70,11 +85,6 @@ func newBuildInfoBuilder(image *Image, repository, buildName, buildNumber, proje
7085 return builder , nil
7186}
7287
73- type RepositoryDetails struct {
74- key string
75- isRemote bool
76- }
77-
7888func (builder * buildInfoBuilder ) setImageSha2 (imageSha2 string ) {
7989 builder .imageSha2 = imageSha2
8090}
@@ -91,7 +101,7 @@ func (builder *buildInfoBuilder) getSearchableRepo() string {
91101}
92102
93103// Set build properties on image layers in Artifactory.
94- func setBuildProperties (buildName , buildNumber , project string , imageLayers []utils.ResultItem , serviceManager artifactory.ArtifactoryServicesManager ) (err error ) {
104+ func setBuildProperties (buildName , buildNumber , project string , imageLayers []utils.ResultItem , serviceManager artifactory.ArtifactoryServicesManager , originalRepo string , repoDetails * RepositoryDetails ) (err error ) {
95105 // Skip if no build info is provided
96106 if buildName == "" || buildNumber == "" {
97107 log .Debug ("Skipping setting properties - build name and build number are required" )
@@ -109,7 +119,14 @@ func setBuildProperties(buildName, buildNumber, project string, imageLayers []ut
109119 return nil
110120 }
111121
112- pathToFile , err := writeLayersToFile (imageLayers )
122+ // Filter image layers for virtual repositories to only include layers from default deployment repository
123+ filteredLayers , err := filterLayersForVirtualRepository (imageLayers , serviceManager , originalRepo , repoDetails )
124+ if err != nil {
125+ log .Debug ("Failed to filter layers for virtual repository, proceeding with all layers:" , err .Error ())
126+ filteredLayers = imageLayers
127+ }
128+
129+ pathToFile , err := writeLayersToFile (filteredLayers )
113130 if err != nil {
114131 return
115132 }
@@ -119,6 +136,135 @@ func setBuildProperties(buildName, buildNumber, project string, imageLayers []ut
119136 return
120137}
121138
139+ // filterLayersForVirtualRepository filters image layers to only include those from the default deployment repository
140+ // when dealing with virtual repositories. For non-virtual repositories, it returns all layers unchanged.
141+ func filterLayersForVirtualRepository (imageLayers []utils.ResultItem , serviceManager artifactory.ArtifactoryServicesManager , originalRepo string , repoDetails * RepositoryDetails ) ([]utils.ResultItem , error ) {
142+ if len (imageLayers ) == 0 {
143+ return imageLayers , nil
144+ }
145+
146+ // Optimization: If we already know the repo type and it's not virtual, skip the API call
147+ if repoDetails != nil && repoDetails .repoType != "" && repoDetails .repoType != "virtual" {
148+ log .Info ("OPTIMIZATION: Repository" , originalRepo , "is not virtual (type:" , repoDetails .repoType + "), skipping API call and proceeding with all layers" )
149+ return imageLayers , nil
150+ }
151+
152+ // For backwards compatibility or when repoDetails is not available, fall back to API call
153+ if repoDetails == nil || repoDetails .repoType == "" {
154+ log .Debug ("Repository type not cached, making API call to determine repository configuration" )
155+ // Check if the original repository is a virtual repository by getting its configuration
156+ repoConfig , err := getRepositoryConfiguration (originalRepo , serviceManager )
157+ if err != nil {
158+ log .Debug ("Failed to get repository configuration for" , originalRepo , ":" , err .Error ())
159+ return imageLayers , err
160+ }
161+
162+ // If it's not a virtual repository, return all layers unchanged
163+ if repoConfig == nil || repoConfig .Rclass != "virtual" {
164+ log .Debug ("Repository" , originalRepo , "is not virtual, proceeding with all layers" )
165+ return imageLayers , nil
166+ }
167+
168+ // If it's a virtual repository but has no default deployment repo, return all layers
169+ if repoConfig .DefaultDeploymentRepo == "" {
170+ log .Debug ("Virtual repository" , originalRepo , "has no default deployment repository, proceeding with all layers" )
171+ return imageLayers , nil
172+ }
173+
174+ // Filter layers to only include those from the default deployment repository
175+ var filteredLayers []utils.ResultItem
176+ for _ , layer := range imageLayers {
177+ if layer .Repo == repoConfig .DefaultDeploymentRepo {
178+ filteredLayers = append (filteredLayers , layer )
179+ }
180+ }
181+
182+ if len (filteredLayers ) > 0 {
183+ log .Info ("Filtered" , len (imageLayers ), "layers to" , len (filteredLayers ), "layers from default deployment repository:" , repoConfig .DefaultDeploymentRepo )
184+ } else {
185+ log .Warn ("No layers found in default deployment repository" , repoConfig .DefaultDeploymentRepo , "for virtual repository" , originalRepo )
186+ log .Warn ("This may indicate that the image layers exist in other repositories but not in the default deployment repository." )
187+ log .Warn ("Properties will not be set to maintain consistency with virtual repository configuration." )
188+ log .Warn ("To fix this, consider pushing the image directly to the virtual repository to ensure it lands in the default deployment repository." )
189+ return []utils.ResultItem {}, nil // Return empty slice instead of all layers
190+ }
191+
192+ return filteredLayers , nil
193+ }
194+
195+ // We know it's a virtual repository, so we need to get its default deployment repo
196+ // We still need an API call here to get the DefaultDeploymentRepo, but only for virtual repos
197+ log .Info ("Making API call for virtual repository" , originalRepo , "to get DefaultDeploymentRepo" )
198+ repoConfig , err := getRepositoryConfiguration (originalRepo , serviceManager )
199+ if err != nil {
200+ log .Debug ("Failed to get repository configuration for virtual repository" , originalRepo , ":" , err .Error ())
201+ return imageLayers , err
202+ }
203+
204+ // If it's a virtual repository but has no default deployment repo, return all layers
205+ if repoConfig .DefaultDeploymentRepo == "" {
206+ log .Debug ("Virtual repository" , originalRepo , "has no default deployment repository, proceeding with all layers" )
207+ return imageLayers , nil
208+ }
209+
210+ // Filter layers to only include those from the default deployment repository
211+ var filteredLayers []utils.ResultItem
212+ for _ , layer := range imageLayers {
213+ if layer .Repo == repoConfig .DefaultDeploymentRepo {
214+ filteredLayers = append (filteredLayers , layer )
215+ }
216+ }
217+
218+ if len (filteredLayers ) > 0 {
219+ log .Info ("Filtered" , len (imageLayers ), "layers to" , len (filteredLayers ), "layers from default deployment repository:" , repoConfig .DefaultDeploymentRepo )
220+ } else {
221+ log .Warn ("No layers found in default deployment repository" , repoConfig .DefaultDeploymentRepo , "for virtual repository" , originalRepo )
222+ log .Warn ("This may indicate that the image layers exist in other repositories but not in the default deployment repository." )
223+ log .Warn ("Properties will not be set to maintain consistency with virtual repository configuration." )
224+ log .Warn ("To fix this, consider pushing the image directly to the virtual repository to ensure it lands in the default deployment repository." )
225+ return []utils.ResultItem {}, nil // Return empty slice instead of all layers
226+ }
227+
228+ return filteredLayers , nil
229+ }
230+
231+ // repositoryConfig represents the virtual repository configuration
232+ type repositoryConfig struct {
233+ Key string `json:"key"`
234+ Rclass string `json:"rclass"`
235+ DefaultDeploymentRepo string `json:"defaultDeploymentRepo"`
236+ }
237+
238+ // getRepositoryConfiguration fetches the repository configuration from Artifactory
239+ func getRepositoryConfiguration (repoKey string , serviceManager artifactory.ArtifactoryServicesManager ) (* repositoryConfig , error ) {
240+ // Create HTTP client details for the request
241+ httpClientDetails := serviceManager .GetConfig ().GetServiceDetails ().CreateHttpClientDetails ()
242+
243+ // Construct the API endpoint URL
244+ baseUrl := serviceManager .GetConfig ().GetServiceDetails ().GetUrl ()
245+ endpoint := "api/repositories/" + repoKey
246+ url := baseUrl + endpoint
247+
248+ // Make the HTTP GET request
249+ resp , body , _ , err := serviceManager .Client ().SendGet (url , true , & httpClientDetails )
250+ if err != nil {
251+ return nil , err
252+ }
253+
254+ // Check for successful response
255+ if resp .StatusCode != http .StatusOK {
256+ return nil , fmt .Errorf ("failed to get repository configuration: HTTP %d" , resp .StatusCode )
257+ }
258+
259+ // Parse the JSON response
260+ var config repositoryConfig
261+ if err := json .Unmarshal (body , & config ); err != nil {
262+ return nil , fmt .Errorf ("failed to parse repository configuration: %v" , err )
263+ }
264+
265+ return & config , nil
266+ }
267+
122268// Download the content of layer search result.
123269func downloadLayer (searchResult utils.ResultItem , result interface {}, serviceManager artifactory.ArtifactoryServicesManager , repo string ) error {
124270 // Search results may include artifacts from the remote-cache repository.
@@ -340,7 +486,7 @@ func (builder *buildInfoBuilder) createBuildInfo(commandType CommandType, manife
340486 return nil , err
341487 }
342488 if ! builder .skipTaggingLayers {
343- if err := setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager ); err != nil {
489+ if err := setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager , builder . repositoryDetails . key , & builder . repositoryDetails ); err != nil {
344490 return nil , err
345491 }
346492 }
@@ -399,7 +545,7 @@ func (builder *buildInfoBuilder) createMultiPlatformBuildInfo(fatManifest *FatMa
399545 Parent : imageLongNameWithoutRepo ,
400546 })
401547 }
402- return buildInfo , setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager )
548+ return buildInfo , setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager , builder . repositoryDetails . key , & builder . repositoryDetails )
403549}
404550
405551// Construct the manifest's module ID by its type (attestation) or its platform.
0 commit comments