Skip to content

Commit 526e1b1

Browse files
authored
Merge pull request #21 from commercialhaskell/b/hackage-security
Use hackage-security and TUF
2 parents 0014ade + e0f1726 commit 526e1b1

18 files changed

Lines changed: 554 additions & 266 deletions

File tree

all-cabal-tool.cabal

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ build-type: Simple
1212
extra-source-files: README.md ChangeLog.md
1313
cabal-version: >=1.10
1414

15+
flag integration
16+
description: Build the test suite that talks to the live Hackage repository.
17+
default: False
18+
manual: True
19+
1520
library
1621
hs-source-dirs: src
1722
exposed-modules: Stackage.Package.Hashes
@@ -22,6 +27,8 @@ library
2227
, Stackage.Package.Git.Repository
2328
, Stackage.Package.Git.Types
2429
, Stackage.Package.Git.WorkTree
30+
, Stackage.Package.Hackage
31+
, Stackage.Package.HttpLib
2532
, Stackage.Package.Metadata
2633
, Stackage.Package.Metadata.Types
2734
, Stackage.Package.Update
@@ -39,19 +46,22 @@ library
3946
, directory
4047
, enclosed-exceptions
4148
, filepath
49+
, hackage-security
4250
, hit
4351
, hourglass
4452
, http-client
4553
, http-client-tls
4654
, http-conduit
4755
, http-types
4856
, memory
57+
, network-uri
4958
, pretty
5059
, process
5160
, resourcet
5261
, system-filepath
5362
, tar
5463
, text
64+
, time
5565
, utf8-string
5666
, yaml
5767
, zlib
@@ -71,6 +81,7 @@ executable all-cabal-tool
7181
, classy-prelude-conduit
7282
, conduit-extra
7383
, directory
84+
, hackage-security
7485
, http-client
7586
, http-conduit
7687
, http-types
@@ -99,6 +110,21 @@ test-suite all-cabal-tool-test
99110
default-language: Haskell2010
100111
ghc-options: -Wall
101112

113+
test-suite all-cabal-tool-integration
114+
type: exitcode-stdio-1.0
115+
hs-source-dirs: test-integration
116+
main-is: Main.hs
117+
build-depends: base
118+
, Cabal
119+
, all-cabal-tool
120+
, directory
121+
, temporary
122+
, time
123+
default-language: Haskell2010
124+
ghc-options: -Wall
125+
if !flag(integration)
126+
buildable: False
127+
102128
source-repository head
103129
type: git
104130
location: https://github.com/commercialhaskell/all-cabal-tool

app/Main.hs

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,13 @@ import Amazonka.Auth (fromKeys)
2020
import Amazonka.S3.PutObject
2121
import Amazonka.S3 (BucketName(..), ObjectKey(..), ObjectCannedACL(..))
2222
import Network.HTTP.Client (parseUrlThrow)
23-
import Network.HTTP.Simple
24-
(Request, parseRequest, addRequestHeader, getResponseStatus,
25-
getResponseStatusCode, getResponseHeader, getResponseBody, httpLBS)
23+
import Network.HTTP.Simple (getResponseBody, httpLBS)
2624
import Options.Applicative
2725
import System.Environment (getEnv, lookupEnv)
2826
import System.IO (hPutStrLn)
2927

3028
import Stackage.Package.Update
29+
import Stackage.Package.Hackage
3130
import Stackage.Package.Locations
3231
import Stackage.Package.Git
3332
import Stackage.Package.IndexConduit
@@ -108,38 +107,25 @@ updateIndex00 awsMech bucketName = do
108107
Left e -> error $ show (key, e :: Error)
109108
Right _ -> putStrLn "Success"
110109

110+
-- | Refresh the local index and, if it moved (or a refresh is being forced),
111+
-- regenerate the repositories from it.
111112
processIndexUpdate
112113
:: MonadIO m
113-
=> Repositories
114-
-> Request -- ^ Request that should be processed
115-
-> Maybe ByteString -- ^ Previous Etag, so we can check if the content has changed.
116-
-> m (Bool, Maybe ByteString)
117-
processIndexUpdate repos indexReq mLastEtag = liftIO $ do
118-
let indexReqWithEtag =
119-
maybe id (addRequestHeader "if-none-match") mLastEtag indexReq
120-
mValidVersionsWithEtag <-
121-
httpTarballSink
122-
indexReqWithEtag
123-
True
124-
(\res ->
125-
case getResponseStatusCode res of
126-
200 -> do
127-
validVersions <- allHashesUpdate repos
128-
return $
129-
Just (validVersions, listToMaybe $ getResponseHeader "etag" res)
130-
304 -> return Nothing
131-
_ -> error $ "Unexpected status: " ++ show (getResponseStatus res))
132-
case mValidVersionsWithEtag of
133-
Nothing -> return (False, mLastEtag)
134-
Just (validVersions, mNewEtag) ->
135-
httpTarballSink
136-
indexReqWithEtag
137-
True
138-
(\res ->
139-
case getResponseStatusCode res of
140-
200 -> (True, mNewEtag) <$ allCabalUpdate repos validVersions
141-
304 -> return (False, mLastEtag)
142-
_ -> error $ "Unexpected status: " ++ show (getResponseStatus res))
114+
=> Hackage
115+
-> Repositories
116+
-> UTCTime -- ^ Time to judge index metadata expiry against
117+
-> Bool -- ^ Regenerate even when the index is unchanged.
118+
-> m Bool
119+
processIndexUpdate hackage repos now forceUpdate = liftIO $ do
120+
(hasUpdates, indexPath) <- refreshIndex hackage now
121+
case hasUpdates of
122+
NoUpdates
123+
| not forceUpdate -> return False
124+
_ -> do
125+
validVersions <-
126+
localTarballSink indexPath False (allHashesUpdate hackage repos)
127+
localTarballSink indexPath False (allCabalUpdate hackage repos validVersions)
128+
return True
143129

