Skip to content

Commit 512633b

Browse files
committed
Optimize-virtual-repo-layer-filtering
1 parent 60a3b4b commit 512633b

File tree

1 file changed

+156
-13
lines changed

1 file changed

+156
-13
lines changed

artifactory/utils/container/buildinfo.go

Lines changed: 156 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package container
22

33
import (
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.
5765
func 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-
7888
func (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.
123266
func 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

Comments
 (0)