module Ecluse.Core.Server.Pipeline.Tarball (
serveTarball,
headTarball,
) where
import Network.HTTP.Client (Manager)
import Network.HTTP.Client qualified as HTTP
import Network.HTTP.Types (RequestHeaders, ResponseHeaders, Status, hContentType, methodHead, mkStatus, statusIsSuccessful)
import Network.Wai (Request, Response, ResponseReceived, requestHeaders, responseLBS)
import UnliftIO.Exception (tryAny)
import Ecluse.Core.Credential (Secret)
import Ecluse.Core.Package (
Artifact (artFilename, artHashes, artSize, artUrl),
PackageDetails (pkgArtifacts),
PackageName,
)
import Ecluse.Core.Package.Integrity (
VersionIntegrity (BelowFloor, MeetsFloor, NoIntegrity),
classifyArtifacts,
)
import Ecluse.Core.Queue (
MirrorArtifact (MirrorArtifact, maFilename, maHashes, maSize),
MirrorJob (MirrorJob, jobArtifact, jobArtifactUrl, jobMirrorTarget, jobPackage, jobTraceContext, jobVersion),
enqueue,
)
import Ecluse.Core.Registry.Metadata (
VersionEvaluation (VersionMetadataUnavailable, VersionMissing, VersionPresent),
fetchVersionDetails,
)
import Ecluse.Core.Rules (evalRules)
import Ecluse.Core.Rules.Types (EvalContext (EvalContext))
import Ecluse.Core.Security (
Origin (TrustedOrigin, UntrustedOrigin),
hostAddress,
tarballHostAllowed,
thgAllowlist,
thgPrivateHost,
thgPublicHost,
)
import Ecluse.Core.Server.Admission (withServeAdmission)
import Ecluse.Core.Server.Conditional (forwardValidators, isNotModified)
import Ecluse.Core.Server.Context (
Handler,
MountBinding (bindingPackumentDeps, bindingRenderer),
PackumentDeps (..),
ServeRuntime (..),
ctxMount,
ctxRuntime,
)
import Ecluse.Core.Server.Pipeline.Internal (
evalTier,
recordDenials,
serveDecisionClass,
)
import Ecluse.Core.Server.Pipeline.Packument (withPublicMetadataClient)
import Ecluse.Core.Server.Pipeline.Shared
import Ecluse.Core.Server.Response (
ArtifactStatus (Forbidden, NotFound, Ok, ServerError, Unavailable'),
MountRenderer,
RejectReason (Unavailable),
Rejection (Rejection, rejectionMessage),
RetryAfter (..),
ServeDecision (Admit, Reject),
Transience (WillResolve, WontResolve),
artifactStatus,
artifactStatusCode,
renderError,
serveDecisionOf,
)
import Ecluse.Core.Server.Route (Filename (Filename))
import Ecluse.Core.Server.Stream (probeUpstreamWhen, streamUpstreamWhen)
import Ecluse.Core.Telemetry.Metrics qualified as Metric
import Ecluse.Core.Telemetry.Record (MetricsPort (..), timedSeconds)
import Ecluse.Core.Telemetry.Span (spanMirrorEnqueue, spanRuleEval)
import Ecluse.Core.Version (Version)
serveTarball ::
PackageName ->
Version ->
Filename ->
Request ->
(Response -> IO ResponseReceived) ->
Handler ResponseReceived
serveTarball :: PackageName
-> Version
-> Filename
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
serveTarball = ArtifactServe
-> PackageName
-> Version
-> Filename
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
tarballWith ArtifactServe
ServeFull
headTarball ::
PackageName ->
Version ->
Filename ->
Request ->
(Response -> IO ResponseReceived) ->
Handler ResponseReceived
headTarball :: PackageName
-> Version
-> Filename
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
headTarball PackageName
name Version
version Filename
filename Request
request Response -> IO ResponseReceived
respond =
ArtifactServe
-> PackageName
-> Version
-> Filename
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
tarballWith ArtifactServe
ServeHead PackageName
name Version
version Filename
filename 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 ArtifactServe
=
ServeFull
|
ServeHead
tarballWith ::
ArtifactServe ->
PackageName ->
Version ->
Filename ->
Request ->
(Response -> IO ResponseReceived) ->
Handler ResponseReceived
tarballWith :: ArtifactServe
-> PackageName
-> Version
-> Filename
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
tarballWith ArtifactServe
mode PackageName
name Version
version Filename
filename 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 -> ArtifactServe
-> MountRenderer
-> PackumentDeps
-> PackageName
-> Version
-> Filename
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
serveTarballWithDeps ArtifactServe
mode MountRenderer
renderer PackumentDeps
deps PackageName
name Version
version Filename
filename Request
request Response -> IO ResponseReceived
respond
serveTarballWithDeps ::
ArtifactServe ->
MountRenderer ->
PackumentDeps ->
PackageName ->
Version ->
Filename ->
Request ->
(Response -> IO ResponseReceived) ->
Handler ResponseReceived
serveTarballWithDeps :: ArtifactServe
-> MountRenderer
-> PackumentDeps
-> PackageName
-> Version
-> Filename
-> Request
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
serveTarballWithDeps ArtifactServe
mode MountRenderer
renderer PackumentDeps
deps PackageName
name Version
version (Filename Text
file) 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
let validators = RequestHeaders -> RequestHeaders
forwardValidators (Request -> RequestHeaders
requestHeaders Request
request)
privateHit <- streamPrivateArtifact mode rt deps clientToken validators name file respond
case privateHit of
Just ResponseReceived
received -> do
IO () -> Handler ()
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (MetricsPort -> Decision -> IO ()
mpServeDecision (ServeRuntime -> MetricsPort
srMetrics ServeRuntime
rt) Decision
Metric.Admit)
ResponseReceived -> Handler ResponseReceived
forall a. a -> Handler a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ResponseReceived
received
Maybe ResponseReceived
Nothing -> ArtifactServe
-> ServeRuntime
-> MountRenderer
-> PackumentDeps
-> RequestHeaders
-> PackageName
-> Version
-> Text
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
servePublicArtifact ArtifactServe
mode ServeRuntime
rt MountRenderer
renderer PackumentDeps
deps RequestHeaders
validators PackageName
name Version
version Text
file Response -> IO ResponseReceived
respond
where
clientToken :: Maybe Secret
clientToken = Request -> Maybe Secret
forwardedToken Request
request
streamPrivateArtifact ::
ArtifactServe ->
ServeRuntime ->
PackumentDeps ->
Maybe Secret ->
RequestHeaders ->
PackageName ->
Text ->
(Response -> IO ResponseReceived) ->
Handler (Maybe ResponseReceived)
streamPrivateArtifact :: ArtifactServe
-> ServeRuntime
-> PackumentDeps
-> Maybe Secret
-> RequestHeaders
-> PackageName
-> Text
-> (Response -> IO ResponseReceived)
-> Handler (Maybe ResponseReceived)
streamPrivateArtifact ArtifactServe
mode ServeRuntime
rt PackumentDeps
deps Maybe Secret
token RequestHeaders
validators PackageName
name Text
file Response -> IO ResponseReceived
respond =
case Maybe Request
privateRequest of
Just Request
req -> IO (Maybe ResponseReceived) -> Handler (Maybe ResponseReceived)
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (ArtifactServe
-> Manager
-> Request
-> (Status -> Bool)
-> (Status -> RequestHeaders -> (Status, RequestHeaders))
-> (Response -> IO ResponseReceived)
-> IO (Maybe ResponseReceived)
relayUpstreamWhen ArtifactServe
mode (ServeRuntime -> Manager
srPrivateManager ServeRuntime
rt) Request
req Status -> Bool
acceptArtifact Status -> RequestHeaders -> (Status, RequestHeaders)
relayArtifact Response -> IO ResponseReceived
respond)
Maybe Request
Nothing -> Maybe ResponseReceived -> Handler (Maybe ResponseReceived)
forall a. a -> Handler a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe ResponseReceived
forall a. Maybe a
Nothing
where
privateRequest :: Maybe HTTP.Request
privateRequest :: Maybe Request
privateRequest =
if Origin -> PackumentDeps -> Text -> Text -> Bool
tarballHostHonoured Origin
TrustedOrigin PackumentDeps
deps Text
privateHost Text
privateHost
then RequestHeaders -> Request -> Request
withValidators RequestHeaders
validators (Request -> Request) -> (Request -> Request) -> Request -> Request
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ArtifactServe -> Request -> Request
withMethod ArtifactServe
mode (Request -> Request) -> Maybe Request -> Maybe Request
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Either UrlFormationError Request -> Maybe Request
forall l r. Either l r -> Maybe r
rightToMaybe (PackumentDeps
-> Limits
-> Manager
-> Text
-> Maybe Secret
-> PackageName
-> Text
-> Either UrlFormationError Request
pdBuildArtifactRequestByFile PackumentDeps
deps (PackumentDeps -> Limits
pdLimits PackumentDeps
deps) (ServeRuntime -> Manager
srPrivateManager ServeRuntime
rt) (PackumentDeps -> Text
pdPrivateBaseUrl PackumentDeps
deps) Maybe Secret
token PackageName
name Text
file)
else Maybe Request
forall a. Maybe a
Nothing
where
privateHost :: Text
privateHost = TarballHostGate -> Text
thgPrivateHost (PackumentDeps -> TarballHostGate
pdTarballHostGate PackumentDeps
deps)
servePublicArtifact ::
ArtifactServe ->
ServeRuntime ->
MountRenderer ->
PackumentDeps ->
RequestHeaders ->
PackageName ->
Version ->
Text ->
(Response -> IO ResponseReceived) ->
Handler ResponseReceived
servePublicArtifact :: ArtifactServe
-> ServeRuntime
-> MountRenderer
-> PackumentDeps
-> RequestHeaders
-> PackageName
-> Version
-> Text
-> (Response -> IO ResponseReceived)
-> Handler ResponseReceived
servePublicArtifact ArtifactServe
mode ServeRuntime
rt MountRenderer
renderer PackumentDeps
deps RequestHeaders
validators PackageName
name Version
version Text
file Response -> IO ResponseReceived
respond = do
let metrics :: MetricsPort
metrics = ServeRuntime -> MetricsPort
srMetrics ServeRuntime
rt
MetricsPort
-> ServeAdmission
-> Handler PublicArtifactGate
-> Handler (Maybe PublicArtifactGate)
forall (m :: * -> *) a.
MonadUnliftIO m =>
MetricsPort -> ServeAdmission -> m a -> m (Maybe a)
withServeAdmission MetricsPort
metrics (ServeRuntime -> ServeAdmission
srAdmission ServeRuntime
rt) (ServeRuntime
-> PackumentDeps
-> PackageName
-> Version
-> Text
-> Handler PublicArtifactGate
gatePublicVersion ServeRuntime
rt PackumentDeps
deps PackageName
name Version
version Text
file) Handler (Maybe PublicArtifactGate)
-> (Maybe PublicArtifactGate -> Handler ResponseReceived)
-> Handler ResponseReceived
forall a b. Handler a -> (a -> Handler b) -> Handler b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Just (Admitted Artifact
artifact) -> 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)
IO ResponseReceived -> Handler ResponseReceived
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (ArtifactServe
-> ServeRuntime
-> MountRenderer
-> PackumentDeps
-> RequestHeaders
-> PackageName
-> Version
-> Artifact
-> (Response -> IO ResponseReceived)
-> IO ResponseReceived
streamPublicArtifact ArtifactServe
mode ServeRuntime
rt MountRenderer
renderer PackumentDeps
deps RequestHeaders
validators PackageName
name Version
version Artifact
artifact Response -> IO ResponseReceived
respond)
Just (Refused ServeDecision
decision) -> 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 MetricsPort
metrics (ServeDecision -> Decision
serveDecisionClass ServeDecision
decision)
MetricsPort -> [ServeDecision] -> IO ()
recordDenials MetricsPort
metrics [ServeDecision
decision]
Response -> IO ResponseReceived
respond (MountRenderer
-> PackumentDeps -> ArtifactStatus -> ServeDecision -> Response
artifactError MountRenderer
renderer PackumentDeps
deps (ServeDecision -> ArtifactStatus
artifactStatus ServeDecision
decision) ServeDecision
decision)
Maybe PublicArtifactGate
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 MetricsPort
metrics Decision
Metric.Unavailable
Response -> IO ResponseReceived
respond (MountRenderer -> Response
serveOverloaded MountRenderer
renderer)
data PublicArtifactGate
=
Admitted Artifact
|
Refused ServeDecision
gatePublicVersion :: ServeRuntime -> PackumentDeps -> PackageName -> Version -> Text -> Handler PublicArtifactGate
gatePublicVersion :: ServeRuntime
-> PackumentDeps
-> PackageName
-> Version
-> Text
-> Handler PublicArtifactGate
gatePublicVersion ServeRuntime
rt PackumentDeps
deps PackageName
name Version
version Text
file = do
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)
eval <-
withPublicMetadataClient rt deps (pdPublicBaseUrl deps) $ \MetadataClient
client ->
IO VersionEvaluation -> IO VersionEvaluation
forall a. IO a -> IO a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (MetadataClient -> PackageName -> Version -> IO VersionEvaluation
fetchVersionDetails MetadataClient
client PackageName
name Version
version)
case eval of
VersionEvaluation
VersionMetadataUnavailable -> PublicArtifactGate -> Handler PublicArtifactGate
forall a. a -> Handler a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ServeDecision -> PublicArtifactGate
Refused ServeDecision
upstreamUnavailable)
VersionEvaluation
VersionMissing -> PublicArtifactGate -> Handler PublicArtifactGate
forall a. a -> Handler a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (ServeDecision -> PublicArtifactGate
Refused ServeDecision
versionAbsent)
VersionPresent PackageDetails
details ->
IO PublicArtifactGate -> Handler PublicArtifactGate
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO PublicArtifactGate -> Handler PublicArtifactGate)
-> IO PublicArtifactGate -> Handler PublicArtifactGate
forall a b. (a -> b) -> a -> b
$
TracingPort
-> forall a.
PackageName -> Version -> IO (a, ServeDecision) -> IO a
spanRuleEval (ServeRuntime -> TracingPort
srTracing ServeRuntime
rt) PackageName
name Version
version (IO (PublicArtifactGate, ServeDecision) -> IO PublicArtifactGate)
-> IO (PublicArtifactGate, ServeDecision) -> IO PublicArtifactGate
forall a b. (a -> b) -> a -> b
$ do
(gate, seconds) <- IO PublicArtifactGate -> IO (PublicArtifactGate, Double)
forall (m :: * -> *) a. MonadIO m => m a -> m (a, Double)
timedSeconds (EvalContext
-> PackumentDeps -> Text -> PackageDetails -> IO PublicArtifactGate
gateVersion EvalContext
evalCtx PackumentDeps
deps Text
file PackageDetails
details)
mpRuleEvalDuration (srMetrics rt) (evalTier (pdRules deps)) seconds
pure (gate, gateVerdict gate)
gateVerdict :: PublicArtifactGate -> ServeDecision
gateVerdict :: PublicArtifactGate -> ServeDecision
gateVerdict = \case
Admitted Artifact
_ -> ServeDecision
Admit
Refused ServeDecision
decision -> ServeDecision
decision
gateVersion :: EvalContext -> PackumentDeps -> Text -> PackageDetails -> IO PublicArtifactGate
gateVersion :: EvalContext
-> PackumentDeps -> Text -> PackageDetails -> IO PublicArtifactGate
gateVersion EvalContext
ctx PackumentDeps
deps Text
file PackageDetails
details = do
decision <- PackageDetails -> Decision -> ServeDecision
serveDecisionOf PackageDetails
details (Decision -> ServeDecision) -> IO Decision -> IO ServeDecision
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> EvalContext -> [PreparedRule] -> PackageDetails -> IO Decision
evalRules EvalContext
ctx (PackumentDeps -> [PreparedRule]
pdRules PackumentDeps
deps) PackageDetails
details
pure $ case decision of
ServeDecision
Admit -> PublicArtifactGate
-> (Artifact -> PublicArtifactGate)
-> Maybe Artifact
-> PublicArtifactGate
forall b a. b -> (a -> b) -> Maybe a -> b
maybe (ServeDecision -> PublicArtifactGate
Refused ServeDecision
versionAbsent) Artifact -> PublicArtifactGate
admitWithIntegrity (Text -> PackageDetails -> Maybe Artifact
artifactFor Text
file PackageDetails
details)
Reject Rejection
_ -> ServeDecision -> PublicArtifactGate
Refused ServeDecision
decision
where
admitWithIntegrity :: Artifact -> PublicArtifactGate
admitWithIntegrity :: Artifact -> PublicArtifactGate
admitWithIntegrity Artifact
artifact = case MinIntegrity -> NonEmpty Artifact -> VersionIntegrity
forall floor.
IntegrityFloor floor =>
floor -> NonEmpty Artifact -> VersionIntegrity
classifyArtifacts (PackumentDeps -> MinIntegrity
pdMinIntegrity PackumentDeps
deps) (Artifact
artifact Artifact -> [Artifact] -> NonEmpty Artifact
forall a. a -> [a] -> NonEmpty a
:| []) of
VersionIntegrity
MeetsFloor -> Artifact -> PublicArtifactGate
Admitted Artifact
artifact
VersionIntegrity
BelowFloor -> ServeDecision -> PublicArtifactGate
Refused ServeDecision
integrityBelowFloor
VersionIntegrity
NoIntegrity -> ServeDecision -> PublicArtifactGate
Refused ServeDecision
integrityMissing
upstreamUnavailable :: ServeDecision
upstreamUnavailable :: ServeDecision
upstreamUnavailable =
Rejection -> ServeDecision
Reject (RejectReason -> Text -> Rejection
Rejection (Transience -> RejectReason
Unavailable (Maybe RetryAfter -> Transience
WillResolve Maybe RetryAfter
forall a. Maybe a
Nothing)) Text
"the upstream registry was unavailable")
versionAbsent :: ServeDecision
versionAbsent :: ServeDecision
versionAbsent =
Rejection -> ServeDecision
Reject (RejectReason -> Text -> Rejection
Rejection (Transience -> RejectReason
Unavailable Transience
WontResolve) Text
"the requested version was not found upstream")
streamPublicArtifact ::
ArtifactServe ->
ServeRuntime ->
MountRenderer ->
PackumentDeps ->
RequestHeaders ->
PackageName ->
Version ->
Artifact ->
(Response -> IO ResponseReceived) ->
IO ResponseReceived
streamPublicArtifact :: ArtifactServe
-> ServeRuntime
-> MountRenderer
-> PackumentDeps
-> RequestHeaders
-> PackageName
-> Version
-> Artifact
-> (Response -> IO ResponseReceived)
-> IO ResponseReceived
streamPublicArtifact ArtifactServe
mode ServeRuntime
rt MountRenderer
renderer PackumentDeps
deps RequestHeaders
validators PackageName
name Version
version Artifact
artifact Response -> IO ResponseReceived
respond
| Bool -> Bool
not Bool
hostHonoured = Response -> IO ResponseReceived
respond Response
crossHostRefused
| Bool
otherwise = case Either UrlFormationError Request
publicRequest of
Left UrlFormationError
_ -> Response -> IO ResponseReceived
respond Response
internalArtifactError
Right Request
req ->
ArtifactServe
-> Manager
-> Request
-> (Status -> Bool)
-> (Status -> RequestHeaders -> (Status, RequestHeaders))
-> (Response -> IO ResponseReceived)
-> IO (Maybe ResponseReceived)
relayUpstreamWhen ArtifactServe
mode (ServeRuntime -> Manager
srPublicManager ServeRuntime
rt) Request
req (Bool -> Status -> Bool
forall a b. a -> b -> a
const Bool
True) Status -> RequestHeaders -> (Status, RequestHeaders)
relayArtifact Response -> IO ResponseReceived
respond IO (Maybe ResponseReceived)
-> (Maybe ResponseReceived -> IO ResponseReceived)
-> IO ResponseReceived
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Just ResponseReceived
received -> do
ArtifactServe -> IO () -> IO ()
enqueueOnFull ArtifactServe
mode (ServeRuntime
-> PackumentDeps -> PackageName -> Version -> Artifact -> IO ()
enqueueMirror ServeRuntime
rt PackumentDeps
deps PackageName
name Version
version Artifact
artifact)
ResponseReceived -> IO ResponseReceived
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ResponseReceived
received
Maybe ResponseReceived
Nothing -> Response -> IO ResponseReceived
respond (MountRenderer
-> PackumentDeps -> ArtifactStatus -> ServeDecision -> Response
artifactError MountRenderer
renderer PackumentDeps
deps (ServeDecision -> ArtifactStatus
artifactStatus ServeDecision
upstreamUnavailable) ServeDecision
upstreamUnavailable)
where
hostHonoured :: Bool
hostHonoured = Origin -> PackumentDeps -> Text -> Text -> Bool
tarballHostHonoured Origin
UntrustedOrigin PackumentDeps
deps (TarballHostGate -> Text
thgPublicHost (PackumentDeps -> TarballHostGate
pdTarballHostGate PackumentDeps
deps)) (Text -> Text
hostAddress (Artifact -> Text
artUrl Artifact
artifact))
publicRequest :: Either UrlFormationError Request
publicRequest = RequestHeaders -> Request -> Request
withValidators RequestHeaders
validators (Request -> Request) -> (Request -> Request) -> Request -> Request
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ArtifactServe -> Request -> Request
withMethod ArtifactServe
mode (Request -> Request)
-> Either UrlFormationError Request
-> Either UrlFormationError Request
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> PackumentDeps
-> Limits
-> Manager
-> Text
-> Maybe Secret
-> Text
-> Either UrlFormationError Request
pdBuildArtifactRequestByUrl PackumentDeps
deps (PackumentDeps -> Limits
pdLimits PackumentDeps
deps) (ServeRuntime -> Manager
srPublicManager ServeRuntime
rt) (PackumentDeps -> Text
pdPublicBaseUrl PackumentDeps
deps) Maybe Secret
forall a. Maybe a
Nothing (Artifact -> Text
artUrl Artifact
artifact)
withMethod :: ArtifactServe -> HTTP.Request -> HTTP.Request
withMethod :: ArtifactServe -> Request -> Request
withMethod = \case
ArtifactServe
ServeFull -> Request -> Request
forall a. a -> a
id
ArtifactServe
ServeHead -> \Request
req -> Request
req{HTTP.method = methodHead}
withValidators :: RequestHeaders -> HTTP.Request -> HTTP.Request
withValidators :: RequestHeaders -> Request -> Request
withValidators RequestHeaders
validators Request
req =
Request
req{HTTP.requestHeaders = validators <> HTTP.requestHeaders req}
acceptArtifact :: Status -> Bool
acceptArtifact :: Status -> Bool
acceptArtifact Status
s = Status -> Bool
statusIsSuccessful Status
s Bool -> Bool -> Bool
|| Status -> Bool
isNotModified Status
s
relayUpstreamWhen ::
ArtifactServe ->
Manager ->
HTTP.Request ->
(Status -> Bool) ->
(Status -> ResponseHeaders -> (Status, ResponseHeaders)) ->
(Response -> IO ResponseReceived) ->
IO (Maybe ResponseReceived)
relayUpstreamWhen :: ArtifactServe
-> Manager
-> Request
-> (Status -> Bool)
-> (Status -> RequestHeaders -> (Status, RequestHeaders))
-> (Response -> IO ResponseReceived)
-> IO (Maybe ResponseReceived)
relayUpstreamWhen = \case
ArtifactServe
ServeFull -> Manager
-> Request
-> (Status -> Bool)
-> (Status -> RequestHeaders -> (Status, RequestHeaders))
-> (Response -> IO ResponseReceived)
-> IO (Maybe ResponseReceived)
streamUpstreamWhen
ArtifactServe
ServeHead -> Manager
-> Request
-> (Status -> Bool)
-> (Status -> RequestHeaders -> (Status, RequestHeaders))
-> (Response -> IO ResponseReceived)
-> IO (Maybe ResponseReceived)
probeUpstreamWhen
enqueueOnFull :: ArtifactServe -> IO () -> IO ()
enqueueOnFull :: ArtifactServe -> IO () -> IO ()
enqueueOnFull ArtifactServe
mode IO ()
act = case ArtifactServe
mode of
ArtifactServe
ServeFull -> IO ()
act
ArtifactServe
ServeHead -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
enqueueMirror :: ServeRuntime -> PackumentDeps -> PackageName -> Version -> Artifact -> IO ()
enqueueMirror :: ServeRuntime
-> PackumentDeps -> PackageName -> Version -> Artifact -> IO ()
enqueueMirror ServeRuntime
rt PackumentDeps
deps PackageName
name Version
version Artifact
artifact =
Maybe (NonEmpty Hash) -> (NonEmpty Hash -> IO ()) -> IO ()
forall (f :: * -> *) a.
Applicative f =>
Maybe a -> (a -> f ()) -> f ()
whenJust ([Hash] -> Maybe (NonEmpty Hash)
forall a. [a] -> Maybe (NonEmpty a)
nonEmpty (Artifact -> [Hash]
artHashes Artifact
artifact)) ((NonEmpty Hash -> IO ()) -> IO ())
-> (NonEmpty Hash -> IO ()) -> IO ()
forall a b. (a -> b) -> a -> b
$ \NonEmpty Hash
hashes ->
IO (Either SomeException ()) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO (Either SomeException ()) -> IO ())
-> ((Maybe RemoteSpanContext -> IO (Either SomeException ()))
-> IO (Either SomeException ()))
-> (Maybe RemoteSpanContext -> IO (Either SomeException ()))
-> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. TracingPort
-> forall a.
PackageName
-> Version
-> Text
-> (a -> Maybe Text)
-> (Maybe RemoteSpanContext -> IO a)
-> IO a
spanMirrorEnqueue (ServeRuntime -> TracingPort
srTracing ServeRuntime
rt) PackageName
name Version
version (Artifact -> Text
artUrl Artifact
artifact) Either SomeException () -> Maybe Text
enqueueErrorDetail ((Maybe RemoteSpanContext -> IO (Either SomeException ()))
-> IO ())
-> (Maybe RemoteSpanContext -> IO (Either SomeException ()))
-> IO ()
forall a b. (a -> b) -> a -> b
$
NonEmpty Hash
-> Maybe RemoteSpanContext -> IO (Either SomeException ())
enqueueJob NonEmpty Hash
hashes
where
enqueueJob :: NonEmpty Hash
-> Maybe RemoteSpanContext -> IO (Either SomeException ())
enqueueJob NonEmpty Hash
hashes Maybe RemoteSpanContext
traceContext = do
enqueued <- IO () -> IO (Either SomeException ())
forall (m :: * -> *) a.
MonadUnliftIO m =>
m a -> m (Either SomeException a)
tryAny (MirrorQueue -> MirrorJob -> IO ()
enqueue (ServeRuntime -> MirrorQueue
srQueue ServeRuntime
rt) (NonEmpty Hash -> Maybe RemoteSpanContext -> MirrorJob
mirrorJob NonEmpty Hash
hashes Maybe RemoteSpanContext
traceContext))
either (const (mpMirrorEnqueueFailure (srMetrics rt))) (const (mpMirrorEnqueued (srMetrics rt))) enqueued
pure enqueued
mirrorJob :: NonEmpty Hash -> Maybe RemoteSpanContext -> MirrorJob
mirrorJob NonEmpty Hash
hashes Maybe RemoteSpanContext
traceContext =
MirrorJob
{ jobPackage :: PackageName
jobPackage = PackageName
name
, jobVersion :: Version
jobVersion = Version
version
, jobArtifactUrl :: Text
jobArtifactUrl = Artifact -> Text
artUrl Artifact
artifact
, jobMirrorTarget :: Text
jobMirrorTarget = PackumentDeps -> Text
pdMirrorTarget PackumentDeps
deps
, jobArtifact :: MirrorArtifact
jobArtifact =
MirrorArtifact
{ maFilename :: Text
maFilename = Artifact -> Text
artFilename Artifact
artifact
, maHashes :: NonEmpty Hash
maHashes = NonEmpty Hash
hashes
, maSize :: Maybe Int
maSize = Artifact -> Maybe Int
artSize Artifact
artifact
}
,
jobTraceContext :: Maybe RemoteSpanContext
jobTraceContext = Maybe RemoteSpanContext
traceContext
}
enqueueErrorDetail :: Either SomeException () -> Maybe Text
enqueueErrorDetail :: Either SomeException () -> Maybe Text
enqueueErrorDetail = (SomeException -> Maybe Text)
-> (() -> Maybe Text) -> Either SomeException () -> Maybe Text
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text)
-> (SomeException -> Text) -> SomeException -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. SomeException -> Text
enqueueFailureDetail) (Maybe Text -> () -> Maybe Text
forall a b. a -> b -> a
const Maybe Text
forall a. Maybe a
Nothing)
enqueueFailureDetail :: SomeException -> Text
enqueueFailureDetail :: SomeException -> Text
enqueueFailureDetail SomeException
e = Text
"mirror enqueue failed: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> String -> Text
forall a. ToText a => a -> Text
toText (SomeException -> String
forall e. Exception e => e -> String
displayException SomeException
e)
tarballHostHonoured :: Origin -> PackumentDeps -> Text -> Text -> Bool
tarballHostHonoured :: Origin -> PackumentDeps -> Text -> Text -> Bool
tarballHostHonoured Origin
origin PackumentDeps
deps =
Origin
-> TarballHostPolicy
-> LoweredHostSet
-> [IPRange]
-> Text
-> Text
-> Bool
tarballHostAllowed
Origin
origin
(PackumentDeps -> TarballHostPolicy
pdTarballHostPolicy PackumentDeps
deps)
(TarballHostGate -> LoweredHostSet
thgAllowlist (PackumentDeps -> TarballHostGate
pdTarballHostGate PackumentDeps
deps))
(PackumentDeps -> [IPRange]
pdAdditionalBlockedRanges PackumentDeps
deps)
artifactFor :: Text -> PackageDetails -> Maybe Artifact
artifactFor :: Text -> PackageDetails -> Maybe Artifact
artifactFor Text
file PackageDetails
details =
(Artifact -> Bool) -> NonEmpty Artifact -> Maybe Artifact
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Maybe a
find ((Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
file) (Text -> Bool) -> (Artifact -> Text) -> Artifact -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Artifact -> Text
artFilename) (PackageDetails -> NonEmpty Artifact
pkgArtifacts PackageDetails
details)
crossHostRefused :: Response
crossHostRefused :: Response
crossHostRefused =
Status -> RequestHeaders -> ByteString -> Response
responseLBS (Int -> ByteString -> Status
mkStatus Int
403 ByteString
"Forbidden") [(HeaderName
hContentType, ByteString
"application/json")] ByteString
"{\"error\":\"the upstream artifact host is not permitted by the tarball-host policy\"}"
relayArtifact :: Status -> ResponseHeaders -> (Status, ResponseHeaders)
relayArtifact :: Status -> RequestHeaders -> (Status, RequestHeaders)
relayArtifact Status
status RequestHeaders
headers =
(Status
status, (Header -> Bool) -> RequestHeaders -> RequestHeaders
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool) -> (Header -> Bool) -> Header -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. HeaderName -> Bool
forall {a}. (Eq a, IsString a) => a -> Bool
isHopByHop (HeaderName -> Bool) -> (Header -> HeaderName) -> Header -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Header -> HeaderName
forall a b. (a, b) -> a
fst) RequestHeaders
headers)
where
isHopByHop :: a -> Bool
isHopByHop a
name = a
name a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
"Transfer-Encoding" Bool -> Bool -> Bool
|| a
name a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
"Connection"
artifactError :: MountRenderer -> PackumentDeps -> ArtifactStatus -> ServeDecision -> Response
artifactError :: MountRenderer
-> PackumentDeps -> ArtifactStatus -> ServeDecision -> Response
artifactError MountRenderer
renderer PackumentDeps
deps ArtifactStatus
status ServeDecision
decision =
Status -> RequestHeaders -> RenderedBody -> Response
renderedResponse (ArtifactStatus -> Status
toStatus ArtifactStatus
actualStatus) RequestHeaders
retryHeaders (MountRenderer -> Maybe HelpMessage -> Text -> RenderedBody
renderError MountRenderer
renderer (PackumentDeps -> Maybe HelpMessage
pdHelp PackumentDeps
deps) Text
message)
where
retryHeaders :: ResponseHeaders
retryHeaders :: RequestHeaders
retryHeaders = case ArtifactStatus
actualStatus of
Unavailable' (Just (RetryAfter Int
secs)) -> [(HeaderName
hRetryAfter, Int -> ByteString
forall b a. (Show a, IsString b) => a -> b
show Int
secs)]
ArtifactStatus
_ -> []
actualStatus :: ArtifactStatus
actualStatus :: ArtifactStatus
actualStatus = if Bool
isVersionAbsent then ArtifactStatus
NotFound else ArtifactStatus
status
isVersionAbsent :: Bool
isVersionAbsent :: Bool
isVersionAbsent = case ServeDecision
decision of
Reject (Rejection (Unavailable Transience
WontResolve) Text
_) -> Bool
True
ServeDecision
_ -> Bool
False
toStatus :: ArtifactStatus -> Status
toStatus :: ArtifactStatus -> Status
toStatus ArtifactStatus
s = Int -> ByteString -> Status
mkStatus (ArtifactStatus -> Int
artifactStatusCode ArtifactStatus
s) (ArtifactStatus -> ByteString
statusReason ArtifactStatus
s)
statusReason :: ArtifactStatus -> ByteString
statusReason :: ArtifactStatus -> ByteString
statusReason = \case
ArtifactStatus
Ok -> ByteString
"OK"
ArtifactStatus
Forbidden -> ByteString
"Forbidden"
Unavailable'{} -> ByteString
"Service Unavailable"
ArtifactStatus
ServerError -> ByteString
"Internal Server Error"
ArtifactStatus
NotFound -> ByteString
"Not Found"
message :: Text
message :: Text
message = case ServeDecision
decision of
ServeDecision
Admit -> Text
"the artifact is available"
Reject Rejection
rej -> Rejection -> Text
rejectionMessage Rejection
rej
internalArtifactError :: Response
internalArtifactError :: Response
internalArtifactError =
Status -> RequestHeaders -> ByteString -> Response
responseLBS (Int -> ByteString -> Status
mkStatus Int
500 ByteString
"Internal Server Error") [(HeaderName
hContentType, ByteString
"application/json")] ByteString
"{\"error\":\"could not form the upstream artifact URL\"}"