module Ecluse.Core.Server.Pipeline.Packument (
PackumentReplies (..),
servePackument,
headPackument,
packumentETag,
) where
import Crypto.Hash (Context, SHA256, hashFinalize, hashInit, hashUpdates)
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Data.Text qualified as T
import Katip (Severity (DebugS, InfoS), logFM, ls)
import Network.HTTP.Types (ResponseHeaders, hContentLength)
import Network.Wai (Request, ResponseReceived, requestHeaders)
import UnliftIO (concurrently)
import UnliftIO.Exception (catchAny, throwIO)
import Ecluse.Core.Package (
PackageInfo (infoVersions),
PackageName,
renderPackageName,
)
import Ecluse.Core.Package.Filter (filterPlanFromDecisions, fpDecisions, fpSurvivors, restrictToSurvivors)
import Ecluse.Core.Package.Integrity (
MinTrustedIntegrity,
)
import Ecluse.Core.Package.Merge (
DivergencePolicy,
MergePlan (mpSurvivors),
Provenance (GatedSource, TrustedSource),
SourceId,
applyDivergencePolicy,
mergePackuments,
)
import Ecluse.Core.Registry.CachedDocument (CachedDoc)
import Ecluse.Core.Registry.Metadata (
ContentDigest,
Manifest (manifestDigest, manifestInfo, manifestRaw),
digestBytes,
)
import Ecluse.Core.Rules (evalRules)
import Ecluse.Core.Rules.Types (Decision, EvalContext (ctxAdvisoryEtag), mkEvalContext)
import Ecluse.Core.Server.Admission (withServeAdmission)
import Ecluse.Core.Server.Cache (resolveAssembled)
import Ecluse.Core.Server.Conditional (Conditional (Modified, NotModified), ETag, etagHeader, evaluateETag, mkStrongETag, renderETag)
import Ecluse.Core.Server.Context (
Handler,
MountBinding (bindingPackumentDeps),
PackumentDeps (..),
ServeRuntime (..),
ctxMount,
ctxRuntime,
)
import Ecluse.Core.Server.Fault (RenderEscape (RenderEscape))
import Ecluse.Core.Server.Pipeline.Diagnostics (warnDivergences)
import Ecluse.Core.Server.Pipeline.Internal (
VersionVerdict (..),
admitByIntegrity,
evalTier,
logDenials,
packumentServeDecision,
recordDenials,
recordEffectfulFailures,
)
import Ecluse.Core.Server.Pipeline.Origin (
Contribution (..),
OriginResult (..),
fetchPrivateOrigin,
fetchPublicOrigin,
fingerprintPiece,
originManifest,
)
import Ecluse.Core.Server.Pipeline.Shared
import Ecluse.Core.Server.Response (
PackumentStatus (PackumentBadGateway, PackumentForbidden, PackumentOk, PackumentServerError, PackumentUnavailable),
RejectReason (Unavailable, UpstreamInvalid),
Rejection (Rejection, rejectionMessage),
RetryAfter (RetryAfter),
ServeDecision (Admit, Reject),
Transience (WillResolve),
appendHelp,
packumentStatus,
serveDecisionOf,
)
import Ecluse.Core.Telemetry.Metrics qualified as Metric
import Ecluse.Core.Telemetry.Record (MetricsPort (..), timedSeconds)
import Ecluse.Core.Telemetry.Span (TracingPort, spanPackumentGate)
data PackumentReplies response = PackumentReplies
{ forall response.
PackumentReplies response
-> ResponseHeaders -> LByteString -> response
packumentOk :: ResponseHeaders -> LByteString -> response
, forall response.
PackumentReplies response -> ResponseHeaders -> response
packumentNotModified :: ResponseHeaders -> response
, forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentUnauthorised :: ResponseHeaders -> Text -> response
, forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentForbidden :: ResponseHeaders -> Text -> response
, forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentInternal :: ResponseHeaders -> Text -> response
, forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentBadGateway :: ResponseHeaders -> Text -> response
, forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentUnavailable :: ResponseHeaders -> Text -> response
}
servePackument ::
PackumentReplies response ->
PackageName ->
Request ->
(response -> IO ResponseReceived) ->
Handler ResponseReceived
servePackument :: forall response.
PackumentReplies response
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
servePackument = PackumentServe
-> PackumentReplies response
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
forall response.
PackumentServe
-> PackumentReplies response
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
packumentWith PackumentServe
PackumentFull
headPackument ::
PackumentReplies response ->
PackageName ->
Request ->
(response -> IO ResponseReceived) ->
Handler ResponseReceived
headPackument :: forall response.
PackumentReplies response
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
headPackument = PackumentServe
-> PackumentReplies response
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
forall response.
PackumentServe
-> PackumentReplies response
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
packumentWith PackumentServe
PackumentHead
data PackumentServe
=
PackumentFull
|
PackumentHead
packumentWith ::
PackumentServe ->
PackumentReplies response ->
PackageName ->
Request ->
(response -> IO ResponseReceived) ->
Handler ResponseReceived
packumentWith :: forall response.
PackumentServe
-> PackumentReplies response
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
packumentWith PackumentServe
mode PackumentReplies response
replies PackageName
name Request
request response -> IO ResponseReceived
respond = do
deps <- (RequestCtx -> PackumentDeps) -> Handler PackumentDeps
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks (MountBinding -> PackumentDeps
bindingPackumentDeps (MountBinding -> PackumentDeps)
-> (RequestCtx -> MountBinding) -> RequestCtx -> PackumentDeps
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RequestCtx -> MountBinding
ctxMount)
serveWithDeps mode replies deps name request respond
serveWithDeps ::
PackumentServe ->
PackumentReplies response ->
PackumentDeps ->
PackageName ->
Request ->
(response -> IO ResponseReceived) ->
Handler ResponseReceived
serveWithDeps :: forall response.
PackumentServe
-> PackumentReplies response
-> PackumentDeps
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
serveWithDeps PackumentServe
mode PackumentReplies response
replies PackumentDeps
deps PackageName
name Request
request response -> IO ResponseReceived
respond
| Bool -> Bool
not (Maybe Secret -> Maybe Secret -> Bool
edgeTokenMatches (PackumentDeps -> Maybe Secret
pdInboundToken PackumentDeps
deps) (Request -> Maybe Secret
forwardedToken Request
request)) =
IO ResponseReceived -> Handler ResponseReceived
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (response -> IO ResponseReceived
respond (PackumentReplies response -> ResponseHeaders -> Text -> response
forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentUnauthorised PackumentReplies response
replies [] Text
"authentication required"))
| Bool
otherwise = do
rt <- (RequestCtx -> ServeRuntime) -> Handler ServeRuntime
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks RequestCtx -> ServeRuntime
ctxRuntime
withServeAdmission (srMetrics rt) (srAdmission rt) (serveAdmittedPackument mode replies deps name request respond rt) >>= \case
Just ResponseReceived
received -> ResponseReceived -> Handler ResponseReceived
forall a. a -> Handler a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ResponseReceived
received
Maybe ResponseReceived
Nothing -> IO ResponseReceived -> Handler ResponseReceived
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO ResponseReceived -> Handler ResponseReceived)
-> IO ResponseReceived -> Handler ResponseReceived
forall a b. (a -> b) -> a -> b
$ do
MetricsPort -> Decision -> IO ()
mpServeDecision (ServeRuntime -> MetricsPort
srMetrics ServeRuntime
rt) Decision
Metric.Unavailable
response -> IO ResponseReceived
respond (PackumentReplies response -> ResponseHeaders -> Text -> response
forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentUnavailable PackumentReplies response
replies [Header
shedRetryAfter] Text
"server is busy; retry later")
serveAdmittedPackument ::
PackumentServe ->
PackumentReplies response ->
PackumentDeps ->
PackageName ->
Request ->
(response -> IO ResponseReceived) ->
ServeRuntime ->
Handler ResponseReceived
serveAdmittedPackument :: forall response.
PackumentServe
-> PackumentReplies response
-> PackumentDeps
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> ServeRuntime
-> Handler ResponseReceived
serveAdmittedPackument PackumentServe
mode PackumentReplies response
replies PackumentDeps
deps PackageName
name Request
request response -> IO ResponseReceived
respond ServeRuntime
rt = do
Severity -> LogStr -> Handler ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
InfoS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (Text
"serving packument request for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PackageName -> Text
renderPackageName PackageName
name))
let metrics :: MetricsPort
metrics = ServeRuntime -> MetricsPort
srMetrics ServeRuntime
rt
clientToken :: Maybe Secret
clientToken = Request -> Maybe Secret
forwardedToken Request
request
evalCtx <- IO EvalContext -> Handler EvalContext
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO UTCTime -> IO (Maybe DbEtag) -> IO EvalContext
mkEvalContext (PackumentDeps -> IO UTCTime
pdNow PackumentDeps
deps) (PackumentDeps -> IO (Maybe DbEtag)
pdAdvisoryEtag PackumentDeps
deps))
(privResult, pubResult) <-
concurrently
(fetchPrivateOrigin deps rt clientToken name)
(fetchPublicOrigin deps rt name)
(public, publicExclusions, publicVerdicts) <- liftIO (gatePublic (srTracing rt) metrics deps name evalCtx (originManifest pubResult))
let (private, privateExclusions) = admitTrusted (pdMinTrustedIntegrity deps) (originManifest privResult)
sources = [Maybe Contribution] -> [Contribution]
forall a. [Maybe a] -> [a]
catMaybes [Maybe Contribution
private, Maybe Contribution
public]
noServeableVersions = do
let decisions :: [ServeDecision]
decisions = OriginResult -> OriginResult -> [ServeDecision] -> [ServeDecision]
collectDecisions OriginResult
privResult OriginResult
pubResult ([ServeDecision]
privateExclusions [ServeDecision] -> [ServeDecision] -> [ServeDecision]
forall a. Semigroup a => a -> a -> a
<> [ServeDecision]
publicExclusions)
IO () -> Handler ()
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (MetricsPort -> Decision -> IO ()
mpServeDecision MetricsPort
metrics ([ServeDecision] -> Decision
packumentServeDecision [ServeDecision]
decisions))
IO () -> Handler ()
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (MetricsPort -> [ServeDecision] -> IO ()
recordDenials MetricsPort
metrics [ServeDecision]
decisions)
PackageName -> Maybe DbEtag -> [VersionVerdict] -> Handler ()
forall (m :: * -> *).
KatipContext m =>
PackageName -> Maybe DbEtag -> [VersionVerdict] -> m ()
logDenials PackageName
name (EvalContext -> Maybe DbEtag
ctxAdvisoryEtag EvalContext
evalCtx) [VersionVerdict]
publicVerdicts
IO ResponseReceived -> Handler ResponseReceived
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (response -> IO ResponseReceived
respond (PackumentReplies response
-> PackumentDeps -> [ServeDecision] -> response
forall response.
PackumentReplies response
-> PackumentDeps -> [ServeDecision] -> response
noSurvivors PackumentReplies response
replies PackumentDeps
deps [ServeDecision]
decisions))
serveResolved MergePlan
served = do
IO () -> Handler ()
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (MetricsPort -> Decision -> IO ()
mpServeDecision MetricsPort
metrics Decision
Metric.Admit)
PackumentServe
-> PackumentReplies response
-> PackumentDeps
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> ServeRuntime
-> [Contribution]
-> MergePlan
-> Handler ResponseReceived
forall response.
PackumentServe
-> PackumentReplies response
-> PackumentDeps
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> ServeRuntime
-> [Contribution]
-> MergePlan
-> Handler ResponseReceived
answerPackumentConditional PackumentServe
mode PackumentReplies response
replies PackumentDeps
deps PackageName
name Request
request response -> IO ResponseReceived
respond ServeRuntime
rt [Contribution]
sources MergePlan
served
case packumentPlan sources of
Maybe MergePlan
Nothing -> Handler ResponseReceived
noServeableVersions
Just MergePlan
plan -> do
MetricsPort -> PackageName -> MergePlan -> Handler ()
forall (m :: * -> *).
KatipContext m =>
MetricsPort -> PackageName -> MergePlan -> m ()
warnDivergences MetricsPort
metrics PackageName
name MergePlan
plan
Handler ResponseReceived
-> (MergePlan -> Handler ResponseReceived)
-> Maybe MergePlan
-> Handler ResponseReceived
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Handler ResponseReceived
noServeableVersions MergePlan -> Handler ResponseReceived
serveResolved (DivergencePolicy -> MergePlan -> Maybe MergePlan
survivingPlan (PackumentDeps -> DivergencePolicy
pdDivergencePolicy PackumentDeps
deps) MergePlan
plan)
answerPackumentConditional ::
PackumentServe ->
PackumentReplies response ->
PackumentDeps ->
PackageName ->
Request ->
(response -> IO ResponseReceived) ->
ServeRuntime ->
[Contribution] ->
MergePlan ->
Handler ResponseReceived
answerPackumentConditional :: forall response.
PackumentServe
-> PackumentReplies response
-> PackumentDeps
-> PackageName
-> Request
-> (response -> IO ResponseReceived)
-> ServeRuntime
-> [Contribution]
-> MergePlan
-> Handler ResponseReceived
answerPackumentConditional PackumentServe
mode PackumentReplies response
replies PackumentDeps
deps PackageName
name Request
request response -> IO ResponseReceived
respond ServeRuntime
rt [Contribution]
sources MergePlan
plan = do
let etag :: ETag
etag = Text
-> PackageName -> [(Provenance, ContentDigest, [Text])] -> ETag
packumentETag (PackumentDeps -> Text
pdMountBaseUrl PackumentDeps
deps) PackageName
name ((Contribution -> (Provenance, ContentDigest, [Text]))
-> [Contribution] -> [(Provenance, ContentDigest, [Text])]
forall a b. (a -> b) -> [a] -> [b]
map Contribution -> (Provenance, ContentDigest, [Text])
fingerprintPiece [Contribution]
sources)
case ResponseHeaders -> ETag -> Conditional
evaluateETag (Request -> ResponseHeaders
requestHeaders Request
request) ETag
etag of
NotModified ETag
matched -> do
Severity -> LogStr -> Handler ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
DebugS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (Text
"packument unchanged for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PackageName -> Text
renderPackageName PackageName
name Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (304, unassembled)"))
IO ResponseReceived -> Handler ResponseReceived
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (response -> IO ResponseReceived
respond (PackumentReplies response -> ResponseHeaders -> response
forall response.
PackumentReplies response -> ResponseHeaders -> response
packumentNotModified PackumentReplies response
replies [ETag -> Header
etagHeader ETag
matched]))
Modified ETag
fresh -> do
Severity -> LogStr -> Handler ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
DebugS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (Text
"serving packument for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PackageName -> Text
renderPackageName PackageName
name))
bytes <- IO ByteString -> Handler ByteString
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (ServeRuntime
-> PackumentDeps
-> [Contribution]
-> MergePlan
-> ETag
-> IO ByteString
servedBytes ServeRuntime
rt PackumentDeps
deps [Contribution]
sources MergePlan
plan ETag
fresh)
liftIO (respond (packumentResponse replies mode fresh bytes))
admitTrusted :: MinTrustedIntegrity -> Maybe Manifest -> (Maybe Contribution, [ServeDecision])
admitTrusted :: MinTrustedIntegrity
-> Maybe Manifest -> (Maybe Contribution, [ServeDecision])
admitTrusted MinTrustedIntegrity
minTrusted = \case
Maybe Manifest
Nothing -> (Maybe Contribution
forall a. Maybe a
Nothing, [])
Just Manifest
manifest ->
let (PackageInfo
admissible, [ServeDecision]
integrityRefusals) =
MinTrustedIntegrity
-> ServeDecision
-> ServeDecision
-> PackageInfo
-> (PackageInfo, [ServeDecision])
forall floor.
IntegrityFloor floor =>
floor
-> ServeDecision
-> ServeDecision
-> PackageInfo
-> (PackageInfo, [ServeDecision])
admitByIntegrity MinTrustedIntegrity
minTrusted ServeDecision
trustedIntegrityBelowFloor ServeDecision
trustedIntegrityMissing (Manifest -> PackageInfo
manifestInfo Manifest
manifest)
in if Map Text PackageDetails -> Bool
forall k a. Map k a -> Bool
Map.null (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
admissible)
then (Maybe Contribution
forall a. Maybe a
Nothing, [ServeDecision]
integrityRefusals)
else (Contribution -> Maybe Contribution
forall a. a -> Maybe a
Just (Provenance
-> PackageInfo -> CachedDoc -> ContentDigest -> Contribution
Contribution Provenance
TrustedSource PackageInfo
admissible (Manifest -> CachedDoc
manifestRaw Manifest
manifest) (Manifest -> ContentDigest
manifestDigest Manifest
manifest)), [ServeDecision]
integrityRefusals)
gatePublic :: TracingPort -> MetricsPort -> PackumentDeps -> PackageName -> EvalContext -> Maybe Manifest -> IO (Maybe Contribution, [ServeDecision], [VersionVerdict])
gatePublic :: TracingPort
-> MetricsPort
-> PackumentDeps
-> PackageName
-> EvalContext
-> Maybe Manifest
-> IO (Maybe Contribution, [ServeDecision], [VersionVerdict])
gatePublic TracingPort
tracing MetricsPort
metrics PackumentDeps
deps PackageName
name EvalContext
ctx = \case
Maybe Manifest
Nothing -> (Maybe Contribution, [ServeDecision], [VersionVerdict])
-> IO (Maybe Contribution, [ServeDecision], [VersionVerdict])
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Maybe Contribution
forall a. Maybe a
Nothing, [], [])
Just Manifest
manifest -> TracingPort -> forall a. PackageName -> IO a -> IO a
spanPackumentGate TracingPort
tracing PackageName
name (IO (Maybe Contribution, [ServeDecision], [VersionVerdict])
-> IO (Maybe Contribution, [ServeDecision], [VersionVerdict]))
-> IO (Maybe Contribution, [ServeDecision], [VersionVerdict])
-> IO (Maybe Contribution, [ServeDecision], [VersionVerdict])
forall a b. (a -> b) -> a -> b
$ do
let (PackageInfo
admissible, [ServeDecision]
integrityRefusals) = MinIntegrity
-> ServeDecision
-> ServeDecision
-> PackageInfo
-> (PackageInfo, [ServeDecision])
forall floor.
IntegrityFloor floor =>
floor
-> ServeDecision
-> ServeDecision
-> PackageInfo
-> (PackageInfo, [ServeDecision])
admitByIntegrity (PackumentDeps -> MinIntegrity
pdMinIntegrity PackumentDeps
deps) ServeDecision
integrityBelowFloor ServeDecision
integrityMissing (Manifest -> PackageInfo
manifestInfo Manifest
manifest)
(decisions, seconds) <- IO (Map Text Decision) -> IO (Map Text Decision, Double)
forall (m :: * -> *) a. MonadIO m => m a -> m (a, Double)
timedSeconds (PackumentDeps
-> EvalContext -> PackageInfo -> IO (Map Text Decision)
decideVersions PackumentDeps
deps EvalContext
ctx PackageInfo
admissible)
mpRuleEvalDuration metrics (evalTier (pdRules deps)) seconds
recordEffectfulFailures metrics (Map.elems decisions)
let plan = Map Text Decision -> PackageInfo -> FilterPlan
filterPlanFromDecisions Map Text Decision
decisions PackageInfo
admissible
pure $
if Set.null (fpSurvivors plan)
then
let verdicts = PackageInfo -> [Decision] -> [VersionVerdict]
projectDecisions PackageInfo
admissible (FilterPlan -> [Decision]
fpDecisions FilterPlan
plan)
in (Nothing, map vvDecision verdicts <> integrityRefusals, verdicts)
else
( Just (Contribution GatedSource (restrictToSurvivors (fpSurvivors plan) admissible) (manifestRaw manifest) (manifestDigest manifest))
, integrityRefusals
, []
)
decideVersions :: PackumentDeps -> EvalContext -> PackageInfo -> IO (Map Text Decision)
decideVersions :: PackumentDeps
-> EvalContext -> PackageInfo -> IO (Map Text Decision)
decideVersions PackumentDeps
deps EvalContext
ctx PackageInfo
info =
(PackageDetails -> IO Decision)
-> Map Text PackageDetails -> IO (Map Text Decision)
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
forall (f :: * -> *) a b.
Applicative f =>
(a -> f b) -> Map Text a -> f (Map Text b)
traverse (EvalContext -> [PreparedRule] -> PackageDetails -> IO Decision
evalRules EvalContext
ctx (PackumentDeps -> [PreparedRule]
pdRules PackumentDeps
deps)) (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
info)
projectDecisions :: PackageInfo -> [Decision] -> [VersionVerdict]
projectDecisions :: PackageInfo -> [Decision] -> [VersionVerdict]
projectDecisions PackageInfo
info =
((Text, PackageDetails) -> Decision -> VersionVerdict)
-> [(Text, PackageDetails)] -> [Decision] -> [VersionVerdict]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (Text, PackageDetails) -> Decision -> VersionVerdict
versionVerdict (Map Text PackageDetails -> [(Text, PackageDetails)]
forall k a. Map k a -> [(k, a)]
Map.toList (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
info))
where
versionVerdict :: (Text, PackageDetails) -> Decision -> VersionVerdict
versionVerdict (Text
ver, PackageDetails
details) Decision
d = Text -> ServeDecision -> VersionVerdict
VersionVerdict Text
ver (PackageDetails -> Decision -> ServeDecision
serveDecisionOf PackageDetails
details Decision
d)
newtype ServedBody = ServedBody {ServedBody -> CachedDoc
servedDoc :: CachedDoc}
packumentPlan :: [Contribution] -> Maybe MergePlan
packumentPlan :: [Contribution] -> Maybe MergePlan
packumentPlan [Contribution]
sources = do
plan <- [(Provenance, PackageInfo)] -> Maybe MergePlan
mergePackuments [(Contribution -> Provenance
srcProvenance Contribution
s, Contribution -> PackageInfo
srcInfo Contribution
s) | Contribution
s <- [Contribution]
sources]
guard (not (Map.null (mpSurvivors plan)))
pure plan
survivingPlan :: DivergencePolicy -> MergePlan -> Maybe MergePlan
survivingPlan :: DivergencePolicy -> MergePlan -> Maybe MergePlan
survivingPlan DivergencePolicy
policy MergePlan
plan =
let served :: MergePlan
served = DivergencePolicy -> MergePlan -> MergePlan
applyDivergencePolicy DivergencePolicy
policy MergePlan
plan
in if Map Text Int -> Bool
forall k a. Map k a -> Bool
Map.null (MergePlan -> Map Text Int
mpSurvivors MergePlan
served) then Maybe MergePlan
forall a. Maybe a
Nothing else MergePlan -> Maybe MergePlan
forall a. a -> Maybe a
Just MergePlan
served
packumentETag :: Text -> PackageName -> [(Provenance, ContentDigest, [Text])] -> ETag
packumentETag :: Text
-> PackageName -> [(Provenance, ContentDigest, [Text])] -> ETag
packumentETag Text
mountBaseUrl PackageName
name [(Provenance, ContentDigest, [Text])]
sources =
Digest SHA256 -> ETag
mkStrongETag (Context SHA256 -> Digest SHA256
forall a. HashAlgorithm a => Context a -> Digest a
hashFinalize (Context SHA256 -> [ByteString] -> Context SHA256
forall a ba.
(HashAlgorithm a, ByteArrayAccess ba) =>
Context a -> [ba] -> Context a
hashUpdates (Context SHA256
forall a. HashAlgorithm a => Context a
hashInit :: Context SHA256) [ByteString]
pieces))
where
pieces :: [ByteString]
pieces :: [ByteString]
pieces =
[ ByteString
"ecluse:packument-etag:v1\0"
, Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 Text
mountBaseUrl ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
"\0"
, Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 (PackageName -> Text
renderPackageName PackageName
name) ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
"\0"
]
[ByteString] -> [ByteString] -> [ByteString]
forall a. Semigroup a => a -> a -> a
<> ((Provenance, ContentDigest, [Text]) -> [ByteString])
-> [(Provenance, ContentDigest, [Text])] -> [ByteString]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Provenance, ContentDigest, [Text]) -> [ByteString]
sourcePieces [(Provenance, ContentDigest, [Text])]
sources
sourcePieces :: (Provenance, ContentDigest, [Text]) -> [ByteString]
sourcePieces :: (Provenance, ContentDigest, [Text]) -> [ByteString]
sourcePieces (Provenance
provenance, ContentDigest
digest, [Text]
survivors) =
Provenance -> ByteString
provenanceTag Provenance
provenance
ByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
: ContentDigest -> ByteString
digestBytes ContentDigest
digest
ByteString -> [ByteString] -> [ByteString]
forall a. a -> [a] -> [a]
: (Text -> ByteString) -> [Text] -> [ByteString]
forall a b. (a -> b) -> [a] -> [b]
map (\Text
v -> Text -> ByteString
forall a b. ConvertUtf8 a b => a -> b
encodeUtf8 Text
v ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> ByteString
"\0") [Text]
survivors
[ByteString] -> [ByteString] -> [ByteString]
forall a. Semigroup a => a -> a -> a
<> [ByteString
"\1"]
provenanceTag :: Provenance -> ByteString
provenanceTag :: Provenance -> ByteString
provenanceTag = \case
Provenance
TrustedSource -> ByteString
"t\0"
Provenance
GatedSource -> ByteString
"g\0"
servedBytes :: ServeRuntime -> PackumentDeps -> [Contribution] -> MergePlan -> ETag -> IO ByteString
servedBytes :: ServeRuntime
-> PackumentDeps
-> [Contribution]
-> MergePlan
-> ETag
-> IO ByteString
servedBytes ServeRuntime
rt PackumentDeps
deps [Contribution]
sources MergePlan
plan ETag
etag =
MetricsPort
-> MetadataCache -> Text -> IO ByteString -> IO ByteString
resolveAssembled (ServeRuntime -> MetricsPort
srMetrics ServeRuntime
rt) (ServeRuntime -> MetadataCache
srMetadataCache ServeRuntime
rt) (ETag -> Text
renderETag ETag
etag) (IO ByteString -> IO ByteString) -> IO ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$
IO ByteString -> IO ByteString
markRenderEscape (IO ByteString -> IO ByteString) -> IO ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$
ByteString -> IO ByteString
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ByteString -> IO ByteString) -> ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$!
LByteString -> ByteString
LBS.toStrict (PackumentDeps -> CachedDoc -> LByteString
pdSerialise PackumentDeps
deps (ServedBody -> CachedDoc
servedDoc (PackumentDeps -> [Contribution] -> MergePlan -> ServedBody
renderServedBody PackumentDeps
deps [Contribution]
sources MergePlan
plan)))
where
markRenderEscape :: IO ByteString -> IO ByteString
markRenderEscape :: IO ByteString -> IO ByteString
markRenderEscape IO ByteString
render = IO ByteString
render IO ByteString -> (SomeException -> IO ByteString) -> IO ByteString
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> (SomeException -> m a) -> m a
`catchAny` (RenderEscape -> IO ByteString
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO (RenderEscape -> IO ByteString)
-> (SomeException -> RenderEscape)
-> SomeException
-> IO ByteString
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SomeException -> RenderEscape
RenderEscape)
renderServedBody :: PackumentDeps -> [Contribution] -> MergePlan -> ServedBody
renderServedBody :: PackumentDeps -> [Contribution] -> MergePlan -> ServedBody
renderServedBody PackumentDeps
deps [Contribution]
sources MergePlan
plan =
CachedDoc -> ServedBody
ServedBody (PackumentDeps
-> Text
-> Map Int CachedDoc
-> MergePlan
-> Maybe CachedDoc
-> CachedDoc
pdAssemble PackumentDeps
deps (PackumentDeps -> Text
pdMountBaseUrl PackumentDeps
deps) Map Int CachedDoc
bySource MergePlan
plan ([Contribution] -> Maybe CachedDoc
baseDocument [Contribution]
sources))
where
bySource :: Map SourceId CachedDoc
bySource :: Map Int CachedDoc
bySource = [(Int, CachedDoc)] -> Map Int CachedDoc
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([Int] -> [CachedDoc] -> [(Int, CachedDoc)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
0 ..] ((Contribution -> CachedDoc) -> [Contribution] -> [CachedDoc]
forall a b. (a -> b) -> [a] -> [b]
map Contribution -> CachedDoc
srcValue [Contribution]
sources))
baseDocument :: [Contribution] -> Maybe CachedDoc
baseDocument :: [Contribution] -> Maybe CachedDoc
baseDocument [Contribution]
sources =
Contribution -> CachedDoc
srcValue (Contribution -> CachedDoc)
-> Maybe Contribution -> Maybe CachedDoc
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ((Contribution -> Bool) -> [Contribution] -> Maybe Contribution
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((Provenance -> Provenance -> Bool
forall a. Eq a => a -> a -> Bool
== Provenance
TrustedSource) (Provenance -> Bool)
-> (Contribution -> Provenance) -> Contribution -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Contribution -> Provenance
srcProvenance) [Contribution]
sources Maybe Contribution -> Maybe Contribution -> Maybe Contribution
forall a. Maybe a -> Maybe a -> Maybe a
forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> [Contribution] -> Maybe Contribution
forall a. [a] -> Maybe a
listToMaybe [Contribution]
sources)
collectDecisions :: OriginResult -> OriginResult -> [ServeDecision] -> [ServeDecision]
collectDecisions :: OriginResult -> OriginResult -> [ServeDecision] -> [ServeDecision]
collectDecisions OriginResult
privResult OriginResult
pubResult [ServeDecision]
publicExclusions =
OriginResult -> [ServeDecision]
privateDecision OriginResult
privResult [ServeDecision] -> [ServeDecision] -> [ServeDecision]
forall a. Semigroup a => a -> a -> a
<> OriginResult -> [ServeDecision]
publicMismatch OriginResult
pubResult [ServeDecision] -> [ServeDecision] -> [ServeDecision]
forall a. Semigroup a => a -> a -> a
<> [ServeDecision]
publicExclusions
where
privateDecision :: OriginResult -> [ServeDecision]
privateDecision :: OriginResult -> [ServeDecision]
privateDecision = \case
OriginResolved Manifest
_ -> []
OriginResult
OriginUnresolved -> [ServeDecision
neededUpstreamUnavailable]
OriginResult
OriginNameMismatch -> [ServeDecision
upstreamInvalidDecision]
OriginResult
OriginAbsent -> []
publicMismatch :: OriginResult -> [ServeDecision]
publicMismatch :: OriginResult -> [ServeDecision]
publicMismatch = \case
OriginResult
OriginNameMismatch -> [ServeDecision
upstreamInvalidDecision]
OriginResolved Manifest
_ -> []
OriginResult
OriginUnresolved -> []
OriginResult
OriginAbsent -> []
neededUpstreamUnavailable :: ServeDecision
neededUpstreamUnavailable :: ServeDecision
neededUpstreamUnavailable = Rejection -> ServeDecision
Reject (RejectReason -> Text -> Rejection
Rejection (Transience -> RejectReason
Unavailable (Maybe RetryAfter -> Transience
WillResolve Maybe RetryAfter
forall a. Maybe a
Nothing)) Text
"a needed upstream was unavailable")
upstreamInvalidDecision :: ServeDecision
upstreamInvalidDecision :: ServeDecision
upstreamInvalidDecision = Rejection -> ServeDecision
Reject (RejectReason -> Text -> Rejection
Rejection RejectReason
UpstreamInvalid Text
"an upstream returned a packument for a different package")
packumentResponse :: PackumentReplies response -> PackumentServe -> ETag -> ByteString -> response
packumentResponse :: forall response.
PackumentReplies response
-> PackumentServe -> ETag -> ByteString -> response
packumentResponse PackumentReplies response
replies PackumentServe
mode ETag
etag ByteString
bytes = case PackumentServe
mode of
PackumentServe
PackumentFull ->
PackumentReplies response
-> ResponseHeaders -> LByteString -> response
forall response.
PackumentReplies response
-> ResponseHeaders -> LByteString -> response
packumentOk PackumentReplies response
replies [ETag -> Header
etagHeader ETag
etag] (ByteString -> LByteString
LBS.fromStrict ByteString
bytes)
PackumentServe
PackumentHead ->
PackumentReplies response
-> ResponseHeaders -> LByteString -> response
forall response.
PackumentReplies response
-> ResponseHeaders -> LByteString -> response
packumentOk
PackumentReplies response
replies
[ETag -> Header
etagHeader ETag
etag, (HeaderName
hContentLength, Int -> ByteString
forall b a. (Show a, IsString b) => a -> b
show (ByteString -> Int
BS.length ByteString
bytes))]
(ByteString -> LByteString
LBS.fromStrict ByteString
bytes)
noSurvivors :: PackumentReplies response -> PackumentDeps -> [ServeDecision] -> response
noSurvivors :: forall response.
PackumentReplies response
-> PackumentDeps -> [ServeDecision] -> response
noSurvivors PackumentReplies response
replies PackumentDeps
deps [ServeDecision]
decisions = case PackumentStatus
status of
PackumentStatus
PackumentOk -> PackumentReplies response -> ResponseHeaders -> Text -> response
forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentInternal PackumentReplies response
replies [] Text
body
PackumentStatus
PackumentForbidden -> PackumentReplies response -> ResponseHeaders -> Text -> response
forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentForbidden PackumentReplies response
replies [] Text
body
PackumentUnavailable{} -> PackumentReplies response -> ResponseHeaders -> Text -> response
forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentUnavailable PackumentReplies response
replies (PackumentStatus -> ResponseHeaders
retryAfterHeader PackumentStatus
status) Text
body
PackumentStatus
PackumentBadGateway -> PackumentReplies response -> ResponseHeaders -> Text -> response
forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentBadGateway PackumentReplies response
replies [] Text
body
PackumentStatus
PackumentServerError -> PackumentReplies response -> ResponseHeaders -> Text -> response
forall response.
PackumentReplies response -> ResponseHeaders -> Text -> response
packumentInternal PackumentReplies response
replies [] Text
body
where
status :: PackumentStatus
status :: PackumentStatus
status = [ServeDecision] -> PackumentStatus
packumentStatus [ServeDecision]
decisions
message :: Text
message :: Text
message = case (ServeDecision -> Maybe Text) -> [ServeDecision] -> [Text]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe ServeDecision -> Maybe Text
rejectionText [ServeDecision]
decisions of
[] -> Text
"no versions are available for this package"
[Text]
reasons -> Text -> [Text] -> Text
T.intercalate Text
"; " [Text]
reasons
body :: Text
body = Maybe HelpMessage -> Text -> Text
appendHelp (PackumentDeps -> Maybe HelpMessage
pdHelp PackumentDeps
deps) Text
message
rejectionText :: ServeDecision -> Maybe Text
rejectionText :: ServeDecision -> Maybe Text
rejectionText = \case
ServeDecision
Admit -> Maybe Text
forall a. Maybe a
Nothing
Reject Rejection
rej -> Text -> Maybe Text
forall a. a -> Maybe a
Just (Rejection -> Text
rejectionMessage Rejection
rej)
retryAfterHeader :: PackumentStatus -> ResponseHeaders
= \case
PackumentUnavailable (Just (RetryAfter Int
secs)) -> [(HeaderName
"Retry-After", Int -> ByteString
forall b a. (Show a, IsString b) => a -> b
show Int
secs)]
PackumentStatus
_ -> []