@@ -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
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,8 +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 ) {
95- // Skip if no build info is provided
104+ func setBuildProperties (buildName , buildNumber , project string , imageLayers []utils.ResultItem , serviceManager artifactory.ArtifactoryServicesManager , originalRepo string , repoDetails * RepositoryDetails ) (err error ) {
96105 if buildName == "" || buildNumber == "" {
97106 log .Debug ("Skipping setting properties - build name and build number are required" )
98107 return nil
@@ -103,13 +112,18 @@ func setBuildProperties(buildName, buildNumber, project string, imageLayers []ut
103112 return
104113 }
105114
106- // Skip if no properties were created
107115 if len (props ) == 0 {
108116 log .Debug ("Skipping setting properties - no properties created" )
109117 return nil
110118 }
111119
112- pathToFile , err := writeLayersToFile (imageLayers )
120+ filteredLayers , err := filterLayersForVirtualRepository (imageLayers , serviceManager , originalRepo , repoDetails )
121+ if err != nil {
122+ log .Debug ("Failed to filter layers for virtual repository, proceeding with all layers:" , err .Error ())
123+ filteredLayers = imageLayers
124+ }
125+
126+ pathToFile , err := writeLayersToFile (filteredLayers )
113127 if err != nil {
114128 return
115129 }
@@ -119,6 +133,135 @@ func setBuildProperties(buildName, buildNumber, project string, imageLayers []ut
119133 return
120134}
121135
136+ // filterLayersForVirtualRepository filters image layers to only include those from the default deployment repository
137+ // when dealing with virtual repositories. For non-virtual repositories, it returns all layers unchanged.
138+ func filterLayersForVirtualRepository (imageLayers []utils.ResultItem , serviceManager artifactory.ArtifactoryServicesManager , originalRepo string , repoDetails * RepositoryDetails ) ([]utils.ResultItem , error ) {
139+ if len (imageLayers ) == 0 {
140+ return imageLayers , nil
141+ }
142+
143+ // Optimization: If we already know the repo type and it's not virtual, skip the API call
144+ if repoDetails != nil && repoDetails .repoType != "" && repoDetails .repoType != "virtual" {
145+ log .Info ("OPTIMIZATION: Repository" , originalRepo , "is not virtual (type:" , repoDetails .repoType + "), skipping API call and proceeding with all layers" )
146+ return imageLayers , nil
147+ }
148+
149+ // For backwards compatibility or when repoDetails is not available, fall back to API call
150+ if repoDetails == nil || repoDetails .repoType == "" {
151+ log .Debug ("Repository type not cached, making API call to determine repository configuration" )
152+ // Check if the original repository is a virtual repository by getting its configuration
153+ repoConfig , err := getRepositoryConfiguration (originalRepo , serviceManager )
154+ if err != nil {
155+ log .Debug ("Failed to get repository configuration for" , originalRepo , ":" , err .Error ())
156+ return imageLayers , err
157+ }
158+
159+ // If it's not a virtual repository, return all layers unchanged
160+ if repoConfig == nil || repoConfig .Rclass != "virtual" {
161+ log .Debug ("Repository" , originalRepo , "is not virtual, proceeding with all layers" )
162+ return imageLayers , nil
163+ }
164+
165+ // If it's a virtual repository but has no default deployment repo, return all layers
166+ if repoConfig .DefaultDeploymentRepo == "" {
167+ log .Debug ("Virtual repository" , originalRepo , "has no default deployment repository, proceeding with all layers" )
168+ return imageLayers , nil
169+ }
170+
171+ // Filter layers to only include those from the default deployment repository
172+ var filteredLayers []utils.ResultItem
173+ for _ , layer := range imageLayers {
174+ if layer .Repo == repoConfig .DefaultDeploymentRepo {
175+ filteredLayers = append (filteredLayers , layer )
176+ }
177+ }
178+
179+ if len (filteredLayers ) > 0 {
180+ log .Info ("Filtered" , len (imageLayers ), "layers to" , len (filteredLayers ), "layers from default deployment repository:" , repoConfig .DefaultDeploymentRepo )
181+ } else {
182+ log .Warn (fmt .Sprintf (`No layers found in default deployment repository '%s' for virtual repository '%s'.
183+ This may indicate that image layers exist in other repositories but not in the default deployment repository.
184+ Properties will not be set to maintain consistency with virtual repository configuration.
185+ To fix this, consider pushing the image directly to the virtual repository to ensure it lands in the default deployment repository.` , repoConfig .DefaultDeploymentRepo , originalRepo ))
186+ return []utils.ResultItem {}, nil
187+ }
188+
189+ return filteredLayers , nil
190+ }
191+
192+ // We know it's a virtual repository, so we need to get its default deployment repo
193+ // We still need an API call here to get the DefaultDeploymentRepo, but only for virtual repos
194+ log .Info ("Making API call for virtual repository" , originalRepo , "to get DefaultDeploymentRepo" )
195+ repoConfig , err := getRepositoryConfiguration (originalRepo , serviceManager )
196+ if err != nil {
197+ log .Debug ("Failed to get repository configuration for virtual repository" , originalRepo , ":" , err .Error ())
198+ return imageLayers , err
199+ }
200+
201+ // If it's a virtual repository but has no default deployment repo, return all layers
202+ if repoConfig .DefaultDeploymentRepo == "" {
203+ log .Debug ("Virtual repository" , originalRepo , "has no default deployment repository, proceeding with all layers" )
204+ return imageLayers , nil
205+ }
206+
207+ // Filter layers to only include those from the default deployment repository
208+ var filteredLayers []utils.ResultItem
209+ for _ , layer := range imageLayers {
210+ if layer .Repo == repoConfig .DefaultDeploymentRepo {
211+ filteredLayers = append (filteredLayers , layer )
212+ }
213+ }
214+
215+ if len (filteredLayers ) > 0 {
216+ log .Info ("Filtered" , len (imageLayers ), "layers to" , len (filteredLayers ), "layers from default deployment repository:" , repoConfig .DefaultDeploymentRepo )
217+ } else {
218+ log .Warn (fmt .Sprintf (`No layers found in default deployment repository '%s' for virtual repository '%s'.
219+ This may indicate that image layers exist in other repositories but not in the default deployment repository.
220+ Properties will not be set to maintain consistency with virtual repository configuration.
221+ To fix this, consider pushing the image directly to the virtual repository to ensure it lands in the default deployment repository.` , repoConfig .DefaultDeploymentRepo , originalRepo ))
222+ return []utils.ResultItem {}, nil
223+ }
224+
225+ return filteredLayers , nil
226+ }
227+
228+ // repositoryConfig represents the virtual repository configuration
229+ type repositoryConfig struct {
230+ Key string `json:"key"`
231+ Rclass string `json:"rclass"`
232+ DefaultDeploymentRepo string `json:"defaultDeploymentRepo"`
233+ }
234+
235+ // getRepositoryConfiguration fetches the repository configuration from Artifactory
236+ func getRepositoryConfiguration (repoKey string , serviceManager artifactory.ArtifactoryServicesManager ) (* repositoryConfig , error ) {
237+ // Create HTTP client details for the request
238+ httpClientDetails := serviceManager .GetConfig ().GetServiceDetails ().CreateHttpClientDetails ()
239+
240+ // Construct the API endpoint URL
241+ baseUrl := serviceManager .GetConfig ().GetServiceDetails ().GetUrl ()
242+ endpoint := "api/repositories/" + repoKey
243+ url := baseUrl + endpoint
244+
245+ // Make the HTTP GET request
246+ resp , body , _ , err := serviceManager .Client ().SendGet (url , true , & httpClientDetails )
247+ if err != nil {
248+ return nil , err
249+ }
250+
251+ // Check for successful response
252+ if resp .StatusCode != http .StatusOK {
253+ return nil , fmt .Errorf ("failed to get repository configuration: HTTP %d" , resp .StatusCode )
254+ }
255+
256+ // Parse the JSON response
257+ var config repositoryConfig
258+ if err := json .Unmarshal (body , & config ); err != nil {
259+ return nil , fmt .Errorf ("failed to parse repository configuration: %v" , err )
260+ }
261+
262+ return & config , nil
263+ }
264+
122265// Download the content of layer search result.
123266func downloadLayer (searchResult utils.ResultItem , result interface {}, serviceManager artifactory.ArtifactoryServicesManager , repo string ) error {
124267 // Search results may include artifacts from the remote-cache repository.
@@ -340,7 +483,7 @@ func (builder *buildInfoBuilder) createBuildInfo(commandType CommandType, manife
340483 return nil , err
341484 }
342485 if ! builder .skipTaggingLayers {
343- if err := setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager ); err != nil {
486+ if err := setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager , builder . repositoryDetails . key , & builder . repositoryDetails ); err != nil {
344487 return nil , err
345488 }
346489 }
@@ -399,7 +542,7 @@ func (builder *buildInfoBuilder) createMultiPlatformBuildInfo(fatManifest *FatMa
399542 Parent : imageLongNameWithoutRepo ,
400543 })
401544 }
402- return buildInfo , setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager )
545+ return buildInfo , setBuildProperties (builder .buildName , builder .buildNumber , builder .project , builder .imageLayers , builder .serviceManager , builder . repositoryDetails . key , & builder . repositoryDetails )
403546}
404547
405548// Construct the manifest's module ID by its type (attestation) or its platform.
0 commit comments