mirror of
https://github.com/Start9Labs/registry.git
synced 2026-03-26 02:11:53 +00:00
Feature/zip upload (#124)
* zip eos image on upload * fix warning * revert content length as its already set by module * move stream zipping to cli upload * refactor upload with simple gzip approach - progress bar broken * increase timeout * fix request body and unzip once uploaded * unzipped in wrong fn; cleanup * add progress bar for hashing and zipping, cleanup files * add messaging to progress bars
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -38,3 +38,4 @@ start9-registry.aux
|
|||||||
start9-registry.ps
|
start9-registry.ps
|
||||||
shell.nix
|
shell.nix
|
||||||
testdata/
|
testdata/
|
||||||
|
lbuild.sh
|
||||||
|
|||||||
@@ -378,7 +378,7 @@ makeAuthWare _ app req res = next
|
|||||||
-- | Warp settings for the given foundation value.
|
-- | Warp settings for the given foundation value.
|
||||||
warpSettings :: AppPort -> RegistryCtx -> Settings
|
warpSettings :: AppPort -> RegistryCtx -> Settings
|
||||||
warpSettings port foundation =
|
warpSettings port foundation =
|
||||||
setTimeout 60 $
|
setTimeout 90 $
|
||||||
setPort (fromIntegral port) $
|
setPort (fromIntegral port) $
|
||||||
setHost (appHost $ appSettings foundation) $
|
setHost (appHost $ appSettings foundation) $
|
||||||
setOnException
|
setOnException
|
||||||
|
|||||||
@@ -13,8 +13,15 @@ module Cli.Cli (
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import Conduit (
|
import Conduit (
|
||||||
|
ConduitT,
|
||||||
|
MonadIO,
|
||||||
|
awaitForever,
|
||||||
foldC,
|
foldC,
|
||||||
runConduit,
|
runConduit,
|
||||||
|
runConduitRes,
|
||||||
|
sinkFileCautious,
|
||||||
|
sourceFile,
|
||||||
|
yield,
|
||||||
(.|),
|
(.|),
|
||||||
)
|
)
|
||||||
import Control.Monad.Logger (
|
import Control.Monad.Logger (
|
||||||
@@ -29,6 +36,7 @@ import Crypto.Hash (
|
|||||||
SHA256 (SHA256),
|
SHA256 (SHA256),
|
||||||
hashWith,
|
hashWith,
|
||||||
)
|
)
|
||||||
|
import Crypto.Hash.Conduit (hashFile, sinkHash)
|
||||||
import Data.Aeson (
|
import Data.Aeson (
|
||||||
ToJSON,
|
ToJSON,
|
||||||
eitherDecodeStrict,
|
eitherDecodeStrict,
|
||||||
@@ -40,6 +48,7 @@ import Data.ByteArray.Encoding (
|
|||||||
import Data.ByteString.Char8 qualified as B8
|
import Data.ByteString.Char8 qualified as B8
|
||||||
import Data.ByteString.Lazy qualified as LB
|
import Data.ByteString.Lazy qualified as LB
|
||||||
import Data.Conduit.Process (readProcess)
|
import Data.Conduit.Process (readProcess)
|
||||||
|
import Data.Conduit.Zlib (gzip)
|
||||||
import Data.Default
|
import Data.Default
|
||||||
import Data.Functor.Contravariant (contramap)
|
import Data.Functor.Contravariant (contramap)
|
||||||
import Data.HashMap.Internal.Strict (
|
import Data.HashMap.Internal.Strict (
|
||||||
@@ -70,9 +79,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 (
|
||||||
@@ -82,6 +89,7 @@ import Network.HTTP.Client.Conduit (
|
|||||||
observedStreamFile,
|
observedStreamFile,
|
||||||
)
|
)
|
||||||
import Network.HTTP.Client.TLS (newTlsManager)
|
import Network.HTTP.Client.TLS (newTlsManager)
|
||||||
|
import Network.HTTP.Conduit (responseTimeoutMicro)
|
||||||
import Network.HTTP.Simple (
|
import Network.HTTP.Simple (
|
||||||
getResponseBody,
|
getResponseBody,
|
||||||
getResponseStatus,
|
getResponseStatus,
|
||||||
@@ -90,7 +98,9 @@ import Network.HTTP.Simple (
|
|||||||
parseRequest,
|
parseRequest,
|
||||||
setRequestBody,
|
setRequestBody,
|
||||||
setRequestBodyJSON,
|
setRequestBodyJSON,
|
||||||
setRequestHeaders, setRequestQueryString
|
setRequestHeaders,
|
||||||
|
setRequestQueryString,
|
||||||
|
setRequestResponseTimeout,
|
||||||
)
|
)
|
||||||
import Network.HTTP.Types (status200)
|
import Network.HTTP.Types (status200)
|
||||||
import Network.URI (
|
import Network.URI (
|
||||||
@@ -153,6 +163,7 @@ import Startlude (
|
|||||||
ReaderT (runReaderT),
|
ReaderT (runReaderT),
|
||||||
Semigroup ((<>)),
|
Semigroup ((<>)),
|
||||||
Show,
|
Show,
|
||||||
|
SomeException,
|
||||||
String,
|
String,
|
||||||
appendFile,
|
appendFile,
|
||||||
const,
|
const,
|
||||||
@@ -167,10 +178,12 @@ import Startlude (
|
|||||||
fromMaybe,
|
fromMaybe,
|
||||||
fst,
|
fst,
|
||||||
headMay,
|
headMay,
|
||||||
|
liftIO,
|
||||||
not,
|
not,
|
||||||
panic,
|
panic,
|
||||||
show,
|
show,
|
||||||
snd,
|
snd,
|
||||||
|
throwIO,
|
||||||
unlessM,
|
unlessM,
|
||||||
void,
|
void,
|
||||||
when,
|
when,
|
||||||
@@ -189,6 +202,7 @@ import System.Directory (
|
|||||||
getFileSize,
|
getFileSize,
|
||||||
getHomeDirectory,
|
getHomeDirectory,
|
||||||
listDirectory,
|
listDirectory,
|
||||||
|
removeFile,
|
||||||
)
|
)
|
||||||
import System.FilePath (
|
import System.FilePath (
|
||||||
takeDirectory,
|
takeDirectory,
|
||||||
@@ -197,15 +211,19 @@ import System.FilePath (
|
|||||||
)
|
)
|
||||||
import System.ProgressBar (
|
import System.ProgressBar (
|
||||||
Progress (..),
|
Progress (..),
|
||||||
|
ProgressBar,
|
||||||
|
Style (stylePrefix),
|
||||||
defStyle,
|
defStyle,
|
||||||
|
incProgress,
|
||||||
|
msg,
|
||||||
newProgressBar,
|
newProgressBar,
|
||||||
updateProgress,
|
updateProgress,
|
||||||
)
|
)
|
||||||
|
import UnliftIO.Exception (handle)
|
||||||
import Yesod (
|
import Yesod (
|
||||||
logError,
|
logError,
|
||||||
logWarn,
|
logWarn,
|
||||||
)
|
)
|
||||||
import Crypto.Hash.Conduit (hashFile)
|
|
||||||
|
|
||||||
|
|
||||||
data Upload = Upload
|
data Upload = Upload
|
||||||
@@ -215,6 +233,7 @@ data Upload = Upload
|
|||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
|
|
||||||
data EosUpload = EosUpload
|
data EosUpload = EosUpload
|
||||||
{ eosRepoName :: !String
|
{ eosRepoName :: !String
|
||||||
, eosPath :: !FilePath
|
, eosPath :: !FilePath
|
||||||
@@ -429,6 +448,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 $
|
||||||
@@ -543,6 +563,7 @@ upload (Upload name mpkg shouldIndex) = do
|
|||||||
noBody <-
|
noBody <-
|
||||||
parseRequest ("POST " <> show publishCfgRepoLocation <> "/admin/v0/upload")
|
parseRequest ("POST " <> show publishCfgRepoLocation <> "/admin/v0/upload")
|
||||||
<&> setRequestHeaders [("accept", "text/plain")]
|
<&> setRequestHeaders [("accept", "text/plain")]
|
||||||
|
<&> setRequestResponseTimeout (responseTimeoutMicro (90_000_000)) -- 90 seconds
|
||||||
<&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass)
|
<&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass)
|
||||||
size <- getFileSize pkg
|
size <- getFileSize pkg
|
||||||
bar <- newProgressBar defStyle 30 (Progress 0 (fromIntegral size) ())
|
bar <- newProgressBar defStyle 30 (Progress 0 (fromIntegral size) ())
|
||||||
@@ -572,21 +593,38 @@ 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) = handle @_ @SomeException cleanup $ do
|
||||||
PublishCfgRepo{..} <- findNameInCfg name
|
PublishCfgRepo{..} <- findNameInCfg name
|
||||||
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")]
|
||||||
|
<&> setRequestResponseTimeout (responseTimeoutMicro (90_000_000)) -- 90 seconds
|
||||||
|
<&> setRequestHeaders [("Content-Encoding", "gzip")]
|
||||||
<&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass)
|
<&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass)
|
||||||
size <- getFileSize img
|
size <- getFileSize img
|
||||||
|
hashBar <- newProgressBar defStyle{stylePrefix = msg "Hashing"} 30 (Progress 0 (fromIntegral size) ())
|
||||||
|
runConduitRes $ sourceFile img .| transByteCounter hashBar .| sinkHash @_ @SHA256
|
||||||
hash <- hashFile @_ @SHA256 img
|
hash <- hashFile @_ @SHA256 img
|
||||||
bar <- newProgressBar defStyle 30 (Progress 0 (fromIntegral size) ())
|
let compressedFilePath = "/tmp/eos.img.gz"
|
||||||
body <- observedStreamFile (updateProgress bar . const . sfs2prog) img
|
zipBar <- newProgressBar defStyle{stylePrefix = msg "Gzipping"} 30 (Progress 0 (fromIntegral size) ())
|
||||||
|
runConduitRes $
|
||||||
|
sourceFile img
|
||||||
|
.| transByteCounter zipBar
|
||||||
|
.| gzip
|
||||||
|
.| sinkFileCautious compressedFilePath
|
||||||
|
compressedSize <- getFileSize compressedFilePath
|
||||||
|
fileBar <- newProgressBar defStyle{stylePrefix = msg "Uploading"} 30 (Progress 0 (fromIntegral compressedSize) ())
|
||||||
|
body <- observedStreamFile (updateProgress fileBar . const . sfs2prog) $ compressedFilePath
|
||||||
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
|
||||||
|
removeFile compressedFilePath
|
||||||
if getResponseStatus res == status200
|
if getResponseStatus res == status200
|
||||||
then -- no output is successful
|
then -- no output is successful
|
||||||
pure ()
|
pure ()
|
||||||
@@ -597,6 +635,16 @@ eosUpload (EosUpload name img version) = do
|
|||||||
where
|
where
|
||||||
sfs2prog :: StreamFileStatus -> Progress ()
|
sfs2prog :: StreamFileStatus -> Progress ()
|
||||||
sfs2prog StreamFileStatus{..} = Progress (fromIntegral readSoFar) (fromIntegral fileSize) ()
|
sfs2prog StreamFileStatus{..} = Progress (fromIntegral readSoFar) (fromIntegral fileSize) ()
|
||||||
|
transByteCounter :: MonadIO m => ProgressBar a -> ConduitT B8.ByteString B8.ByteString m ()
|
||||||
|
transByteCounter bar = awaitForever $ \bs -> do
|
||||||
|
let len = B8.length bs
|
||||||
|
liftIO $ incProgress bar len
|
||||||
|
yield bs
|
||||||
|
cleanup e = do
|
||||||
|
$logError $ show e
|
||||||
|
removeFile "/tmp/eos.img.gz"
|
||||||
|
throwIO e
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import Conduit (
|
|||||||
sinkFile,
|
sinkFile,
|
||||||
(.|),
|
(.|),
|
||||||
)
|
)
|
||||||
|
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 +23,7 @@ import Data.Aeson (
|
|||||||
(.:?),
|
(.:?),
|
||||||
(.=),
|
(.=),
|
||||||
)
|
)
|
||||||
|
import Data.Conduit.Zlib (ungzip)
|
||||||
import Data.HashMap.Internal.Strict (
|
import Data.HashMap.Internal.Strict (
|
||||||
HashMap,
|
HashMap,
|
||||||
differenceWith,
|
differenceWith,
|
||||||
@@ -35,6 +37,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 +67,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 (
|
||||||
@@ -93,14 +94,11 @@ import Startlude (
|
|||||||
Eq,
|
Eq,
|
||||||
Int,
|
Int,
|
||||||
Maybe (..),
|
Maybe (..),
|
||||||
Monad ((>>=)),
|
|
||||||
Show,
|
Show,
|
||||||
SomeException (..),
|
SomeException (..),
|
||||||
Text,
|
Text,
|
||||||
asum,
|
asum,
|
||||||
fmap,
|
|
||||||
fromMaybe,
|
fromMaybe,
|
||||||
getCurrentTime,
|
|
||||||
guarded,
|
guarded,
|
||||||
hush,
|
hush,
|
||||||
isNothing,
|
isNothing,
|
||||||
@@ -110,8 +108,6 @@ import Startlude (
|
|||||||
show,
|
show,
|
||||||
toS,
|
toS,
|
||||||
traverse,
|
traverse,
|
||||||
void,
|
|
||||||
when,
|
|
||||||
zip,
|
zip,
|
||||||
($),
|
($),
|
||||||
(&&&),
|
(&&&),
|
||||||
@@ -197,8 +193,8 @@ postEosUploadR = do
|
|||||||
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
|
runConduit $ rawRequestBody .| ungzip .| sinkFile 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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user