module Ecluse.Core.Server.Pipeline.Packument (
servePackument,
headPackument,
withPublicMetadataClient,
packumentETag,
) where
import Crypto.Hash (Context, SHA256, hashFinalize, hashInit, hashUpdates)
import Data.Aeson (Value (Object))
import Data.Aeson qualified as Aeson
import Data.Aeson.Text (encodeToLazyText)
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 Data.Text.Lazy qualified as TL
import Katip (KatipContext, Severity (DebugS, InfoS, WarningS), katipAddContext, logFM, ls, sl)
import Network.HTTP.Client (Manager)
import Network.HTTP.Types (ResponseHeaders, Status, hContentLength, mkStatus, status200)
import Network.Wai (Request, Response, ResponseReceived, requestHeaders)
import UnliftIO (concurrently, withRunInIO)
import UnliftIO.Exception (tryAny)
import Ecluse.Core.Credential (Secret)
import Ecluse.Core.Package (
InvalidEntry (invalidKey, invalidKind, invalidReason, invalidValue),
InvalidEntryKind (InvalidDistTag, InvalidPublishTime, InvalidVersionManifest),
PackageInfo (infoVersions),
PackageName,
renderPackageName,
)
import Ecluse.Core.Package.Filter (filterPlanFromDecisions, fpDecisions, fpSurvivors, restrictToSurvivors)
import Ecluse.Core.Package.Integrity (
MinTrustedIntegrity,
)
import Ecluse.Core.Package.Merge (
MergePlan (mpSurvivors),
Provenance (GatedSource, TrustedSource),
SourceId,
mergePackuments,
)
import Ecluse.Core.Registry.Metadata (
ContentDigest,
Manifest (manifestDigest, manifestInfo, manifestRaw),
MetadataClient (fetchFullManifest),
MetadataError (MetadataBoundExceeded, MetadataNameMismatch, MetadataUndecodable),
digestBytes,
)
import Ecluse.Core.Rules (evalRules)
import Ecluse.Core.Rules.Types (Decision, EvalContext (EvalContext))
import Ecluse.Core.Security (
LimitError (BodyTooLarge, TooDeeplyNested, TooManyVersions),
Limits,
)
import Ecluse.Core.Server.Admission (withServeAdmission)
import Ecluse.Core.Server.Cache (Source (Source), resolveAssembled)
import Ecluse.Core.Server.Conditional (Conditional (Modified, NotModified), ETag, etagHeader, evaluateETag, mkStrongETag, renderETag)
import Ecluse.Core.Server.Context (
Handler,
MountBinding (bindingPackumentDeps, bindingRenderer),
PackumentDeps (..),
ServeRuntime (..),
ctxMount,
ctxRuntime,
)
import Ecluse.Core.Server.Metadata (ManifestCaching (Cached, Uncached))
import Ecluse.Core.Server.Pipeline.Internal (
admitByIntegrity,
evalTier,
logDecodeFailure,
logNameMismatch,
packumentServeDecision,
recordDenials,
recordEffectfulFailures,
)
import Ecluse.Core.Server.Pipeline.Shared
import Ecluse.Core.Server.Response (
MountRenderer,
PackumentStatus (PackumentBadGateway, PackumentForbidden, PackumentOk, PackumentServerError, PackumentUnavailable),
RejectReason (Unavailable, UpstreamInvalid),
Rejection (Rejection, rejectionMessage),
RetryAfter (RetryAfter),
ServeDecision (Admit, Reject),
Transience (WillResolve),
packumentStatus,
packumentStatusCode,
renderError,
serveDecisionOf,
)
import Ecluse.Core.Telemetry.Metrics qualified as Metric
import Ecluse.Core.Telemetry.Record (MetricsPort (..), timedSeconds)
import Ecluse.Core.Telemetry.Span (TracingPort, spanPackumentGate)
servePackument ::
PackageName ->
Request ->
(Response -> IO ResponseReceived) ->
Handler ResponseReceived
servePackument :: PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
servePackument = PackumentServe
-> PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
packumentWith PackumentServe
PackumentFull
headPackument ::
PackageName ->
Request ->
(Response -> IO ResponseReceived) ->
Handler ResponseReceived
headPackument :: PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
headPackument PackageName
name Request
request Response -> IO ResponseReceived
respond =
PackumentServe
-> PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
packumentWith PackumentServe
PackumentHead PackageName
name Request
request (Response -> IO ResponseReceived
respond (Response -> IO ResponseReceived)
-> (Response -> Response) -> Response -> IO ResponseReceived
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Response -> Response
bodiless)
data PackumentServe
=
PackumentFull
|
PackumentHead
packumentWith ::
PackumentServe ->
PackageName ->
Request ->
(Response -> IO ResponseReceived) ->
Handler ResponseReceived
packumentWith :: PackumentServe
-> PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
packumentWith PackumentServe
mode PackageName
name Request
request Response -> IO ResponseReceived
respond = do
renderer <- (RequestCtx -> MountRenderer) -> Handler MountRenderer
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks (MountBinding -> MountRenderer
bindingRenderer (MountBinding -> MountRenderer)
-> (RequestCtx -> MountBinding) -> RequestCtx -> MountRenderer
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RequestCtx -> MountBinding
ctxMount)
asks (bindingPackumentDeps . ctxMount) >>= \case
Maybe PackumentDeps
Nothing -> IO ResponseReceived -> Handler ResponseReceived
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (Response -> IO ResponseReceived
respond (MountRenderer -> Response
recognisedButUnserved MountRenderer
renderer))
Just PackumentDeps
deps -> PackumentServe
-> MountRenderer
-> PackumentDeps
-> PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
serveWithDeps PackumentServe
mode MountRenderer
renderer PackumentDeps
deps PackageName
name Request
request Response -> IO ResponseReceived
respond
serveWithDeps ::
PackumentServe ->
MountRenderer ->
PackumentDeps ->
PackageName ->
Request ->
(Response -> IO ResponseReceived) ->
Handler ResponseReceived
serveWithDeps :: PackumentServe
-> MountRenderer
-> PackumentDeps
-> PackageName
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
serveWithDeps PackumentServe
mode MountRenderer
renderer 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) Maybe Secret
clientToken) = IO ResponseReceived -> Handler ResponseReceived
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (Response -> IO ResponseReceived
respond (MountRenderer -> Response
edgeUnauthorised MountRenderer
renderer))
| 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) (serveAdmitted 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 (MountRenderer -> Response
serveOverloaded MountRenderer
renderer)
where
clientToken :: Maybe Secret
clientToken = Request -> Maybe Secret
forwardedToken Request
request
serveAdmitted :: ServeRuntime -> Handler ResponseReceived
serveAdmitted 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
evalCtx <- IO EvalContext -> Handler EvalContext
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (UTCTime -> EvalContext
EvalContext (UTCTime -> EvalContext) -> IO UTCTime -> IO EvalContext
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> PackumentDeps -> IO UTCTime
pdNow PackumentDeps
deps)
(privResult, pubResult) <-
concurrently
(fetchPrivateOrigin deps rt clientToken name)
(fetchPublicOrigin deps rt name)
(public, publicExclusions) <- 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]
case packumentPlan sources of
Just MergePlan
plan -> 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)
ServeRuntime
-> [Contribution] -> MergePlan -> Handler ResponseReceived
forall {m :: * -> *}.
KatipContext m =>
ServeRuntime -> [Contribution] -> MergePlan -> m ResponseReceived
answerConditional ServeRuntime
rt [Contribution]
sources MergePlan
plan
Maybe MergePlan
Nothing -> 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)
IO ResponseReceived -> Handler ResponseReceived
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (Response -> IO ResponseReceived
respond (MountRenderer -> PackumentDeps -> [ServeDecision] -> Response
noSurvivors MountRenderer
renderer PackumentDeps
deps [ServeDecision]
decisions))
answerConditional :: ServeRuntime -> [Contribution] -> MergePlan -> m ResponseReceived
answerConditional 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 RequestHeaders -> ETag -> Conditional
evaluateETag (Request -> RequestHeaders
requestHeaders Request
request) ETag
etag of
NotModified ETag
matched -> do
Severity -> LogStr -> m ()
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 -> m ResponseReceived
forall a. IO a -> m a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (Response -> IO ResponseReceived
respond (ETag -> Response
notModifiedResponse ETag
matched))
Modified ETag
fresh -> do
Severity -> LogStr -> m ()
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 -> m ByteString
forall a. IO a -> m 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 mode fresh bytes))
data Contribution = Contribution
{ Contribution -> Provenance
srcProvenance :: Provenance
, Contribution -> PackageInfo
srcInfo :: PackageInfo
, Contribution -> Value
srcValue :: Value
, Contribution -> ContentDigest
srcDigest :: ContentDigest
}
fingerprintPiece :: Contribution -> (Provenance, ContentDigest, [Text])
fingerprintPiece :: Contribution -> (Provenance, ContentDigest, [Text])
fingerprintPiece Contribution
s = (Contribution -> Provenance
srcProvenance Contribution
s, Contribution -> ContentDigest
srcDigest Contribution
s, Map Text PackageDetails -> [Text]
forall k a. Map k a -> [k]
Map.keys (PackageInfo -> Map Text PackageDetails
infoVersions (Contribution -> PackageInfo
srcInfo Contribution
s)))
data OriginResult
=
OriginResolved Manifest
|
OriginNameMismatch
|
OriginUnresolved
originManifest :: OriginResult -> Maybe Manifest
originManifest :: OriginResult -> Maybe Manifest
originManifest = \case
OriginResolved Manifest
manifest -> Manifest -> Maybe Manifest
forall a. a -> Maybe a
Just Manifest
manifest
OriginResult
OriginNameMismatch -> Maybe Manifest
forall a. Maybe a
Nothing
OriginResult
OriginUnresolved -> Maybe Manifest
forall a. Maybe a
Nothing
originResultOf :: Either SomeException (Either MetadataError Manifest) -> OriginResult
originResultOf :: Either SomeException (Either MetadataError Manifest)
-> OriginResult
originResultOf = \case
Left SomeException
_ -> OriginResult
OriginUnresolved
Right (Left (MetadataNameMismatch Text
_)) -> OriginResult
OriginNameMismatch
Right (Left MetadataError
_) -> OriginResult
OriginUnresolved
Right (Right Manifest
manifest) -> Manifest -> OriginResult
OriginResolved Manifest
manifest
fetchPrivateOrigin :: PackumentDeps -> ServeRuntime -> Maybe Secret -> PackageName -> Handler OriginResult
fetchPrivateOrigin :: PackumentDeps
-> ServeRuntime
-> Maybe Secret
-> PackageName
-> Handler OriginResult
fetchPrivateOrigin PackumentDeps
deps ServeRuntime
rt Maybe Secret
token PackageName
name = 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
"fetching private origin for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PackageName -> Text
renderPackageName PackageName
name))
resolved <-
Handler (Either MetadataError Manifest)
-> Handler (Either SomeException (Either MetadataError Manifest))
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> m (Either SomeException a)
tryAny (Handler (Either MetadataError Manifest)
-> Handler (Either SomeException (Either MetadataError Manifest)))
-> Handler (Either MetadataError Manifest)
-> Handler (Either SomeException (Either MetadataError Manifest))
forall a b. (a -> b) -> a -> b
$
ServeRuntime
-> PackumentDeps
-> Upstream
-> ManifestCaching
-> Limits
-> Manager
-> Text
-> Maybe Secret
-> (MetadataClient -> IO (Either MetadataError Manifest))
-> Handler (Either MetadataError Manifest)
forall a.
ServeRuntime
-> PackumentDeps
-> Upstream
-> ManifestCaching
-> Limits
-> Manager
-> Text
-> Maybe Secret
-> (MetadataClient -> IO a)
-> Handler a
withMetadataClient ServeRuntime
rt PackumentDeps
deps Upstream
Metric.Private ManifestCaching
Uncached (PackumentDeps -> Limits
pdLimits PackumentDeps
deps) (ServeRuntime -> Manager
srPrivateManager ServeRuntime
rt) (PackumentDeps -> Text
pdPrivateBaseUrl PackumentDeps
deps) Maybe Secret
token ((MetadataClient -> IO (Either MetadataError Manifest))
-> Handler (Either MetadataError Manifest))
-> (MetadataClient -> IO (Either MetadataError Manifest))
-> Handler (Either MetadataError Manifest)
forall a b. (a -> b) -> a -> b
$ \MetadataClient
client ->
MetadataClient -> PackageName -> IO (Either MetadataError Manifest)
fetchFullManifest MetadataClient
client PackageName
name
pure (originResultOf resolved)
fetchPublicOrigin :: PackumentDeps -> ServeRuntime -> PackageName -> Handler OriginResult
fetchPublicOrigin :: PackumentDeps
-> ServeRuntime -> PackageName -> Handler OriginResult
fetchPublicOrigin PackumentDeps
deps ServeRuntime
rt PackageName
name = 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
"fetching public origin for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PackageName -> Text
renderPackageName PackageName
name))
resolved <-
Handler (Either MetadataError Manifest)
-> Handler (Either SomeException (Either MetadataError Manifest))
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> m (Either SomeException a)
tryAny (Handler (Either MetadataError Manifest)
-> Handler (Either SomeException (Either MetadataError Manifest)))
-> Handler (Either MetadataError Manifest)
-> Handler (Either SomeException (Either MetadataError Manifest))
forall a b. (a -> b) -> a -> b
$
ServeRuntime
-> PackumentDeps
-> Text
-> (MetadataClient -> IO (Either MetadataError Manifest))
-> Handler (Either MetadataError Manifest)
forall a.
ServeRuntime
-> PackumentDeps -> Text -> (MetadataClient -> IO a) -> Handler a
withPublicMetadataClient ServeRuntime
rt PackumentDeps
deps (PackumentDeps -> Text
pdPublicBaseUrl PackumentDeps
deps) ((MetadataClient -> IO (Either MetadataError Manifest))
-> Handler (Either MetadataError Manifest))
-> (MetadataClient -> IO (Either MetadataError Manifest))
-> Handler (Either MetadataError Manifest)
forall a b. (a -> b) -> a -> b
$ \MetadataClient
client ->
MetadataClient -> PackageName -> IO (Either MetadataError Manifest)
fetchFullManifest MetadataClient
client PackageName
name
pure (originResultOf resolved)
withMetadataClient ::
ServeRuntime ->
PackumentDeps ->
Metric.Upstream ->
ManifestCaching ->
Limits ->
Manager ->
Text ->
Maybe Secret ->
(MetadataClient -> IO a) ->
Handler a
withMetadataClient :: forall a.
ServeRuntime
-> PackumentDeps
-> Upstream
-> ManifestCaching
-> Limits
-> Manager
-> Text
-> Maybe Secret
-> (MetadataClient -> IO a)
-> Handler a
withMetadataClient ServeRuntime
rt PackumentDeps
deps Upstream
upstream ManifestCaching
caching Limits
limits Manager
manager Text
baseUrl Maybe Secret
token MetadataClient -> IO a
k =
((forall a. Handler a -> IO a) -> IO a) -> Handler a
forall b. ((forall a. Handler a -> IO a) -> IO b) -> Handler b
forall (m :: * -> *) b.
MonadUnliftIO m =>
((forall a. m a -> IO a) -> IO b) -> m b
withRunInIO (((forall a. Handler a -> IO a) -> IO a) -> Handler a)
-> ((forall a. Handler a -> IO a) -> IO a) -> Handler a
forall a b. (a -> b) -> a -> b
$ \forall a. Handler a -> IO a
runInIO ->
MetadataClient -> IO a
k (MetadataClient -> IO a) -> MetadataClient -> IO a
forall a b. (a -> b) -> a -> b
$
PackumentDeps
-> TracingPort
-> MetricsPort
-> Upstream
-> ManifestCaching
-> (PackageName -> MetadataError -> IO ())
-> (PackageName -> [InvalidEntry] -> IO ())
-> (PackageName -> IO ())
-> Limits
-> Manager
-> Text
-> Maybe Secret
-> MetadataClient
pdNewMetadataClient
PackumentDeps
deps
(ServeRuntime -> TracingPort
srTracing ServeRuntime
rt)
(ServeRuntime -> MetricsPort
srMetrics ServeRuntime
rt)
Upstream
upstream
ManifestCaching
caching
(\PackageName
nm MetadataError
err -> Handler () -> IO ()
forall a. Handler a -> IO a
runInIO (PackageName -> Text -> MetadataError -> Handler ()
logMetadataFailure PackageName
nm Text
baseUrl MetadataError
err))
(\PackageName
nm [InvalidEntry]
entries -> Handler () -> IO ()
forall a. Handler a -> IO a
runInIO (PackageName -> Text -> [InvalidEntry] -> Handler ()
forall (m :: * -> *).
KatipContext m =>
PackageName -> Text -> [InvalidEntry] -> m ()
logInvalidEntries PackageName
nm Text
baseUrl [InvalidEntry]
entries))
(\PackageName
nm -> Handler () -> IO ()
forall a. Handler a -> IO a
runInIO (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
"fetching packument from origin for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PackageName -> Text
renderPackageName PackageName
nm))))
Limits
limits
Manager
manager
Text
baseUrl
Maybe Secret
token
withPublicMetadataClient :: ServeRuntime -> PackumentDeps -> Text -> (MetadataClient -> IO a) -> Handler a
withPublicMetadataClient :: forall a.
ServeRuntime
-> PackumentDeps -> Text -> (MetadataClient -> IO a) -> Handler a
withPublicMetadataClient ServeRuntime
rt PackumentDeps
deps Text
baseUrl =
ServeRuntime
-> PackumentDeps
-> Upstream
-> ManifestCaching
-> Limits
-> Manager
-> Text
-> Maybe Secret
-> (MetadataClient -> IO a)
-> Handler a
forall a.
ServeRuntime
-> PackumentDeps
-> Upstream
-> ManifestCaching
-> Limits
-> Manager
-> Text
-> Maybe Secret
-> (MetadataClient -> IO a)
-> Handler a
withMetadataClient ServeRuntime
rt PackumentDeps
deps Upstream
Metric.Public (MetadataCache -> Source -> ManifestCaching
Cached (ServeRuntime -> MetadataCache
srMetadataCache ServeRuntime
rt) (Text -> Source
Source Text
baseUrl)) (PackumentDeps -> Limits
pdLimits PackumentDeps
deps) (ServeRuntime -> Manager
srPublicManager ServeRuntime
rt) Text
baseUrl Maybe Secret
forall a. Maybe a
Nothing
logMetadataFailure :: PackageName -> Text -> MetadataError -> Handler ()
logMetadataFailure :: PackageName -> Text -> MetadataError -> Handler ()
logMetadataFailure PackageName
name Text
baseUrl = \case
MetadataBoundExceeded LimitError
err -> PackageName -> LimitError -> Handler ()
forall (m :: * -> *).
KatipContext m =>
PackageName -> LimitError -> m ()
logBreach PackageName
name LimitError
err
MetadataError
MetadataUndecodable -> PackageName -> Handler ()
forall (m :: * -> *). KatipContext m => PackageName -> m ()
logDecodeFailure PackageName
name
MetadataNameMismatch Text
reported -> PackageName -> Text -> Text -> Handler ()
forall (m :: * -> *).
KatipContext m =>
PackageName -> Text -> Text -> m ()
logNameMismatch PackageName
name Text
baseUrl Text
reported
logBreach :: (KatipContext m) => PackageName -> LimitError -> m ()
logBreach :: forall (m :: * -> *).
KatipContext m =>
PackageName -> LimitError -> m ()
logBreach PackageName
name LimitError
err =
SimpleLogPayload -> m () -> m ()
forall i (m :: * -> *) a.
(LogItem i, KatipContext m) =>
i -> m a -> m a
katipAddContext SimpleLogPayload
payload (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Severity -> LogStr -> m ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
WarningS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls Text
message)
where
payload :: SimpleLogPayload
payload =
Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"module" Text
pipelineModule
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"package" (PackageName -> Text
renderPackageName PackageName
name)
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"bound" Text
boundName
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"observed" Text
observed
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"cap" Text
cap
message :: Text
message :: Text
message = Text
"refused an upstream metadata document: it exceeded the " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
boundName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" response bound (observed " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
observed Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
", cap " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
cap Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
boundName :: Text
observed :: Text
cap :: Text
(Text
boundName, Text
observed, Text
cap) = case LimitError
err of
BodyTooLarge Int
c -> (Text
"body-size", Text
"over " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
c Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" bytes", Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
c Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" bytes")
TooManyVersions Int
seen Int
c -> (Text
"version-count", Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
seen, Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
c)
TooDeeplyNested Int
c -> (Text
"nesting-depth", Text
"over " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
c Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" levels", Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
c Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" levels")
logInvalidEntries :: (KatipContext m) => PackageName -> Text -> [InvalidEntry] -> m ()
logInvalidEntries :: forall (m :: * -> *).
KatipContext m =>
PackageName -> Text -> [InvalidEntry] -> m ()
logInvalidEntries PackageName
name Text
baseUrl [InvalidEntry]
entries =
SimpleLogPayload -> m () -> m ()
forall i (m :: * -> *) a.
(LogItem i, KatipContext m) =>
i -> m a -> m a
katipAddContext SimpleLogPayload
payload (m () -> m ()) -> m () -> m ()
forall a b. (a -> b) -> a -> b
$
Severity -> LogStr -> m ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
WarningS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls Text
message)
where
payload :: SimpleLogPayload
payload =
Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"module" Text
pipelineModule
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"package" (PackageName -> Text
renderPackageName PackageName
name)
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"upstream" Text
baseUrl
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Int -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"droppedVersionManifests" (InvalidEntryKind -> Int
countOf InvalidEntryKind
InvalidVersionManifest)
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Int -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"droppedDistTags" (InvalidEntryKind -> Int
countOf InvalidEntryKind
InvalidDistTag)
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Int -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"droppedPublishTimes" (InvalidEntryKind -> Int
countOf InvalidEntryKind
InvalidPublishTime)
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> [Text] -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"droppedEntries" ((InvalidEntry -> Text) -> [InvalidEntry] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map InvalidEntry -> Text
renderDroppedEntry (Int -> [InvalidEntry] -> [InvalidEntry]
forall a. Int -> [a] -> [a]
take Int
maxRenderedDrops [InvalidEntry]
entries))
countOf :: InvalidEntryKind -> Int
countOf :: InvalidEntryKind -> Int
countOf InvalidEntryKind
kind = (Int -> InvalidEntry -> Int) -> Int -> [InvalidEntry] -> Int
forall b a. (b -> a -> b) -> b -> [a] -> b
forall (t :: * -> *) b a.
Foldable t =>
(b -> a -> b) -> b -> t a -> b
foldl' (\Int
acc InvalidEntry
e -> if InvalidEntry -> InvalidEntryKind
invalidKind InvalidEntry
e InvalidEntryKind -> InvalidEntryKind -> Bool
forall a. Eq a => a -> a -> Bool
== InvalidEntryKind
kind then Int
acc Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1 else Int
acc) Int
0 [InvalidEntry]
entries
message :: Text
message :: Text
message =
Text
"dropped " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show ([InvalidEntry] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [InvalidEntry]
entries) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" malformed entr" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
plural Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" from an upstream packument (the rest is served)"
plural :: Text
plural = if [InvalidEntry] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [InvalidEntry]
entries Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 then Text
"y" else Text
"ies"
renderDroppedEntry :: InvalidEntry -> Text
renderDroppedEntry :: InvalidEntry -> Text
renderDroppedEntry InvalidEntry
e =
InvalidEntryKind -> Text
renderInvalidKind (InvalidEntry -> InvalidEntryKind
invalidKind InvalidEntry
e)
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> InvalidEntry -> Text
invalidKey InvalidEntry
e
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" = "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Value -> Text
truncatedValue (InvalidEntry -> Value
invalidValue InvalidEntry
e)
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ("
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> InvalidEntry -> Text
invalidReason InvalidEntry
e
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")"
renderInvalidKind :: InvalidEntryKind -> Text
renderInvalidKind :: InvalidEntryKind -> Text
renderInvalidKind = \case
InvalidEntryKind
InvalidVersionManifest -> Text
"version-manifest"
InvalidEntryKind
InvalidDistTag -> Text
"dist-tag"
InvalidEntryKind
InvalidPublishTime -> Text
"publish-time"
truncatedValue :: Value -> Text
truncatedValue :: Value -> Text
truncatedValue Value
v =
let rendered :: Text
rendered = LazyText -> Text
TL.toStrict (Int64 -> LazyText -> LazyText
TL.take (Int -> Int64
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
maxRenderedValueChars Int64 -> Int64 -> Int64
forall a. Num a => a -> a -> a
+ Int64
1) (Value -> LazyText
forall a. ToJSON a => a -> LazyText
encodeToLazyText Value
v))
in if Text -> Int
T.length Text
rendered Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
maxRenderedValueChars
then Int -> Text -> Text
T.take Int
maxRenderedValueChars Text
rendered Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"…"
else Text
rendered
maxRenderedDrops :: Int
maxRenderedDrops :: Int
maxRenderedDrops = Int
20
maxRenderedValueChars :: Int
maxRenderedValueChars :: Int
maxRenderedValueChars = Int
200
pipelineModule :: Text
pipelineModule :: Text
pipelineModule = Text
"Ecluse.Server.Pipeline"
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 -> Value -> ContentDigest -> Contribution
Contribution Provenance
TrustedSource PackageInfo
admissible (Manifest -> Value
manifestRaw Manifest
manifest) (Manifest -> ContentDigest
manifestDigest Manifest
manifest)), [ServeDecision]
integrityRefusals)
gatePublic :: TracingPort -> MetricsPort -> PackumentDeps -> PackageName -> EvalContext -> Maybe Manifest -> IO (Maybe Contribution, [ServeDecision])
gatePublic :: TracingPort
-> MetricsPort
-> PackumentDeps
-> PackageName
-> EvalContext
-> Maybe Manifest
-> IO (Maybe Contribution, [ServeDecision])
gatePublic TracingPort
tracing MetricsPort
metrics PackumentDeps
deps PackageName
name EvalContext
ctx = \case
Maybe Manifest
Nothing -> (Maybe Contribution, [ServeDecision])
-> IO (Maybe Contribution, [ServeDecision])
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])
-> IO (Maybe Contribution, [ServeDecision]))
-> IO (Maybe Contribution, [ServeDecision])
-> IO (Maybe Contribution, [ServeDecision])
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 (Nothing, projectDecisions admissible (fpDecisions plan) <> integrityRefusals)
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] -> [ServeDecision]
projectDecisions :: PackageInfo -> [Decision] -> [ServeDecision]
projectDecisions PackageInfo
info =
(PackageDetails -> Decision -> ServeDecision)
-> [PackageDetails] -> [Decision] -> [ServeDecision]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith PackageDetails -> Decision -> ServeDecision
serveDecisionOf (Map Text PackageDetails -> [PackageDetails]
forall k a. Map k a -> [a]
Map.elems (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
info))
newtype ServedBody = ServedBody {ServedBody -> Value
servedValue :: Value}
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
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
$
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 (Value -> LByteString
forall a. ToJSON a => a -> LByteString
Aeson.encode (ServedBody -> Value
servedValue (PackumentDeps -> [Contribution] -> MergePlan -> ServedBody
renderServedBody PackumentDeps
deps [Contribution]
sources MergePlan
plan)))
renderServedBody :: PackumentDeps -> [Contribution] -> MergePlan -> ServedBody
renderServedBody :: PackumentDeps -> [Contribution] -> MergePlan -> ServedBody
renderServedBody PackumentDeps
deps [Contribution]
sources MergePlan
plan =
Value -> ServedBody
ServedBody (PackumentDeps
-> Text -> Map Int Value -> MergePlan -> Value -> Value
pdAssemble PackumentDeps
deps (PackumentDeps -> Text
pdMountBaseUrl PackumentDeps
deps) Map Int Value
bySource MergePlan
plan ([Contribution] -> Value
baseDocument [Contribution]
sources))
where
bySource :: Map SourceId Value
bySource :: Map Int Value
bySource = [(Int, Value)] -> Map Int Value
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList ([Int] -> [Value] -> [(Int, Value)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
0 ..] ((Contribution -> Value) -> [Contribution] -> [Value]
forall a b. (a -> b) -> [a] -> [b]
map Contribution -> Value
srcValue [Contribution]
sources))
baseDocument :: [Contribution] -> Value
baseDocument :: [Contribution] -> Value
baseDocument [Contribution]
sources =
case (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 of
Just Contribution
s -> Contribution -> Value
srcValue Contribution
s
Maybe Contribution
Nothing -> case [Contribution]
sources of
Contribution
s : [Contribution]
_ -> Contribution -> Value
srcValue Contribution
s
[] -> Object -> Value
Object Object
forall a. Monoid a => a
mempty
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]
publicMismatch :: OriginResult -> [ServeDecision]
publicMismatch :: OriginResult -> [ServeDecision]
publicMismatch = \case
OriginResult
OriginNameMismatch -> [ServeDecision
upstreamInvalidDecision]
OriginResolved Manifest
_ -> []
OriginResult
OriginUnresolved -> []
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 :: PackumentServe -> ETag -> ByteString -> Response
packumentResponse :: PackumentServe -> ETag -> ByteString -> Response
packumentResponse PackumentServe
mode ETag
etag ByteString
bytes = case PackumentServe
mode of
PackumentServe
PackumentFull ->
Status -> RequestHeaders -> LByteString -> Response
jsonResponse Status
status200 [ETag -> Header
etagHeader ETag
etag] (ByteString -> LByteString
LBS.fromStrict ByteString
bytes)
PackumentServe
PackumentHead ->
Status -> RequestHeaders -> LByteString -> Response
jsonResponse
Status
status200
[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)
notModifiedResponse :: ETag -> Response
notModifiedResponse :: ETag -> Response
notModifiedResponse ETag
etag =
Status -> RequestHeaders -> LByteString -> Response
jsonResponse (Int -> ByteString -> Status
mkStatus Int
304 ByteString
"Not Modified") [ETag -> Header
etagHeader ETag
etag] LByteString
""
noSurvivors :: MountRenderer -> PackumentDeps -> [ServeDecision] -> Response
noSurvivors :: MountRenderer -> PackumentDeps -> [ServeDecision] -> Response
noSurvivors MountRenderer
renderer PackumentDeps
deps [ServeDecision]
decisions =
Status -> RequestHeaders -> RenderedBody -> Response
renderedResponse (PackumentStatus -> Status
toStatus PackumentStatus
status) (PackumentStatus -> RequestHeaders
retryAfterHeader PackumentStatus
status) (MountRenderer -> Maybe HelpMessage -> Text -> RenderedBody
renderError MountRenderer
renderer (PackumentDeps -> Maybe HelpMessage
pdHelp PackumentDeps
deps) Text
message)
where
status :: PackumentStatus
status :: PackumentStatus
status = [ServeDecision] -> PackumentStatus
packumentStatus [ServeDecision]
decisions
toStatus :: PackumentStatus -> Status
toStatus :: PackumentStatus -> Status
toStatus PackumentStatus
s = Int -> ByteString -> Status
mkStatus (PackumentStatus -> Int
packumentStatusCode PackumentStatus
s) (PackumentStatus -> ByteString
statusReason PackumentStatus
s)
statusReason :: PackumentStatus -> ByteString
statusReason :: PackumentStatus -> ByteString
statusReason = \case
PackumentStatus
PackumentOk -> ByteString
"OK"
PackumentStatus
PackumentForbidden -> ByteString
"Forbidden"
PackumentUnavailable{} -> ByteString
"Service Unavailable"
PackumentStatus
PackumentBadGateway -> ByteString
"Bad Gateway"
PackumentStatus
PackumentServerError -> ByteString
"Internal Server Error"
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
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
_ -> []