mirror of
https://github.com/commercialhaskell/stackage.git
synced 2026-02-01 17:10:25 +01:00
ServerBundle and Upload module
This commit is contained in:
parent
00c546faee
commit
0a234a5f51
63
Stackage2/ServerBundle.hs
Normal file
63
Stackage2/ServerBundle.hs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
-- | Create a bundle to be uploaded to Stackage Server.
|
||||||
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
module Stackage2.ServerBundle
|
||||||
|
( serverBundle
|
||||||
|
, epochTime
|
||||||
|
, bpAllPackages
|
||||||
|
) where
|
||||||
|
|
||||||
|
import qualified Codec.Archive.Tar as Tar
|
||||||
|
import qualified Codec.Archive.Tar.Entry as Tar
|
||||||
|
import qualified Codec.Compression.GZip as GZip
|
||||||
|
import qualified Data.Yaml as Y
|
||||||
|
import Foreign.C.Types (CTime (CTime))
|
||||||
|
import Stackage2.BuildConstraints
|
||||||
|
import Stackage2.BuildPlan
|
||||||
|
import Stackage2.Prelude
|
||||||
|
import qualified System.PosixCompat.Time as PC
|
||||||
|
|
||||||
|
-- | Get current time
|
||||||
|
epochTime :: IO Tar.EpochTime
|
||||||
|
epochTime = (\(CTime t) -> t) <$> PC.epochTime
|
||||||
|
|
||||||
|
-- | All package/versions in a build plan, including core packages.
|
||||||
|
--
|
||||||
|
-- Note that this may include packages not available on Hackage.
|
||||||
|
bpAllPackages :: BuildPlan -> Map PackageName Version
|
||||||
|
bpAllPackages BuildPlan {..} =
|
||||||
|
siCorePackages bpSystemInfo ++ map ppVersion bpPackages
|
||||||
|
|
||||||
|
serverBundle :: Tar.EpochTime
|
||||||
|
-> Text -- ^ title
|
||||||
|
-> Text -- ^ slug
|
||||||
|
-> BuildPlan
|
||||||
|
-> LByteString
|
||||||
|
serverBundle time title slug bp@BuildPlan {..} = GZip.compress $ Tar.write
|
||||||
|
[ fe "build-plan.yaml" (fromStrict $ Y.encode bp)
|
||||||
|
, fe "hackage" hackage
|
||||||
|
, fe "slug" (fromStrict $ encodeUtf8 slug)
|
||||||
|
, fe "desc" (fromStrict $ encodeUtf8 title)
|
||||||
|
]
|
||||||
|
where
|
||||||
|
fe name contents =
|
||||||
|
case Tar.toTarPath False name of
|
||||||
|
Left s -> error s
|
||||||
|
Right name' -> (Tar.fileEntry name' contents)
|
||||||
|
{ Tar.entryTime = time
|
||||||
|
}
|
||||||
|
hackage = builderToLazy $ foldMap goPair $ mapToList packageMap
|
||||||
|
|
||||||
|
-- need to remove some packages that don't exist on Hackage
|
||||||
|
packageMap = foldr deleteMap (bpAllPackages bp) $ map PackageName
|
||||||
|
[ "bin-package-db"
|
||||||
|
, "ghc"
|
||||||
|
, "rts"
|
||||||
|
]
|
||||||
|
|
||||||
|
goPair (name, version) =
|
||||||
|
toBuilder (display name) ++
|
||||||
|
toBuilder (asText "-") ++
|
||||||
|
toBuilder (display version) ++
|
||||||
|
toBuilder (asText "\n")
|
||||||
229
Stackage2/Upload.hs
Normal file
229
Stackage2/Upload.hs
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
-- | Upload to Stackage and Hackage
|
||||||
|
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||||
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
{-# LANGUAGE TupleSections #-}
|
||||||
|
module Stackage2.Upload
|
||||||
|
( UploadBundle (..)
|
||||||
|
, SnapshotIdent (..)
|
||||||
|
, uploadBundle
|
||||||
|
, UploadDocs (..)
|
||||||
|
, uploadDocs
|
||||||
|
, uploadHackageDistro
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Monad.Writer.Strict (execWriter, tell)
|
||||||
|
import Data.Default.Class (Default (..))
|
||||||
|
import Filesystem (isDirectory, isFile)
|
||||||
|
import Network.HTTP.Client
|
||||||
|
import Network.HTTP.Client.MultipartFormData
|
||||||
|
import Stackage2.BuildPlan (BuildPlan)
|
||||||
|
import Stackage2.Prelude
|
||||||
|
import Stackage2.ServerBundle (bpAllPackages)
|
||||||
|
import System.IO.Temp (withSystemTempFile)
|
||||||
|
|
||||||
|
newtype StackageServer = StackageServer { unStackageServer :: Text }
|
||||||
|
deriving (Show, Eq, Ord, Hashable, IsString)
|
||||||
|
instance Default StackageServer where
|
||||||
|
def = "http://www.stackage.org"
|
||||||
|
|
||||||
|
data UploadBundle = UploadBundle
|
||||||
|
{ ubServer :: StackageServer
|
||||||
|
, ubContents :: LByteString
|
||||||
|
, ubAlias :: Maybe Text
|
||||||
|
, ubNightly :: Maybe Text -- ^ should be GHC version
|
||||||
|
, ubLTS :: Maybe Text -- ^ e.g. 2.3
|
||||||
|
, ubAuthToken :: Text
|
||||||
|
}
|
||||||
|
instance Default UploadBundle where
|
||||||
|
def = UploadBundle
|
||||||
|
{ ubServer = def
|
||||||
|
, ubContents = mempty
|
||||||
|
, ubAlias = Nothing
|
||||||
|
, ubNightly = Nothing
|
||||||
|
, ubLTS = Nothing
|
||||||
|
, ubAuthToken = "no-auth-token-provided"
|
||||||
|
}
|
||||||
|
|
||||||
|
newtype SnapshotIdent = SnapshotIdent { unSnapshotIdent :: Text }
|
||||||
|
deriving (Show, Eq, Ord, Hashable, IsString)
|
||||||
|
|
||||||
|
uploadBundle :: UploadBundle -> Manager -> IO SnapshotIdent
|
||||||
|
uploadBundle UploadBundle {..} man = do
|
||||||
|
req1 <- parseUrl $ unpack $ unStackageServer ubServer ++ "/upload"
|
||||||
|
req2 <- formDataBody formData req1
|
||||||
|
let req3 = req2
|
||||||
|
{ method = "PUT"
|
||||||
|
, requestHeaders =
|
||||||
|
[ ("Authorization", encodeUtf8 ubAuthToken)
|
||||||
|
, ("Accept", "application/json")
|
||||||
|
] ++ requestHeaders req2
|
||||||
|
, redirectCount = 0
|
||||||
|
, checkStatus = \_ _ _ -> Nothing
|
||||||
|
, responseTimeout = Just 300000000
|
||||||
|
}
|
||||||
|
res <- httpLbs req3 man
|
||||||
|
case lookup "x-stackage-ident" $ responseHeaders res of
|
||||||
|
Just snapid -> return $ SnapshotIdent $ decodeUtf8 snapid
|
||||||
|
Nothing -> error $ "An error occurred: " ++ show res
|
||||||
|
where
|
||||||
|
params = mapMaybe (\(x, y) -> (x, ) <$> y)
|
||||||
|
[ ("alias", ubAlias)
|
||||||
|
, ("nightly", ubNightly)
|
||||||
|
, ("lts", ubLTS)
|
||||||
|
]
|
||||||
|
formData = ($ []) $ execWriter $ do
|
||||||
|
forM_ params $ \(key, value) ->
|
||||||
|
tell' $ partBS key $ encodeUtf8 value
|
||||||
|
tell' $ partFileRequestBody "stackage" "stackage"
|
||||||
|
$ RequestBodyLBS ubContents
|
||||||
|
|
||||||
|
tell' x = tell (x:)
|
||||||
|
|
||||||
|
data UploadDocs = UploadDocs
|
||||||
|
{ udServer :: StackageServer
|
||||||
|
, udDocs :: FilePath -- ^ may be a directory or a tarball
|
||||||
|
, udAuthToken :: Text
|
||||||
|
, udSnapshot :: SnapshotIdent
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadDocs (UploadDocs (StackageServer host) fp0 token ident) man = do
|
||||||
|
fe <- isFile fp0
|
||||||
|
if fe
|
||||||
|
then uploadDocsFile $ fpToString fp0
|
||||||
|
else do
|
||||||
|
de <- isDirectory fp0
|
||||||
|
if de
|
||||||
|
then uploadDocsDir
|
||||||
|
else error $ "Path not found: " ++ fpToString fp0
|
||||||
|
where
|
||||||
|
uploadDocsDir = withSystemTempFile "haddocks.tar.xz" $ \fp h -> do
|
||||||
|
hClose h
|
||||||
|
dirs <- fmap sort
|
||||||
|
$ runResourceT
|
||||||
|
$ sourceDirectory fp0
|
||||||
|
$$ filterMC (liftIO . isDirectory)
|
||||||
|
=$ mapC (fpToString . filename)
|
||||||
|
=$ sinkList
|
||||||
|
writeFile (fp0 </> "index.html") $ mkIndex
|
||||||
|
(unpack $ unSnapshotIdent ident)
|
||||||
|
dirs
|
||||||
|
writeFile (fp0 </> "style.css") styleCss
|
||||||
|
-- FIXME write index.html, style.css
|
||||||
|
let cp = (proc "tar" $ "cJf" : fp : "index.html" : "style.css" : dirs)
|
||||||
|
{ cwd = Just $ fpToString fp0
|
||||||
|
}
|
||||||
|
withCheckedProcess cp $ \Inherited Inherited Inherited -> return ()
|
||||||
|
uploadDocsFile fp
|
||||||
|
uploadDocsFile fp = do
|
||||||
|
req1 <- parseUrl $ unpack $ concat
|
||||||
|
[ host
|
||||||
|
, "/upload-haddock/"
|
||||||
|
, unSnapshotIdent ident
|
||||||
|
]
|
||||||
|
let formData =
|
||||||
|
[ partFileSource "tarball" fp
|
||||||
|
]
|
||||||
|
req2 <- formDataBody formData req1
|
||||||
|
let req3 = req2
|
||||||
|
{ method = "PUT"
|
||||||
|
, requestHeaders =
|
||||||
|
[ ("Authorization", encodeUtf8 token)
|
||||||
|
, ("Accept", "application/json")
|
||||||
|
] ++ requestHeaders req2
|
||||||
|
, redirectCount = 0
|
||||||
|
, checkStatus = \_ _ _ -> Nothing
|
||||||
|
, responseTimeout = Just 300000000
|
||||||
|
}
|
||||||
|
httpLbs req3 man
|
||||||
|
|
||||||
|
uploadHackageDistro :: BuildPlan
|
||||||
|
-> ByteString -- ^ Hackage username
|
||||||
|
-> ByteString -- ^ Hackage password
|
||||||
|
-> Manager
|
||||||
|
-> IO (Response LByteString)
|
||||||
|
uploadHackageDistro bp username password =
|
||||||
|
httpLbs (applyBasicAuth username password req)
|
||||||
|
where
|
||||||
|
csv = encodeUtf8
|
||||||
|
$ builderToLazy
|
||||||
|
$ mconcat
|
||||||
|
$ intersperse "\n"
|
||||||
|
$ map go
|
||||||
|
$ mapToList
|
||||||
|
$ bpAllPackages bp
|
||||||
|
go (name, version) =
|
||||||
|
"\"" ++
|
||||||
|
(toBuilder $ display name) ++
|
||||||
|
"\",\"" ++
|
||||||
|
(toBuilder $ display version) ++
|
||||||
|
"\",\"http://www.stackage.org/package/" ++
|
||||||
|
(toBuilder $ display name) ++
|
||||||
|
"\""
|
||||||
|
|
||||||
|
req = "http://hackage.haskell.org/distro/Stackage/packages.csv"
|
||||||
|
{ requestHeaders = [("Content-Type", "text/csv")]
|
||||||
|
, requestBody = RequestBodyLBS csv
|
||||||
|
, checkStatus = \_ _ _ -> Nothing
|
||||||
|
, method = "PUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
mkIndex :: String -> [String] -> String
|
||||||
|
mkIndex snapid dirs = concat
|
||||||
|
[ "<!DOCTYPE html>\n<html lang='en'><head><title>Haddocks index</title>"
|
||||||
|
, "<link rel='stylesheet' href='https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css'>"
|
||||||
|
, "<link rel='stylesheet' href='style.css'>"
|
||||||
|
, "<link rel='shortcut icon' href='http://www.stackage.org/static/img/favicon.ico' />"
|
||||||
|
, "</head>"
|
||||||
|
, "<body><div class='container'>"
|
||||||
|
, "<div class='row'><div class='span12 col-md-12'>"
|
||||||
|
, "<h1>Haddock documentation index</h1>"
|
||||||
|
, "<p class='return'><a href=\"http://www.stackage.org/stackage/"
|
||||||
|
, snapid
|
||||||
|
, "\">Return to snapshot</a></p><ul>"
|
||||||
|
, concatMap toLI dirs
|
||||||
|
, "</ul></div></div></div></body></html>"
|
||||||
|
]
|
||||||
|
where
|
||||||
|
toLI name = concat
|
||||||
|
[ "<li><a href='"
|
||||||
|
, name
|
||||||
|
, "/index.html'>"
|
||||||
|
, name
|
||||||
|
, "</a></li>"
|
||||||
|
]
|
||||||
|
|
||||||
|
styleCss :: String
|
||||||
|
styleCss = concat
|
||||||
|
[ "@media (min-width: 530px) {"
|
||||||
|
, "ul { -webkit-column-count: 2; -moz-column-count: 2; column-count: 2 }"
|
||||||
|
, "}"
|
||||||
|
, "@media (min-width: 760px) {"
|
||||||
|
, "ul { -webkit-column-count: 3; -moz-column-count: 3; column-count: 3 }"
|
||||||
|
, "}"
|
||||||
|
, "ul {"
|
||||||
|
, " margin-left: 0;"
|
||||||
|
, " padding-left: 0;"
|
||||||
|
, " list-style-type: none;"
|
||||||
|
, "}"
|
||||||
|
, "body {"
|
||||||
|
, " background: #f0f0f0;"
|
||||||
|
, " font-family: 'Lato', sans-serif;"
|
||||||
|
, " text-shadow: 1px 1px 1px #ffffff;"
|
||||||
|
, " font-size: 20px;"
|
||||||
|
, " line-height: 30px;"
|
||||||
|
, " padding-bottom: 5em;"
|
||||||
|
, "}"
|
||||||
|
, "h1 {"
|
||||||
|
, " font-weight: normal;"
|
||||||
|
, " color: #06537d;"
|
||||||
|
, " font-size: 45px;"
|
||||||
|
, "}"
|
||||||
|
, ".return a {"
|
||||||
|
, " color: #06537d;"
|
||||||
|
, " font-style: italic;"
|
||||||
|
, "}"
|
||||||
|
, ".return {"
|
||||||
|
, " margin-bottom: 1em;"
|
||||||
|
, "}"]
|
||||||
@ -40,6 +40,8 @@ library
|
|||||||
Stackage2.UpdateBuildPlan
|
Stackage2.UpdateBuildPlan
|
||||||
Stackage2.GithubPings
|
Stackage2.GithubPings
|
||||||
Stackage2.PackageDescription
|
Stackage2.PackageDescription
|
||||||
|
Stackage2.ServerBundle
|
||||||
|
Stackage2.Upload
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
, containers
|
, containers
|
||||||
, Cabal >= 1.14
|
, Cabal >= 1.14
|
||||||
@ -60,6 +62,11 @@ library
|
|||||||
, system-fileio
|
, system-fileio
|
||||||
, mtl
|
, mtl
|
||||||
, aeson
|
, aeson
|
||||||
|
, yaml
|
||||||
|
, unix-compat
|
||||||
|
, http-client
|
||||||
|
, temporary
|
||||||
|
, data-default-class
|
||||||
|
|
||||||
executable stackage
|
executable stackage
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user