Skip to content

Commit c63817d

Browse files
authored
Chunk large license scan uploads to avoid timeouts (#1509)
1 parent acb77c4 commit c63817d

File tree

4 files changed

+47
-18
lines changed

4 files changed

+47
-18
lines changed

Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
## 3.9.48
55
- General: Fix a bug where directory traversal could fail if the user does not have permission to read a directory ([#1508](https://github.com/fossas/fossa-cli/pull/1508)).
6+
- Performance: Fix timeout issues when uploading large numbers of license scans by processing them in smaller batches ([#1509](https://github.com/fossas/fossa-cli/pull/1509)).
67

78
## 3.9.47
89
- Licensing: Adds support for Zeebe Community License v1.1 and Camunda License v1.0

src/Control/Carrier/FossaApiClient/Internal/FossaAPIV1.hs

+27-17
Original file line numberDiff line numberDiff line change
@@ -179,11 +179,13 @@ import Fossa.API.Types (
179179
SignedURLWithKey (surlwkKey, surlwkSignedURL),
180180
TokenTypeResponse,
181181
UploadResponse,
182+
chunkArchiveComponents,
182183
useApiOpts,
183184
)
184185

185186
import Control.Effect.Reader
186187
import Data.Foldable (traverse_)
188+
import Data.List.Extra (chunk)
187189
import Fossa.API.CoreTypes qualified as CoreTypes
188190
import Network.HTTP.Client (responseStatus)
189191
import Network.HTTP.Client qualified as C
@@ -652,24 +654,25 @@ uploadNativeContainerScan apiOpts ProjectRevision{..} metadata scan =
652654
(baseUrl, baseOpts) <- useApiOpts apiOpts
653655
let locator = renderLocator $ Locator "custom" projectName (Just projectRevision)
654656
opts =
655-
"locator" =: locator
657+
baseOpts
658+
<> "locator" =: locator
656659
<> "cliVersion" =: cliVersion
657660
<> "managedBuild" =: True
658661
<> maybe mempty ("branch" =:) projectBranch
659662
<> "scanType" =: ("native" :: Text)
660663
<> mkMetadataOpts metadata projectName
661664

662-
uploadScan url containerScan = req POST url (ReqBodyJson containerScan) jsonResponse (baseOpts <> opts)
663-
sparkleAnalysisUrl = containerUploadUrl Sparkle baseUrl
664-
665-
resp <-
666-
( warnOnErr @Text "Container scan upload to new analysis service failed, falling back to core analysis."
667-
. errCtx ("Upload to new analysis service at " <> renderUrl sparkleAnalysisUrl)
668-
$ uploadScan sparkleAnalysisUrl scan
669-
)
670-
<||> context "Upload to CORE analysis service" (uploadScan (containerUploadUrl Core baseUrl) scan)
671-
665+
resp <- uploadToSparkle baseUrl opts <||> uploadToCore baseUrl opts
672666
pure $ responseBody resp
667+
where
668+
uploadScan url opts containerScan = req POST url (ReqBodyJson containerScan) jsonResponse opts
669+
sparkleAnalysisUrl = containerUploadUrl Sparkle
670+
coreAnalysisUrl = containerUploadUrl Core
671+
uploadToSparkle baseUrl opts =
672+
warnOnErr @Text "Container scan upload to new analysis service failed, falling back to core analysis."
673+
. errCtx ("Upload to new analysis service at " <> renderUrl (sparkleAnalysisUrl baseUrl))
674+
$ uploadScan (sparkleAnalysisUrl baseUrl) opts scan
675+
uploadToCore baseUrl opts = context "Upload to CORE analysis service" $ uploadScan (coreAnalysisUrl baseUrl) opts scan
673676

674677
-- | Replacement for @Data.HTTP.Req.req@ that additionally logs information about a request in a debug bundle.
675678
req ::
@@ -1015,8 +1018,9 @@ licenseScanFinalize ::
10151018
ArchiveComponents ->
10161019
m ()
10171020
licenseScanFinalize apiOpts archiveProjects = do
1018-
_ <- licenseScanFinalize' apiOpts archiveProjects
1019-
pure ()
1021+
-- The latency for scans with hundreds of license scans is way too high, leading to spurious error messages.
1022+
-- https://fossa.atlassian.net/browse/ANE-2272
1023+
traverse_ (licenseScanFinalize' apiOpts) $ chunkArchiveComponents 100 archiveProjects
10201024

10211025
licenseScanFinalize' ::
10221026
APIClientEffs sig m =>
@@ -1580,12 +1584,18 @@ finalizePathDependencyScan ::
15801584
m (Maybe ())
15811585
finalizePathDependencyScan apiOpts locators forceRebuild = runEmpty $
15821586
fossaReqAllow401 $ do
1587+
-- The latency for scans with hundreds of license scans is way too high, leading to spurious error messages.
1588+
-- Reference: https://fossa.atlassian.net/browse/ANE-2272
1589+
--
1590+
-- No similar issue was reported for this method,
1591+
-- but since it uses the same methodology I decided it was better to go ahead and do it.
15831592
(baseUrl, baseOpts) <- useApiOpts apiOpts
1584-
let req' = PathDependencyFinalizeReq locators forceRebuild
1585-
_ <-
1593+
traverse_ (finalize baseUrl baseOpts) $ chunk 100 locators
1594+
where
1595+
mkReq locs = PathDependencyFinalizeReq locs forceRebuild
1596+
finalize baseUrl baseOpts locs =
15861597
context "Queuing a build for all license scan uploads" $
1587-
req POST (pathDependencyFinalizeUrl baseUrl) (ReqBodyJson req') ignoreResponse (baseOpts)
1588-
pure ()
1598+
req POST (pathDependencyFinalizeUrl baseUrl) (ReqBodyJson (mkReq locs)) ignoreResponse (baseOpts)
15891599

15901600
alreadyAnalyzedPathRevisionURLEndpoint :: Url 'Https -> Locator -> Url 'Https
15911601
alreadyAnalyzedPathRevisionURLEndpoint baseUrl locator = baseUrl /: "api" /: "cli" /: "path_dependency_scan" /: renderLocator locator /: "analyzed"

src/Data/List/Extra.hs

+9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module Data.List.Extra (
22
(!?),
33
head',
44
singleton,
5+
chunk,
56
) where
67

78
import Data.Maybe (listToMaybe)
@@ -21,3 +22,11 @@ head' = listToMaybe
2122
-- | Create a one-item list from the item given
2223
singleton :: a -> [a]
2324
singleton = (: [])
25+
26+
-- | Chunk the list into a list of lists.
27+
-- If chunk size is 0 or lower, returns a single chunk containing the entire list.
28+
chunk :: Int -> [a] -> [[a]]
29+
chunk _ [] = []
30+
chunk size as
31+
| size <= 0 = [as]
32+
| otherwise = take size as : chunk size (drop size as)

src/Fossa/API/Types.hs

+10-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ module Fossa.API.Types (
4444
defaultApiPollDelay,
4545
blankOrganization,
4646
orgFileUpload,
47+
chunkArchiveComponents,
4748
) where
4849

4950
import App.Fossa.Lernie.Types (GrepEntry)
@@ -66,7 +67,7 @@ import Data.Aeson (
6667
import Data.Coerce (coerce)
6768
import Data.Function (on)
6869
import Data.List (sort, sortBy)
69-
import Data.List.Extra ((!?))
70+
import Data.List.Extra (chunk, (!?))
7071
import Data.Map.Strict (Map)
7172
import Data.Map.Strict qualified as Map
7273
import Data.Maybe (catMaybes, fromMaybe)
@@ -149,6 +150,14 @@ data ArchiveComponents = ArchiveComponents
149150
}
150151
deriving (Eq, Ord, Show)
151152

153+
-- | Split the @ArchiveComponents@ into chunks of the given size.
154+
-- If chunk size is 0 or lower, returns a single chunk containing the entire list.
155+
chunkArchiveComponents :: Int -> ArchiveComponents -> [ArchiveComponents]
156+
chunkArchiveComponents chunkSize ArchiveComponents{..} = map mkComponent $ chunk chunkSize archiveComponentsArchives
157+
where
158+
mkComponent :: [Archive] -> ArchiveComponents
159+
mkComponent as = ArchiveComponents as archiveComponentsRebuild archiveComponentsUpload
160+
152161
instance ToJSON ArchiveComponents where
153162
toJSON ArchiveComponents{..} =
154163
object

0 commit comments

Comments
 (0)