144130

145131

@@ -152,6 +138,7 @@ data Options = Options
152138
, oLocalPath :: Maybe FilePath -- default $HOME
153139
, oGithubAccount :: String -- default "commercialhaskell"
154140
, oDelay :: Maybe Int -- default 60 seconds
141+
, oOneShot :: Bool
155142
, oS3Bucket :: Maybe BucketName
156143
, oAwsDiscoveryMech :: AwsDiscoverMechanism
157144
}
@@ -200,6 +187,10 @@ optionsParser =
200187
help
201188
("Delay in seconds before next check for a new version " ++
202189
"of 01-index.tar.gz file")))) <*>
190+
(switch
191+
(long "one-shot" <>
192+
help
193+
("Update once and exit, instead of polling forever. "))) <*>
203194
(optional
204195
((BucketName . pack) <$>
205196
(strOption
@@ -233,29 +224,36 @@ main = do
233224
when (isNothing ms3Bucket) $
234225
putStrLn
235226
"WARNING: No s3-bucket is provided. Uploading of 00-index.tar.gz will be disabled."
236-
indexReq <- parseRequest $ mirrorFPComplete ++ "/01-index.tar.gz"
237227
reposInfoInit <- getReposInfo localPath oGithubAccount gitUser
238-
let innerLoop reposInfo mlastEtag = do
239-
putStrLn $ "Checking index, etag == " ++ tshow mlastEtag
240-
commitMessage <- getCommitMessage
241-
(newInfo, mnewEtag) <- withRepositories reposInfo $ \ repos -> do
242-
(updated, mnewEtag) <- processIndexUpdate repos indexReq mlastEtag
243-
when updated $
244-
do pushRepos repos commitMessage
245-
case ms3Bucket of
246-
Just s3Bucket ->
247-
updateIndex00 oAwsDiscoveryMech s3Bucket
248-
_ -> return ()
249-
return mnewEtag
250-
threadDelay delay
251-
innerLoop newInfo mnewEtag
252-
let outerLoop = do
253-
catchAny
254-
(innerLoop reposInfoInit Nothing)
255-
(\e -> do
256-
hPutStrLn stderr $
257-
"ERROR: Received an unexpected exception while updating repositories: " ++
258-
show e
259-
threadDelay delay
260-
outerLoop)
261-
outerLoop
228+
withHackage (localPath </> "hackage-security-cache") $ \hackage -> do
229+
let onePass reposInfo forceUpdate = do
230+
putStrLn "Checking index"
231+
now <- getCurrentTime
232+
commitMessage <- getCommitMessage
233+
(newInfo, ()) <- withRepositories reposInfo $ \ repos -> do
234+
updated <- processIndexUpdate hackage repos now forceUpdate
235+
when updated $
236+
do pushRepos repos commitMessage
237+
case ms3Bucket of
238+
Just s3Bucket ->
239+
updateIndex00 oAwsDiscoveryMech s3Bucket
240+
_ -> return ()
241+
return newInfo
242+
let innerLoop reposInfo forceUpdate = do
243+
newInfo <- onePass reposInfo forceUpdate
244+
threadDelay delay
245+
innerLoop newInfo False
246+
let outerLoop = do
247+
catchAny
248+
-- A fresh process has no idea how far behind the repositories are,
249+
-- so the first pass runs whether or not the index moved.
250+
(innerLoop reposInfoInit True)
251+
(\e -> do
252+
hPutStrLn stderr $
253+
"ERROR: Received an unexpected exception while updating repositories: " ++
254+
show e
255+
threadDelay delay
256+
outerLoop)
257+
if oOneShot
258+
then void $ onePass reposInfoInit False
259+
else outerLoop

justfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ push-cachix:
1616
nix develop --profile .dev-profile -c true
1717
cachix push stackage-infrastructure .dev-profile
1818

19+
# Run the test suite that talks to the live Hackage repository
20+
test-integration:
21+
nix develop --command cabal test all-cabal-tool-integration --flags=integration
22+
1923
# Build with stack
2024
build-stack:
2125
stack build

nix/packages/all-cabal-tool.nix

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
{ mkDerivation, aeson, amazonka, amazonka-core, amazonka-s3, base
33
, byteable, bytestring, Cabal, classy-prelude-conduit, conduit
44
, conduit-extra, containers, cryptonite, cryptonite-conduit
5-
, directory, enclosed-exceptions, filepath, hit, hourglass
6-
, http-client, http-client-tls, http-conduit, http-types, lens, lib
7-
, memory, optparse-applicative, pretty, process, QuickCheck
8-
, resourcet, system-filepath, tar, tasty, tasty-quickcheck
9-
, temporary, text, utf8-string, yaml, zlib
5+
, directory, enclosed-exceptions, filepath, hackage-security, hit
6+
, hourglass, http-client, http-client-tls, http-conduit, http-types
7+
, lens, lib, memory, network-uri, optparse-applicative, pretty
8+
, process, QuickCheck, resourcet, system-filepath, tar, tasty
9+
, tasty-quickcheck, temporary, text, time, utf8-string, yaml, zlib
1010
}:
1111
mkDerivation {
1212
pname = "all-cabal-tool";
@@ -17,14 +17,16 @@ mkDerivation {
1717
libraryHaskellDepends = [
1818
aeson base byteable bytestring Cabal classy-prelude-conduit conduit
1919
conduit-extra containers cryptonite cryptonite-conduit directory
20-
enclosed-exceptions filepath hit hourglass http-client
21-
http-client-tls http-conduit http-types memory pretty process
22-
resourcet system-filepath tar text utf8-string yaml zlib
20+
enclosed-exceptions filepath hackage-security hit hourglass
21+
http-client http-client-tls http-conduit http-types memory
22+
network-uri pretty process resourcet system-filepath tar text time
23+
utf8-string yaml zlib
2324
];
2425
executableHaskellDepends = [
2526
aeson amazonka amazonka-core amazonka-s3 base bytestring
26-
classy-prelude-conduit conduit-extra directory http-client
27-
http-conduit http-types lens optparse-applicative temporary zlib
27+
classy-prelude-conduit conduit-extra directory hackage-security
28+
http-client http-conduit http-types lens optparse-applicative
29+
temporary zlib
2830
];
2931
testHaskellDepends = [
3032
aeson base bytestring containers hit QuickCheck tasty

src/Stackage/Package/Git/Object.hs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Data.Git.Storage
2323
import Data.Git.Storage.Object
2424
import Data.Git.Ref
2525
import Data.Git.Storage.Loose
26+
import GHC.Stack (HasCallStack)
2627
import System.Directory
2728
import System.FilePath
2829

@@ -40,17 +41,17 @@ data GitObject
4041
-- | Marshalls a bytestring into a git blob object, computes its SHA1,
4142
-- compresses it and returns a `GitFile` that is of type `NonExecFile`.
4243
makeGitFile
43-
:: (MonadThrow m, PrimMonad m)
44+
:: (HasCallStack, MonadThrow m, PrimMonad m)
4445
=> LByteString -- ^ Content of the blob.
4546
-> Word64 -- ^ Size of the content.
4647
-> m GitFile
4748
makeGitFile lbs sz = do
4849
(sha1, zipped) <-
4950
runConduit $
50-
srcWithHeader "blob" lbs sz =$=
51+
srcWithHeader "blob" lbs sz .|
5152
getZipSink ((,) <$> ZipSink sha1Sink <*> ZipSink compressSink)
5253
unless (sz == fromIntegral (L.length lbs)) $
53-
error "Stackage.Package.Git.makeGitFile: Size mismatch."
54+
error "Size mismatch."
5455
return $
5556
GitFile
5657
{ gitFileRef = unDigestRef sha1
@@ -122,11 +123,11 @@ srcWithHeader oType oContent oSize = do
122123

123124
compressSink
124125
:: (PrimMonad m, MonadThrow m)
125-
=> Consumer ByteString m ByteString
126-
compressSink = compress 1 defaultWindowBits =$= foldC
126+
=> ConduitT ByteString Void m ByteString
127+
compressSink = compress 1 defaultWindowBits .| foldC
127128

128129

129-
sha1Sink :: (Monad m) => Consumer ByteString m (Digest SHA1)
130+
sha1Sink :: (Monad m) => ConduitT ByteString Void m (Digest SHA1)
130131
sha1Sink = sinkHash
131132

132133

0 commit comments

Comments
 (0)