zip eos image on upload

This commit is contained in:
Lucy Cifferello
2022-09-09 15:36:36 -06:00
parent 3aef9dbf09
commit 6f831e805d
4 changed files with 76 additions and 17 deletions

View File

@@ -74,6 +74,7 @@ dependencies:
- yesod-auth-basic - yesod-auth-basic
- yesod-core - yesod-core
- yesod-persistent - yesod-persistent
- zip-stream
library: library:
source-dirs: src source-dirs: src

View File

@@ -29,6 +29,7 @@ import Crypto.Hash (
SHA256 (SHA256), SHA256 (SHA256),
hashWith, hashWith,
) )
import Crypto.Hash.Conduit (hashFile)
import Data.Aeson ( import Data.Aeson (
ToJSON, ToJSON,
eitherDecodeStrict, eitherDecodeStrict,
@@ -70,9 +71,7 @@ import Handler.Admin (
PackageList (..), PackageList (..),
) )
import Lib.External.AppMgr (sourceManifest) import Lib.External.AppMgr (sourceManifest)
import Lib.Types.Core ( import Lib.Types.Core (PkgId (..))
PkgId (..),
)
import Lib.Types.Emver (Version (..)) import Lib.Types.Emver (Version (..))
import Lib.Types.Manifest (PackageManifest (..)) import Lib.Types.Manifest (PackageManifest (..))
import Network.HTTP.Client.Conduit ( import Network.HTTP.Client.Conduit (
@@ -90,7 +89,8 @@ import Network.HTTP.Simple (
parseRequest, parseRequest,
setRequestBody, setRequestBody,
setRequestBodyJSON, setRequestBodyJSON,
setRequestHeaders, setRequestQueryString setRequestHeaders,
setRequestQueryString,
) )
import Network.HTTP.Types (status200) import Network.HTTP.Types (status200)
import Network.URI ( import Network.URI (
@@ -150,6 +150,7 @@ import Startlude (
IsString (..), IsString (..),
Maybe (..), Maybe (..),
Monad ((>>=)), Monad ((>>=)),
Num (fromInteger),
ReaderT (runReaderT), ReaderT (runReaderT),
Semigroup ((<>)), Semigroup ((<>)),
Show, Show,
@@ -205,7 +206,6 @@ import Yesod (
logError, logError,
logWarn, logWarn,
) )
import Crypto.Hash.Conduit (hashFile)
data Upload = Upload data Upload = Upload
@@ -215,6 +215,7 @@ data Upload = Upload
} }
deriving (Show) deriving (Show)
data EosUpload = EosUpload data EosUpload = EosUpload
{ eosRepoName :: !String { eosRepoName :: !String
, eosPath :: !FilePath , eosPath :: !FilePath
@@ -429,6 +430,7 @@ parsePkgCat = subparser $ command "categorize" (info cat $ progDesc "Add or remo
<*> strArgument (metavar "PACKAGE_ID") <*> strArgument (metavar "PACKAGE_ID")
<*> strArgument (metavar "CATEGORY") <*> strArgument (metavar "CATEGORY")
parseEosPublish :: Parser EosUpload parseEosPublish :: Parser EosUpload
parseEosPublish = parseEosPublish =
subparser $ subparser $
@@ -572,19 +574,25 @@ upload (Upload name mpkg shouldIndex) = do
sfs2prog :: StreamFileStatus -> Progress () sfs2prog :: StreamFileStatus -> Progress ()
sfs2prog StreamFileStatus{..} = Progress (fromIntegral readSoFar) (fromIntegral fileSize) () sfs2prog StreamFileStatus{..} = Progress (fromIntegral readSoFar) (fromIntegral fileSize) ()
eosUpload :: EosUpload -> IO () eosUpload :: EosUpload -> IO ()
eosUpload (EosUpload name img version) = do eosUpload (EosUpload name img version) = do
PublishCfgRepo{..} <- findNameInCfg name PublishCfgRepo{..} <- findNameInCfg name
size <- fromInteger <$> getFileSize img
noBody <- noBody <-
parseRequest ("POST " <> show publishCfgRepoLocation <> "/admin/v0/eos-upload") parseRequest ("POST " <> show publishCfgRepoLocation <> "/admin/v0/eos-upload")
<&> setRequestHeaders [("accept", "text/plain")] <&> setRequestHeaders [("accept", "text/plain")]
<&> setRequestHeaders [("Content-Encoding", "gzip")]
<&> setRequestHeaders [("Content-Length", show size)]
<&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass) <&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass)
size <- getFileSize img
hash <- hashFile @_ @SHA256 img hash <- hashFile @_ @SHA256 img
bar <- newProgressBar defStyle 30 (Progress 0 (fromIntegral size) ()) bar <- newProgressBar defStyle 30 (Progress 0 (fromIntegral size) ())
body <- observedStreamFile (updateProgress bar . const . sfs2prog) img body <- observedStreamFile (updateProgress bar . const . sfs2prog) img
let withBody = setRequestBody body noBody let withBody = setRequestBody body noBody
let withQParams = setRequestQueryString [("version", Just $ show version), ("hash", Just $ convertToBase Base16 hash)] withBody let withQParams =
setRequestQueryString
[("version", Just $ show version), ("hash", Just $ convertToBase Base16 hash)]
withBody
manager <- newTlsManager manager <- newTlsManager
res <- runReaderT (httpLbs withQParams) manager res <- runReaderT (httpLbs withQParams) manager
if getResponseStatus res == status200 if getResponseStatus res == status200
@@ -598,6 +606,7 @@ eosUpload (EosUpload name img version) = do
sfs2prog :: StreamFileStatus -> Progress () sfs2prog :: StreamFileStatus -> Progress ()
sfs2prog StreamFileStatus{..} = Progress (fromIntegral readSoFar) (fromIntegral fileSize) () sfs2prog StreamFileStatus{..} = Progress (fromIntegral readSoFar) (fromIntegral fileSize) ()
index :: String -> String -> Version -> IO () index :: String -> String -> Version -> IO ()
index name pkg v = performHttp name "POST" [i|/admin/v0/index|] (IndexPkgReq (PkgId $ toS pkg) v) index name pkg v = performHttp name "POST" [i|/admin/v0/index|] (IndexPkgReq (PkgId $ toS pkg) v)

View File

@@ -5,11 +5,30 @@
module Handler.Admin where module Handler.Admin where
import Codec.Archive.Zip.Conduit.Zip (
ZipData (ZipDataSource),
ZipEntry (
ZipEntry,
zipEntryExternalAttributes,
zipEntryName,
zipEntrySize,
zipEntryTime
),
ZipInfo (..),
ZipOptions (..),
zipStream,
)
import Conduit ( import Conduit (
ConduitT,
MonadUnliftIO,
fuseBoth,
runConduit, runConduit,
sinkFile, sinkFile,
sinkFileBS,
yield,
(.|), (.|),
) )
import Control.Monad.Extra
import Control.Monad.Reader.Has (ask) import Control.Monad.Reader.Has (ask)
import Control.Monad.Trans.Maybe (MaybeT (..)) import Control.Monad.Trans.Maybe (MaybeT (..))
import Data.Aeson ( import Data.Aeson (
@@ -22,6 +41,7 @@ import Data.Aeson (
(.:?), (.:?),
(.=), (.=),
) )
import Data.ByteString (ByteString)
import Data.HashMap.Internal.Strict ( import Data.HashMap.Internal.Strict (
HashMap, HashMap,
differenceWith, differenceWith,
@@ -35,6 +55,7 @@ import Data.List (
import Data.String.Interpolate.IsString ( import Data.String.Interpolate.IsString (
i, i,
) )
import Data.Time
import Database.Persist ( import Database.Persist (
Entity (entityKey), Entity (entityKey),
PersistStoreRead (get), PersistStoreRead (get),
@@ -64,9 +85,7 @@ import Lib.PkgRepository (
getPackages, getPackages,
getVersionsFor, getVersionsFor,
) )
import Lib.Types.Core ( import Lib.Types.Core (PkgId (unPkgId))
PkgId (unPkgId),
)
import Lib.Types.Emver (Version (..)) import Lib.Types.Emver (Version (..))
import Lib.Types.Manifest (PackageManifest (..)) import Lib.Types.Manifest (PackageManifest (..))
import Model ( import Model (
@@ -90,28 +109,29 @@ import Settings
import Startlude ( import Startlude (
Applicative (pure), Applicative (pure),
Bool (..), Bool (..),
Either (Right),
Eq, Eq,
FilePath,
Int, Int,
Maybe (..), Maybe (..),
Monad ((>>=)),
Show, Show,
SomeException (..), SomeException (..),
Text, Text,
Word64,
asum, asum,
fmap, decodeUtf8,
encodeUtf8,
fromMaybe, fromMaybe,
getCurrentTime,
guarded, guarded,
hush, hush,
isNothing, isNothing,
liftIO, liftIO,
not, not,
readMaybe,
replicate, replicate,
show, show,
toS, toS,
traverse, traverse,
void,
when,
zip, zip,
($), ($),
(&&&), (&&&),
@@ -140,6 +160,7 @@ import Yesod (
delete, delete,
getsYesod, getsYesod,
logError, logError,
lookupHeader,
rawRequestBody, rawRequestBody,
requireCheckJsonBody, requireCheckJsonBody,
runDB, runDB,
@@ -193,16 +214,41 @@ postEosUploadR = do
hash <- case maybeHash of hash <- case maybeHash of
Nothing -> sendResponseStatus status400 ("Missing Hash" :: Text) Nothing -> sendResponseStatus status400 ("Missing Hash" :: Text)
Just h -> pure h Just h -> pure h
(maybeSize :: Maybe Word64) <- maybeM (pure Nothing) (pure . readMaybe . decodeUtf8) (lookupHeader "Content-Length")
resourcesTemp <- getsYesod $ (</> "temp") . resourcesDir . appSettings resourcesTemp <- getsYesod $ (</> "temp") . resourcesDir . appSettings
createDirectoryIfMissing True resourcesTemp createDirectoryIfMissing True resourcesTemp
withTempDirectory resourcesTemp "neweos" $ \dir -> do withTempDirectory resourcesTemp "neweos" $ \dir -> do
let path = dir </> "eos" <.> "img" let path = dir </> "eos" <.> "img"
runConduit $ rawRequestBody .| sinkFile path void . runConduit $ createZipEntry path maybeSize rawRequestBody .| (zipStream zipOptions `fuseBoth` sinkFileBS path)
_ <- runDB $ upsert (EosHash version hash) [EosHashHash =. hash] void . runDB $ upsert (EosHash version hash) [EosHashHash =. hash]
let targetPath = root </> show version let targetPath = root </> show version
removePathForcibly targetPath removePathForcibly targetPath
createDirectoryIfMissing True targetPath createDirectoryIfMissing True targetPath
renameDirectory dir targetPath renameDirectory dir targetPath
where
zipOptions =
ZipOptions
{ zipOpt64 = True
, zipOptCompressLevel = -1
, zipOptInfo = ZipInfo{zipComment = encodeUtf8 "zipped eos image"}
}
createZipEntry ::
(MonadUnliftIO m) =>
FilePath ->
Maybe Word64 ->
ConduitT () ByteString m () ->
ConduitT () (ZipEntry, ZipData m) m ()
createZipEntry path size dataSource = do
utcTime <- liftIO getCurrentTime
localTimeZone <- liftIO $ getTimeZone utcTime
let zipEntry =
ZipEntry
{ zipEntryName = Right $ encodeUtf8 $ show path
, zipEntryTime = utcToLocalTime localTimeZone utcTime
, zipEntrySize = size
, zipEntryExternalAttributes = Nothing
}
yield (zipEntry, ZipDataSource dataSource)
data IndexPkgReq = IndexPkgReq data IndexPkgReq = IndexPkgReq

View File

@@ -97,6 +97,7 @@ getVersionSpecFromQuery = do
Nothing -> sendResponseStatus status400 ("Invalid App Version Specification" :: Text) Nothing -> sendResponseStatus status400 ("Invalid App Version Specification" :: Text)
Just t -> pure t Just t -> pure t
getVersionFromQuery :: MonadHandler m => m (Maybe Version) getVersionFromQuery :: MonadHandler m => m (Maybe Version)
getVersionFromQuery = do getVersionFromQuery = do
versionString <- lookupGetParam "version" versionString <- lookupGetParam "version"
@@ -106,9 +107,11 @@ getVersionFromQuery = do
Nothing -> sendResponseStatus status400 ("Invalid Version" :: Text) Nothing -> sendResponseStatus status400 ("Invalid Version" :: Text)
Just t -> pure (Just t) Just t -> pure (Just t)
getHashFromQuery :: MonadHandler m => m (Maybe Text) getHashFromQuery :: MonadHandler m => m (Maybe Text)
getHashFromQuery = lookupGetParam "hash" getHashFromQuery = lookupGetParam "hash"
versionPriorityFromQueryIsMin :: MonadHandler m => m Bool versionPriorityFromQueryIsMin :: MonadHandler m => m Bool
versionPriorityFromQueryIsMin = do versionPriorityFromQueryIsMin = do
priorityString <- lookupGetParam "version-priority" priorityString <- lookupGetParam "version-priority"