Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions commands/curation/curationaudit.go
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,8 @@ func (nc *treeAnalyzer) fetchNodeStatus(node xrayUtils.GraphNode, p *sync.Map) e
name = scope + "/" + name
}
for _, packageUrl := range packageUrls {
resp, _, err := nc.rtManager.Client().SendHead(packageUrl, &nc.httpClientDetails)
requestDetails := nc.httpClientDetails.Clone()
resp, _, err := nc.rtManager.Client().SendHead(packageUrl, requestDetails)
if err != nil {
if resp != nil && resp.StatusCode >= 400 {
return errorutils.CheckErrorf(errorTemplateHeadRequest, packageUrl, name, version, resp.StatusCode, err)
Expand Down Expand Up @@ -897,8 +898,9 @@ func (nc *treeAnalyzer) fetchNodeStatus(node xrayUtils.GraphNode, p *sync.Map) e

// We try to collect curation details from GET response after HEAD request got forbidden status code.
func (nc *treeAnalyzer) getBlockedPackageDetails(packageUrl string, name string, version string) (*PackageStatus, error) {
nc.httpClientDetails.Headers["X-Artifactory-Curation-Request-Waiver"] = "syn"
getResp, respBody, _, err := nc.rtManager.Client().SendGet(packageUrl, true, &nc.httpClientDetails)
requestDetails := nc.httpClientDetails.Clone()
requestDetails.Headers["X-Artifactory-Curation-Request-Waiver"] = "syn"
getResp, respBody, _, err := nc.rtManager.Client().SendGet(packageUrl, true, requestDetails)
if err != nil {
if getResp == nil {
return nil, err
Expand Down
57 changes: 57 additions & 0 deletions commands/curation/curationaudit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1636,3 +1636,60 @@ func TestSendWaiverRequests(t *testing.T) {
})
}
}

// TestFetchNodesStatusConcurrentMapWrite reproduces crash
// reported when many packages are blocked by curation simultaneously.
func TestFetchNodesStatusConcurrentMapWrite(t *testing.T) {
const numNodes = 50

// Mock server: HEAD returns 403 for all packages, GET returns curation block JSON
blockResponse := `{"errors":[{"status":403,"message":"Package download was blocked by JFrog Packages Curation service due to the following policies violated {testPolicy, testCondition, testExplanation, testRecommendation}"}]}`
serverMock, _, rtManager := coreCommonTests.CreateRtRestsMockServer(t, func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead {
w.WriteHeader(http.StatusForbidden)
return
}
if r.Method == http.MethodGet {
w.WriteHeader(http.StatusForbidden)
_, _ = w.Write([]byte(blockResponse))
return
}
})
defer serverMock.Close()

rtAuth := rtManager.GetConfig().GetServiceDetails()
httpClientDetails := rtAuth.CreateHttpClientDetails()

root := &xrayUtils.GraphNode{Id: "npm://root:1.0.0"}
for i := 0; i < numNodes; i++ {
root.Nodes = append(root.Nodes, &xrayUtils.GraphNode{
Id: fmt.Sprintf("npm://pkg-%d:%d.0.0", i, i),
})
}

analyzer := treeAnalyzer{
rtManager: rtManager,
extractPoliciesRegex: regexp.MustCompile(extractPoliciesRegexTemplate),
rtAuth: rtAuth,
httpClientDetails: httpClientDetails,
url: rtAuth.GetUrl(),
repo: "npm-remote",
tech: techutils.Npm,
parallelRequests: 10,
}

packagesStatusMap := sync.Map{}
rootNodes := map[string]struct{}{root.Id: {}}

// This will crash with "concurrent map writes" without the fix
err := analyzer.fetchNodesStatus(root, &packagesStatusMap, rootNodes)
assert.NoError(t, err)

// Verify all blocked packages were recorded
count := 0
packagesStatusMap.Range(func(_, _ any) bool {
count++
return true
})
assert.Equal(t, numNodes, count, "expected all %d packages to be recorded as blocked", numNodes)
}
Loading