mirror of
https://github.com/Start9Labs/registry.git
synced 2026-03-26 02:11:53 +00:00
hash on upload (#123)
* hash on upload * Update src/Cli/Cli.hs * fix import and clean up error response * remove content length limit * remove import * lift version * slow af but works Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
-- ADMIN API V0
|
-- ADMIN API V0
|
||||||
/admin/v0/upload PkgUploadR POST !admin
|
/admin/v0/upload PkgUploadR POST !admin
|
||||||
|
/admin/v0/eos-upload EosUploadR POST !admin
|
||||||
/admin/v0/index PkgIndexR POST !admin
|
/admin/v0/index PkgIndexR POST !admin
|
||||||
/admin/v0/deindex PkgDeindexR GET POST !admin
|
/admin/v0/deindex PkgDeindexR GET POST !admin
|
||||||
/admin/v0/category/#Text CategoryR POST DELETE !admin
|
/admin/v0/category/#Text CategoryR POST DELETE !admin
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ import Startlude (
|
|||||||
killThread,
|
killThread,
|
||||||
newEmptyMVar,
|
newEmptyMVar,
|
||||||
newMVar,
|
newMVar,
|
||||||
onException,
|
|
||||||
panic,
|
panic,
|
||||||
print,
|
print,
|
||||||
putMVar,
|
putMVar,
|
||||||
@@ -180,6 +179,7 @@ import Handler.Admin (
|
|||||||
deletePkgCategorizeR,
|
deletePkgCategorizeR,
|
||||||
getPkgDeindexR,
|
getPkgDeindexR,
|
||||||
postCategoryR,
|
postCategoryR,
|
||||||
|
postEosUploadR,
|
||||||
postPkgCategorizeR,
|
postPkgCategorizeR,
|
||||||
postPkgDeindexR,
|
postPkgDeindexR,
|
||||||
postPkgIndexR,
|
postPkgIndexR,
|
||||||
@@ -187,7 +187,6 @@ import Handler.Admin (
|
|||||||
)
|
)
|
||||||
import Handler.Eos (getEosR, getEosVersionR)
|
import Handler.Eos (getEosR, getEosVersionR)
|
||||||
import Handler.Package
|
import Handler.Package
|
||||||
import Lib.PkgRepository (watchEosRepoRoot)
|
|
||||||
import Lib.Ssl (
|
import Lib.Ssl (
|
||||||
doesSslNeedRenew,
|
doesSslNeedRenew,
|
||||||
renewSslCerts,
|
renewSslCerts,
|
||||||
@@ -240,12 +239,12 @@ makeFoundation appSettings = do
|
|||||||
-- logging function. To get out of this loop, we initially create a
|
-- logging function. To get out of this loop, we initially create a
|
||||||
-- temporary foundation without a real connection pool, get a log function
|
-- temporary foundation without a real connection pool, get a log function
|
||||||
-- from there, and then create the real foundation.
|
-- from there, and then create the real foundation.
|
||||||
let mkFoundation appConnPool appStopFsNotifyEos = RegistryCtx{..}
|
let mkFoundation appConnPool = RegistryCtx{..}
|
||||||
-- The RegistryCtx {..} syntax is an example of record wild cards. For more
|
-- The RegistryCtx {..} syntax is an example of record wild cards. For more
|
||||||
-- information, see:
|
-- information, see:
|
||||||
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
|
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
|
||||||
tempFoundation =
|
tempFoundation =
|
||||||
mkFoundation (panic "connPool forced in tempFoundation") (panic "stopFsNotify forced in tempFoundation")
|
mkFoundation (panic "connPool forced in tempFoundation")
|
||||||
logFunc = messageLoggerSource tempFoundation appLogger
|
logFunc = messageLoggerSource tempFoundation appLogger
|
||||||
|
|
||||||
createDirectoryIfMissing True (errorLogRoot appSettings)
|
createDirectoryIfMissing True (errorLogRoot appSettings)
|
||||||
@@ -255,8 +254,6 @@ makeFoundation appSettings = do
|
|||||||
flip runLoggingT logFunc $
|
flip runLoggingT logFunc $
|
||||||
createPostgresqlPool (pgConnStr $ appDatabaseConf appSettings) (pgPoolSize . appDatabaseConf $ appSettings)
|
createPostgresqlPool (pgConnStr $ appDatabaseConf appSettings) (pgPoolSize . appDatabaseConf $ appSettings)
|
||||||
|
|
||||||
stopEosWatch <- runLoggingT (runReaderT (watchEosRepoRoot pool) appSettings) logFunc
|
|
||||||
|
|
||||||
runSqlPool
|
runSqlPool
|
||||||
(Database.Persist.Migration.Postgres.runMigration Database.Persist.Migration.defaultSettings manualMigration)
|
(Database.Persist.Migration.Postgres.runMigration Database.Persist.Migration.defaultSettings manualMigration)
|
||||||
pool
|
pool
|
||||||
@@ -264,7 +261,7 @@ makeFoundation appSettings = do
|
|||||||
runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc
|
runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc
|
||||||
|
|
||||||
-- Return the foundation
|
-- Return the foundation
|
||||||
return $ mkFoundation pool stopEosWatch
|
return $ mkFoundation pool
|
||||||
|
|
||||||
|
|
||||||
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
||||||
@@ -449,7 +446,7 @@ startWeb foundation = do
|
|||||||
app <- makeApplication foundation
|
app <- makeApplication foundation
|
||||||
startWeb' app
|
startWeb' app
|
||||||
where
|
where
|
||||||
startWeb' app = (`onException` appStopFsNotifyEos foundation) $ do
|
startWeb' app = do
|
||||||
let AppSettings{..} = appSettings foundation
|
let AppSettings{..} = appSettings foundation
|
||||||
runLog $ $logInfo [i|Launching Tor Web Server on port #{torPort}|]
|
runLog $ $logInfo [i|Launching Tor Web Server on port #{torPort}|]
|
||||||
torAction <- async $ runSettings (warpSettings torPort foundation) app
|
torAction <- async $ runSettings (warpSettings torPort foundation) app
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ import Network.HTTP.Simple (
|
|||||||
parseRequest,
|
parseRequest,
|
||||||
setRequestBody,
|
setRequestBody,
|
||||||
setRequestBodyJSON,
|
setRequestBodyJSON,
|
||||||
setRequestHeaders,
|
setRequestHeaders, setRequestQueryString
|
||||||
)
|
)
|
||||||
import Network.HTTP.Types (status200)
|
import Network.HTTP.Types (status200)
|
||||||
import Network.URI (
|
import Network.URI (
|
||||||
@@ -205,6 +205,7 @@ import Yesod (
|
|||||||
logError,
|
logError,
|
||||||
logWarn,
|
logWarn,
|
||||||
)
|
)
|
||||||
|
import Crypto.Hash.Conduit (hashFile)
|
||||||
|
|
||||||
|
|
||||||
data Upload = Upload
|
data Upload = Upload
|
||||||
@@ -214,6 +215,13 @@ data Upload = Upload
|
|||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
|
data EosUpload = EosUpload
|
||||||
|
{ eosRepoName :: !String
|
||||||
|
, eosPath :: !FilePath
|
||||||
|
, eosVersion :: !Version
|
||||||
|
}
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
|
||||||
newtype PublishCfg = PublishCfg
|
newtype PublishCfg = PublishCfg
|
||||||
{ publishCfgRepos :: HashMap String PublishCfgRepo
|
{ publishCfgRepos :: HashMap String PublishCfgRepo
|
||||||
@@ -260,6 +268,7 @@ data Command
|
|||||||
| CmdCatDel !String !String
|
| CmdCatDel !String !String
|
||||||
| CmdPkgCatAdd !String !PkgId !String
|
| CmdPkgCatAdd !String !PkgId !String
|
||||||
| CmdPkgCatDel !String !PkgId !String
|
| CmdPkgCatDel !String !PkgId !String
|
||||||
|
| CmdEosUpload !EosUpload
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
|
|
||||||
@@ -373,6 +382,7 @@ parseCommand =
|
|||||||
<|> (CmdListUnindexed <$> parseListUnindexed)
|
<|> (CmdListUnindexed <$> parseListUnindexed)
|
||||||
<|> parseCat
|
<|> parseCat
|
||||||
<|> parsePkgCat
|
<|> parsePkgCat
|
||||||
|
<|> (CmdEosUpload <$> parseEosPublish)
|
||||||
where
|
where
|
||||||
reg = parseRepoAdd <|> (CmdRegDel <$> parseRepoDel) <|> (parseRepoList $> CmdRegList)
|
reg = parseRepoAdd <|> (CmdRegDel <$> parseRepoDel) <|> (parseRepoList $> CmdRegList)
|
||||||
|
|
||||||
@@ -419,6 +429,20 @@ 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 =
|
||||||
|
subparser $
|
||||||
|
command "eos-upload" (info go $ progDesc "Publishes a .img to a remote registry")
|
||||||
|
<> metavar
|
||||||
|
"eos-upload"
|
||||||
|
where
|
||||||
|
go =
|
||||||
|
liftA3
|
||||||
|
EosUpload
|
||||||
|
(strOption (short 't' <> long "target" <> metavar "NAME" <> help "Name of registry in publish.dhall"))
|
||||||
|
(strOption (short 'i' <> long "image" <> metavar "EOS_IMG" <> help "File path of the image to publish"))
|
||||||
|
(strOption (short 'v' <> long "version" <> help "Version of the image"))
|
||||||
|
|
||||||
|
|
||||||
opts :: ParserInfo Command
|
opts :: ParserInfo Command
|
||||||
opts = info (parseCommand <**> helper) (fullDesc <> progDesc "Publish tool for Embassy Packages")
|
opts = info (parseCommand <**> helper) (fullDesc <> progDesc "Publish tool for Embassy Packages")
|
||||||
@@ -438,6 +462,7 @@ cliMain =
|
|||||||
CmdCatDel target cat -> catDel target cat
|
CmdCatDel target cat -> catDel target cat
|
||||||
CmdPkgCatAdd target pkg cat -> pkgCatAdd target pkg cat
|
CmdPkgCatAdd target pkg cat -> pkgCatAdd target pkg cat
|
||||||
CmdPkgCatDel target pkg cat -> pkgCatDel target pkg cat
|
CmdPkgCatDel target pkg cat -> pkgCatDel target pkg cat
|
||||||
|
CmdEosUpload up -> eosUpload up
|
||||||
|
|
||||||
|
|
||||||
init :: Maybe Shell -> IO ()
|
init :: Maybe Shell -> IO ()
|
||||||
@@ -547,6 +572,31 @@ 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 name img version) = do
|
||||||
|
PublishCfgRepo{..} <- findNameInCfg name
|
||||||
|
noBody <-
|
||||||
|
parseRequest ("POST " <> show publishCfgRepoLocation <> "/admin/v0/eos-upload")
|
||||||
|
<&> setRequestHeaders [("accept", "text/plain")]
|
||||||
|
<&> applyBasicAuth (B8.pack publishCfgRepoUser) (B8.pack publishCfgRepoPass)
|
||||||
|
size <- getFileSize img
|
||||||
|
hash <- hashFile @_ @SHA256 img
|
||||||
|
bar <- newProgressBar defStyle 30 (Progress 0 (fromIntegral size) ())
|
||||||
|
body <- observedStreamFile (updateProgress bar . const . sfs2prog) img
|
||||||
|
let withBody = setRequestBody body noBody
|
||||||
|
let withQParams = setRequestQueryString [("version", Just $ show version), ("hash", Just $ convertToBase Base16 hash)] withBody
|
||||||
|
manager <- newTlsManager
|
||||||
|
res <- runReaderT (httpLbs withQParams) manager
|
||||||
|
if getResponseStatus res == status200
|
||||||
|
then -- no output is successful
|
||||||
|
pure ()
|
||||||
|
else do
|
||||||
|
$logError (decodeUtf8 . LB.toStrict $ getResponseBody res)
|
||||||
|
exitWith $ ExitFailure 1
|
||||||
|
putChunkLn $ fromString ("Successfully uploaded " <> img) & fore green
|
||||||
|
where
|
||||||
|
sfs2prog :: StreamFileStatus -> Progress ()
|
||||||
|
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)
|
||||||
|
|||||||
@@ -162,7 +162,6 @@ data RegistryCtx = RegistryCtx
|
|||||||
, appWebServerThreadId :: MVar (ThreadId, ThreadId)
|
, appWebServerThreadId :: MVar (ThreadId, ThreadId)
|
||||||
, appShouldRestartWeb :: MVar Bool
|
, appShouldRestartWeb :: MVar Bool
|
||||||
, appConnPool :: ConnectionPool
|
, appConnPool :: ConnectionPool
|
||||||
, appStopFsNotifyEos :: IO Bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -288,6 +287,7 @@ instance Yesod RegistryCtx where
|
|||||||
|
|
||||||
maximumContentLengthIO :: RegistryCtx -> Maybe (Route RegistryCtx) -> IO (Maybe Word64)
|
maximumContentLengthIO :: RegistryCtx -> Maybe (Route RegistryCtx) -> IO (Maybe Word64)
|
||||||
maximumContentLengthIO _ (Just PkgUploadR) = pure Nothing
|
maximumContentLengthIO _ (Just PkgUploadR) = pure Nothing
|
||||||
|
maximumContentLengthIO _ (Just EosUploadR) = pure Nothing
|
||||||
maximumContentLengthIO _ _ = pure $ Just 2097152 -- the original default
|
maximumContentLengthIO _ _ = pure $ Just 2097152 -- the original default
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import Conduit (
|
|||||||
sinkFile,
|
sinkFile,
|
||||||
(.|),
|
(.|),
|
||||||
)
|
)
|
||||||
import Control.Exception (ErrorCall (ErrorCall))
|
|
||||||
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 (
|
||||||
@@ -44,6 +43,7 @@ import Database.Persist (
|
|||||||
entityVal,
|
entityVal,
|
||||||
insert_,
|
insert_,
|
||||||
selectList,
|
selectList,
|
||||||
|
(=.),
|
||||||
)
|
)
|
||||||
import Database.Persist.Postgresql (runSqlPoolNoTransaction)
|
import Database.Persist.Postgresql (runSqlPoolNoTransaction)
|
||||||
import Database.Queries (upsertPackageVersion)
|
import Database.Queries (upsertPackageVersion)
|
||||||
@@ -52,6 +52,8 @@ import Foundation (
|
|||||||
RegistryCtx (..),
|
RegistryCtx (..),
|
||||||
)
|
)
|
||||||
import Handler.Util (
|
import Handler.Util (
|
||||||
|
getHashFromQuery,
|
||||||
|
getVersionFromQuery,
|
||||||
orThrow,
|
orThrow,
|
||||||
sendResponseText,
|
sendResponseText,
|
||||||
)
|
)
|
||||||
@@ -69,6 +71,8 @@ import Lib.Types.Emver (Version (..))
|
|||||||
import Lib.Types.Manifest (PackageManifest (..))
|
import Lib.Types.Manifest (PackageManifest (..))
|
||||||
import Model (
|
import Model (
|
||||||
Category (..),
|
Category (..),
|
||||||
|
EntityField (EosHashHash),
|
||||||
|
EosHash (EosHash),
|
||||||
Key (AdminKey, PkgRecordKey, VersionRecordKey),
|
Key (AdminKey, PkgRecordKey, VersionRecordKey),
|
||||||
PkgCategory (PkgCategory),
|
PkgCategory (PkgCategory),
|
||||||
Unique (UniqueName, UniquePkgCategory),
|
Unique (UniqueName, UniquePkgCategory),
|
||||||
@@ -77,6 +81,7 @@ import Model (
|
|||||||
unPkgRecordKey,
|
unPkgRecordKey,
|
||||||
)
|
)
|
||||||
import Network.HTTP.Types (
|
import Network.HTTP.Types (
|
||||||
|
status400,
|
||||||
status403,
|
status403,
|
||||||
status404,
|
status404,
|
||||||
status500,
|
status500,
|
||||||
@@ -103,7 +108,6 @@ import Startlude (
|
|||||||
not,
|
not,
|
||||||
replicate,
|
replicate,
|
||||||
show,
|
show,
|
||||||
throwIO,
|
|
||||||
toS,
|
toS,
|
||||||
traverse,
|
traverse,
|
||||||
void,
|
void,
|
||||||
@@ -139,6 +143,7 @@ import Yesod (
|
|||||||
rawRequestBody,
|
rawRequestBody,
|
||||||
requireCheckJsonBody,
|
requireCheckJsonBody,
|
||||||
runDB,
|
runDB,
|
||||||
|
sendResponseStatus,
|
||||||
)
|
)
|
||||||
import Yesod.Auth (YesodAuth (maybeAuthId))
|
import Yesod.Auth (YesodAuth (maybeAuthId))
|
||||||
import Yesod.Core.Types (JSONResponse (JSONResponse))
|
import Yesod.Core.Types (JSONResponse (JSONResponse))
|
||||||
@@ -167,10 +172,9 @@ postPkgUploadR = do
|
|||||||
renameDirectory dir targetPath
|
renameDirectory dir targetPath
|
||||||
maybeAuthId >>= \case
|
maybeAuthId >>= \case
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
-- TODO: Send this to Matrix
|
|
||||||
$logError
|
$logError
|
||||||
"The Impossible has happened, an unauthenticated user has managed to upload a pacakge to this registry"
|
"The Impossible has happened, an unauthenticated user has managed to upload a pacakge to this registry"
|
||||||
throwIO $ ErrorCall "Unauthenticated user has uploaded package to registry!!!"
|
pure ()
|
||||||
Just name -> do
|
Just name -> do
|
||||||
now <- liftIO getCurrentTime
|
now <- liftIO getCurrentTime
|
||||||
runDB $ insert_ (Upload (AdminKey name) (PkgRecordKey packageManifestId) packageManifestVersion now)
|
runDB $ insert_ (Upload (AdminKey name) (PkgRecordKey packageManifestId) packageManifestVersion now)
|
||||||
@@ -178,6 +182,29 @@ postPkgUploadR = do
|
|||||||
retry m = runMaybeT . asum $ replicate 3 (MaybeT $ hush <$> try @_ @SomeException m)
|
retry m = runMaybeT . asum $ replicate 3 (MaybeT $ hush <$> try @_ @SomeException m)
|
||||||
|
|
||||||
|
|
||||||
|
postEosUploadR :: Handler ()
|
||||||
|
postEosUploadR = do
|
||||||
|
root <- getsYesod $ (</> "eos") . resourcesDir . appSettings
|
||||||
|
maybeVersion <- getVersionFromQuery
|
||||||
|
version <- case maybeVersion of
|
||||||
|
Nothing -> sendResponseStatus status400 ("Missing Version" :: Text)
|
||||||
|
Just v -> pure v
|
||||||
|
maybeHash <- getHashFromQuery
|
||||||
|
hash <- case maybeHash of
|
||||||
|
Nothing -> sendResponseStatus status400 ("Missing Hash" :: Text)
|
||||||
|
Just h -> pure h
|
||||||
|
resourcesTemp <- getsYesod $ (</> "temp") . resourcesDir . appSettings
|
||||||
|
createDirectoryIfMissing True resourcesTemp
|
||||||
|
withTempDirectory resourcesTemp "neweos" $ \dir -> do
|
||||||
|
let path = dir </> "eos" <.> "img"
|
||||||
|
runConduit $ rawRequestBody .| sinkFile path
|
||||||
|
_ <- runDB $ upsert (EosHash version hash) [EosHashHash =. hash]
|
||||||
|
let targetPath = root </> show version
|
||||||
|
removePathForcibly targetPath
|
||||||
|
createDirectoryIfMissing True targetPath
|
||||||
|
renameDirectory dir targetPath
|
||||||
|
|
||||||
|
|
||||||
data IndexPkgReq = IndexPkgReq
|
data IndexPkgReq = IndexPkgReq
|
||||||
{ indexPkgReqId :: !PkgId
|
{ indexPkgReqId :: !PkgId
|
||||||
, indexPkgReqVersion :: !Version
|
, indexPkgReqVersion :: !Version
|
||||||
|
|||||||
@@ -3,13 +3,10 @@
|
|||||||
|
|
||||||
module Handler.Eos.V0.EosImg where
|
module Handler.Eos.V0.EosImg where
|
||||||
|
|
||||||
import Crypto.Hash (SHA256)
|
|
||||||
import Crypto.Hash.Conduit (hashFile)
|
|
||||||
import Data.Attoparsec.Text qualified as Atto
|
import Data.Attoparsec.Text qualified as Atto
|
||||||
import Data.ByteArray.Encoding (Base (..), convertToBase)
|
|
||||||
import Data.String.Interpolate.IsString (i)
|
import Data.String.Interpolate.IsString (i)
|
||||||
import Data.Text qualified as T
|
import Data.Text qualified as T
|
||||||
import Database.Persist (Entity (..), insertUnique)
|
import Database.Persist (Entity (..))
|
||||||
import Database.Persist.Class (getBy)
|
import Database.Persist.Class (getBy)
|
||||||
import Foundation (Handler, RegistryCtx (..))
|
import Foundation (Handler, RegistryCtx (..))
|
||||||
import Handler.Util (getVersionSpecFromQuery)
|
import Handler.Util (getVersionSpecFromQuery)
|
||||||
@@ -18,11 +15,12 @@ import Lib.Types.Emver (Version (..), parseVersion, satisfies)
|
|||||||
import Model (EosHash (..), Unique (..))
|
import Model (EosHash (..), Unique (..))
|
||||||
import Network.HTTP.Types (status404)
|
import Network.HTTP.Types (status404)
|
||||||
import Settings (AppSettings (..))
|
import Settings (AppSettings (..))
|
||||||
import Startlude (Down (..), FilePath, Maybe (..), Text, decodeUtf8, filter, for_, headMay, partitionEithers, pure, show, sortOn, void, ($), (.), (<$>))
|
import Startlude (Down (..), Maybe (..), Text, filter, for_, headMay, partitionEithers, pure, show, sortOn, ($), (.), (<$>))
|
||||||
import System.FilePath ((</>))
|
import System.FilePath ((</>))
|
||||||
import UnliftIO.Directory (listDirectory)
|
import UnliftIO.Directory (listDirectory)
|
||||||
import Yesod (Content (..), TypedContent, YesodDB, YesodPersist (runDB), addHeader, getsYesod, respond, sendResponseStatus, typeOctet)
|
import Yesod (Content (..), TypedContent, YesodDB, YesodPersist (runDB), addHeader, getsYesod, respond, sendResponseStatus, typeOctet)
|
||||||
import Yesod.Core (logWarn)
|
import Yesod.Core (logWarn)
|
||||||
|
import Data.Maybe (maybe)
|
||||||
|
|
||||||
|
|
||||||
getEosR :: Handler TypedContent
|
getEosR :: Handler TypedContent
|
||||||
@@ -37,17 +35,13 @@ getEosR = do
|
|||||||
Nothing -> sendResponseStatus status404 (NotFoundE [i|EOS version satisfying #{spec}|])
|
Nothing -> sendResponseStatus status404 (NotFoundE [i|EOS version satisfying #{spec}|])
|
||||||
Just version -> do
|
Just version -> do
|
||||||
let imgPath = root </> show version </> "eos.img"
|
let imgPath = root </> show version </> "eos.img"
|
||||||
h <- runDB $ retrieveHash version imgPath
|
h <- runDB $ retrieveHash version
|
||||||
addHeader "x-eos-hash" h
|
maybe (pure ()) (addHeader "x-eos-hash") h
|
||||||
respond typeOctet $ ContentFile imgPath Nothing
|
respond typeOctet $ ContentFile imgPath Nothing
|
||||||
where
|
where
|
||||||
retrieveHash :: Version -> FilePath -> YesodDB RegistryCtx Text
|
retrieveHash :: Version -> YesodDB RegistryCtx (Maybe Text)
|
||||||
retrieveHash v fp = do
|
retrieveHash v = do
|
||||||
mHash <- getBy (UniqueVersion v)
|
mHash <- getBy (UniqueVersion v)
|
||||||
case mHash of
|
case mHash of
|
||||||
Just h -> pure . eosHashHash . entityVal $ h
|
Just h -> pure . Just . eosHashHash . entityVal $ h
|
||||||
Nothing -> do
|
Nothing -> pure Nothing
|
||||||
h <- hashFile @_ @SHA256 fp
|
|
||||||
let t = decodeUtf8 $ convertToBase Base16 h
|
|
||||||
void $ insertUnique (EosHash v t) -- lazily populate
|
|
||||||
pure t
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Data.Aeson (ToJSON (..), eitherDecode)
|
|||||||
import Data.ByteString.Lazy qualified as LBS
|
import Data.ByteString.Lazy qualified as LBS
|
||||||
import Data.HashMap.Strict (HashMap)
|
import Data.HashMap.Strict (HashMap)
|
||||||
import Data.HashMap.Strict qualified as HM
|
import Data.HashMap.Strict qualified as HM
|
||||||
import Data.List (lookup, sortOn)
|
import Data.List (lookup)
|
||||||
import Data.List.NonEmpty.Extra qualified as NE
|
import Data.List.NonEmpty.Extra qualified as NE
|
||||||
import Data.Tuple.Extra (second)
|
import Data.Tuple.Extra (second)
|
||||||
import Database.Queries (collateVersions, getPkgDataSource)
|
import Database.Queries (collateVersions, getPkgDataSource)
|
||||||
@@ -16,7 +16,7 @@ import Lib.Types.Core (PkgId)
|
|||||||
import Lib.Types.Emver (Version, satisfies)
|
import Lib.Types.Emver (Version, satisfies)
|
||||||
import Model (VersionRecord (..))
|
import Model (VersionRecord (..))
|
||||||
import Network.HTTP.Types (status400)
|
import Network.HTTP.Types (status400)
|
||||||
import Startlude (Bool (True), Down (Down), Either (..), Generic, Maybe (..), NonEmpty, Show, const, encodeUtf8, filter, flip, headMay, nonEmpty, pure, ($), (.), (<$>), (<&>))
|
import Startlude (Bool (True), Down (Down), Either (..), Generic, Maybe (..), NonEmpty, Show, const, encodeUtf8, filter, flip, nonEmpty, pure, ($), (.), (<$>), (<&>))
|
||||||
import Yesod (ToContent (..), ToTypedContent (..), YesodPersist (runDB), YesodRequest (reqGetParams), getRequest, sendResponseStatus)
|
import Yesod (ToContent (..), ToTypedContent (..), YesodPersist (runDB), YesodRequest (reqGetParams), getRequest, sendResponseStatus)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -97,6 +97,17 @@ 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 = do
|
||||||
|
versionString <- lookupGetParam "version"
|
||||||
|
case versionString of
|
||||||
|
Nothing -> pure Nothing
|
||||||
|
Just v -> case readMaybe v of
|
||||||
|
Nothing -> sendResponseStatus status400 ("Invalid Version" :: Text)
|
||||||
|
Just t -> pure (Just t)
|
||||||
|
|
||||||
|
getHashFromQuery :: MonadHandler m => m (Maybe Text)
|
||||||
|
getHashFromQuery = lookupGetParam "hash"
|
||||||
|
|
||||||
versionPriorityFromQueryIsMin :: MonadHandler m => m Bool
|
versionPriorityFromQueryIsMin :: MonadHandler m => m Bool
|
||||||
versionPriorityFromQueryIsMin = do
|
versionPriorityFromQueryIsMin = do
|
||||||
|
|||||||
@@ -31,15 +31,8 @@ import Control.Monad.Reader.Has (
|
|||||||
ask,
|
ask,
|
||||||
asks,
|
asks,
|
||||||
)
|
)
|
||||||
import Crypto.Hash (SHA256)
|
|
||||||
import Crypto.Hash.Conduit (hashFile)
|
|
||||||
import Data.Aeson (eitherDecodeFileStrict')
|
import Data.Aeson (eitherDecodeFileStrict')
|
||||||
import Data.Attoparsec.Text (parseOnly)
|
|
||||||
import Data.Attoparsec.Text qualified as Atto
|
import Data.Attoparsec.Text qualified as Atto
|
||||||
import Data.ByteArray.Encoding (
|
|
||||||
Base (Base16),
|
|
||||||
convertToBase,
|
|
||||||
)
|
|
||||||
import Data.ByteString (
|
import Data.ByteString (
|
||||||
readFile,
|
readFile,
|
||||||
writeFile,
|
writeFile,
|
||||||
@@ -58,7 +51,6 @@ import Database.Esqueleto.Experimental (
|
|||||||
import Database.Persist (
|
import Database.Persist (
|
||||||
insertKey,
|
insertKey,
|
||||||
update,
|
update,
|
||||||
upsert,
|
|
||||||
(=.),
|
(=.),
|
||||||
)
|
)
|
||||||
import Database.Persist.Sql (
|
import Database.Persist.Sql (
|
||||||
@@ -80,8 +72,7 @@ import Lib.Types.Manifest (
|
|||||||
PackageManifest (..),
|
PackageManifest (..),
|
||||||
)
|
)
|
||||||
import Model (
|
import Model (
|
||||||
EntityField (EosHashHash, PkgRecordUpdatedAt),
|
EntityField (PkgRecordUpdatedAt),
|
||||||
EosHash (EosHash),
|
|
||||||
Key (PkgRecordKey),
|
Key (PkgRecordKey),
|
||||||
PkgDependency (PkgDependency),
|
PkgDependency (PkgDependency),
|
||||||
PkgRecord (PkgRecord),
|
PkgRecord (PkgRecord),
|
||||||
@@ -95,7 +86,6 @@ import Startlude (
|
|||||||
Eq ((==)),
|
Eq ((==)),
|
||||||
Exception,
|
Exception,
|
||||||
FilePath,
|
FilePath,
|
||||||
IO,
|
|
||||||
Integer,
|
Integer,
|
||||||
Maybe (..),
|
Maybe (..),
|
||||||
MonadIO (liftIO),
|
MonadIO (liftIO),
|
||||||
@@ -103,7 +93,6 @@ import Startlude (
|
|||||||
Ord (compare),
|
Ord (compare),
|
||||||
Show,
|
Show,
|
||||||
SomeException (..),
|
SomeException (..),
|
||||||
decodeUtf8,
|
|
||||||
filter,
|
filter,
|
||||||
find,
|
find,
|
||||||
first,
|
first,
|
||||||
@@ -111,7 +100,6 @@ import Startlude (
|
|||||||
for_,
|
for_,
|
||||||
fst,
|
fst,
|
||||||
headMay,
|
headMay,
|
||||||
not,
|
|
||||||
on,
|
on,
|
||||||
partitionEithers,
|
partitionEithers,
|
||||||
pure,
|
pure,
|
||||||
@@ -120,40 +108,26 @@ import Startlude (
|
|||||||
sortBy,
|
sortBy,
|
||||||
throwIO,
|
throwIO,
|
||||||
toS,
|
toS,
|
||||||
void,
|
|
||||||
($),
|
($),
|
||||||
(&&),
|
|
||||||
(.),
|
(.),
|
||||||
(/=),
|
(/=),
|
||||||
(<$>),
|
(<$>),
|
||||||
)
|
)
|
||||||
import System.FSNotify (
|
|
||||||
ActionPredicate,
|
|
||||||
Event (..),
|
|
||||||
eventPath,
|
|
||||||
watchTree,
|
|
||||||
withManager,
|
|
||||||
)
|
|
||||||
import System.FilePath (
|
import System.FilePath (
|
||||||
takeBaseName,
|
takeBaseName,
|
||||||
takeDirectory,
|
takeDirectory,
|
||||||
takeExtension,
|
takeExtension,
|
||||||
takeFileName,
|
|
||||||
(<.>),
|
(<.>),
|
||||||
(</>),
|
(</>),
|
||||||
)
|
)
|
||||||
import UnliftIO (
|
import UnliftIO (
|
||||||
MonadUnliftIO,
|
MonadUnliftIO,
|
||||||
askRunInIO,
|
|
||||||
async,
|
async,
|
||||||
catch,
|
catch,
|
||||||
mapConcurrently_,
|
mapConcurrently_,
|
||||||
newEmptyMVar,
|
|
||||||
takeMVar,
|
|
||||||
tryPutMVar,
|
|
||||||
wait,
|
wait,
|
||||||
)
|
)
|
||||||
import UnliftIO.Concurrent (forkIO)
|
|
||||||
import UnliftIO.Directory (
|
import UnliftIO.Directory (
|
||||||
doesDirectoryExist,
|
doesDirectoryExist,
|
||||||
doesPathExist,
|
doesPathExist,
|
||||||
@@ -299,40 +273,6 @@ extractPkg pool fp = handle @_ @SomeException cleanup $ do
|
|||||||
throwIO e
|
throwIO e
|
||||||
|
|
||||||
|
|
||||||
watchEosRepoRoot :: (MonadUnliftIO m, MonadReader r m, Has EosRepo r, MonadLoggerIO m) => ConnectionPool -> m (IO Bool)
|
|
||||||
watchEosRepoRoot pool = do
|
|
||||||
$logInfo "Starting FSNotify Watch Manager: EOS"
|
|
||||||
root <- asks eosRepoFileRoot
|
|
||||||
runInIO <- askRunInIO
|
|
||||||
box <- newEmptyMVar @_ @()
|
|
||||||
_ <- forkIO $
|
|
||||||
liftIO $
|
|
||||||
withManager $ \watchManager -> do
|
|
||||||
stop <- watchTree watchManager root shouldIndex $ \evt -> do
|
|
||||||
let os = eventPath evt
|
|
||||||
void . forkIO $
|
|
||||||
runInIO $ do
|
|
||||||
indexOs pool os
|
|
||||||
takeMVar box
|
|
||||||
stop
|
|
||||||
pure $ tryPutMVar box ()
|
|
||||||
where
|
|
||||||
shouldIndex :: ActionPredicate
|
|
||||||
shouldIndex (Added path _ isDir) = not isDir && takeExtension path == ".img"
|
|
||||||
shouldIndex (Modified path _ isDir) = not isDir && takeExtension path == ".img"
|
|
||||||
shouldIndex _ = False
|
|
||||||
indexOs :: (MonadUnliftIO m, MonadLoggerIO m) => ConnectionPool -> FilePath -> m ()
|
|
||||||
indexOs pool path = do
|
|
||||||
hash <- hashFile @_ @SHA256 path
|
|
||||||
let hashText = decodeUtf8 $ convertToBase Base16 hash
|
|
||||||
let vText = takeFileName (takeDirectory path)
|
|
||||||
let eVersion = parseOnly parseVersion . T.pack $ vText
|
|
||||||
case eVersion of
|
|
||||||
Left e -> $logError [i|Invalid Version Number (#{vText}): #{e}|]
|
|
||||||
Right version ->
|
|
||||||
void $ flip runSqlPool pool $ upsert (EosHash version hashText) [EosHashHash =. hashText]
|
|
||||||
|
|
||||||
|
|
||||||
getManifestLocation :: (MonadReader r m, Has PkgRepo r) => PkgId -> Version -> m FilePath
|
getManifestLocation :: (MonadReader r m, Has PkgRepo r) => PkgId -> Version -> m FilePath
|
||||||
getManifestLocation pkg version = do
|
getManifestLocation pkg version = do
|
||||||
root <- asks pkgRepoFileRoot
|
root <- asks pkgRepoFileRoot
|
||||||
|
|||||||
Reference in New Issue
Block a user