module Ecluse.Core.Server.Pipeline.Tarball (
TarballReplies (..),
serveTarball,
headTarball,
) where
import Network.HTTP.Client qualified as HTTP
import Network.HTTP.Types (RequestHeaders, ResponseHeaders, Status, mkStatus)
import Network.Wai (Request, ResponseReceived, StreamingBody, requestHeaders)
import Ecluse.Core.Credential (Secret)
import Ecluse.Core.Cve (DbEtag)
import Ecluse.Core.Package (
Artifact (artFilename, artUrl),
PackageDetails,
PackageName,
)
import Ecluse.Core.Package.Admission (
ArtifactAdmission (
AdmissionAdmit,
AdmissionBelowFloor,
AdmissionDenied,
AdmissionFileAbsent,
AdmissionIntegrityMissing,
AdmissionUndecidable
),
admitArtifact,
)
import Ecluse.Core.Queue (
MirrorJob (MirrorJob, jobArtifactFilename, jobArtifactUrl, jobPackage, jobTraceContext, jobVersion),
QueueFault,
enqueue,
qfDetail,
)
import Ecluse.Core.Registry.Metadata (
VersionEvaluation (VersionMetadataUnavailable, VersionMissing, VersionPresent),
fetchVersionDetails,
)
import Ecluse.Core.Rules.Types (EvalContext, mkEvalContext)
import Ecluse.Core.Security (
Origin (TrustedOrigin, UntrustedOrigin),
hostPortAddress,
thgPrivateHostPort,
thgPublicHostPort,
)
import Ecluse.Core.Server.Admission (withServeAdmission)
import UnliftIO (withRunInIO)
import Ecluse.Core.Server.Conditional (forwardValidators)
import Ecluse.Core.Server.Context (
Handler,
MirrorServePlan (MirrorOnAdmit, NoMirrorWrite),
MountBinding (bindingPackumentDeps),
PackumentDeps (..),
ServeRuntime (..),
ctxMount,
ctxRuntime,
tarballHostHonoured,
)
import Ecluse.Core.Server.Path (Filename (Filename))
import Ecluse.Core.Server.Pipeline.Internal (
VersionVerdict (..),
evalTier,
logDenials,
recordDenials,
serveDecisionClass,
)
import Ecluse.Core.Server.Pipeline.Origin (withPublicMetadataClient)
import Ecluse.Core.Server.Pipeline.Shared
import Ecluse.Core.Server.Pipeline.Tarball.Relay (
ArtifactServe (ServeFull, ServeHead),
RelayVerdict (RelayedArtifact, RelayedNonSuccess, RelayedOddShape),
acceptArtifact,
observeRelayAnomaly,
relayArtifact,
relayUpstreamWhen,
relayVerdict,
withMethod,
withValidators,
)
import Ecluse.Core.Server.Response (
ArtifactStatus (Forbidden, NotFound, Ok, ServerError, Unavailable'),
RejectReason (Unavailable),
Rejection (Rejection, rejectionMessage),
RetryAfter (..),
ServeDecision (Admit, Reject),
Transience (WillResolve, WontResolve),
appendHelp,
artifactStatus,
artifactStatusCode,
serveDecisionOf,
)
import Ecluse.Core.Server.Stream (RelayResponder (RelayResponder))
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, renderVersion)
data TarballReplies response = TarballReplies
{ forall response.
TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
tarballError :: Status -> ResponseHeaders -> Text -> response
, forall response.
TarballReplies response
-> Status -> ResponseHeaders -> StreamingBody -> response
tarballStream :: Status -> ResponseHeaders -> StreamingBody -> response
, forall response.
TarballReplies response -> Status -> ResponseHeaders -> response
tarballEmpty :: Status -> ResponseHeaders -> response
}
serveTarball ::
TarballReplies response ->
PackageName ->
Version ->
Filename ->
Request ->
(response -> IO ResponseReceived) ->
Handler ResponseReceived
serveTarball :: forall response.
TarballReplies response
-> PackageName
-> Version
-> Filename
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
serveTarball = ArtifactServe
-> TarballReplies response
-> PackageName
-> Version
-> Filename
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
forall response.
ArtifactServe
-> TarballReplies response
-> PackageName
-> Version
-> Filename
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
tarballWith ArtifactServe
ServeFull
headTarball ::
TarballReplies response ->
PackageName ->
Version ->
Filename ->
Request ->
(response -> IO ResponseReceived) ->
Handler ResponseReceived
headTarball :: forall response.
TarballReplies response
-> PackageName
-> Version
-> Filename
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
headTarball = ArtifactServe
-> TarballReplies response
-> PackageName
-> Version
-> Filename
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
forall response.
ArtifactServe
-> TarballReplies response
-> PackageName
-> Version
-> Filename
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
tarballWith ArtifactServe
ServeHead
tarballWith ::
ArtifactServe ->
TarballReplies response ->
PackageName ->
Version ->
Filename ->
Request ->
(response -> IO ResponseReceived) ->
Handler ResponseReceived
tarballWith :: forall response.
ArtifactServe
-> TarballReplies response
-> PackageName
-> Version
-> Filename
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
tarballWith ArtifactServe
mode TarballReplies response
replies PackageName
name Version
version Filename
filename 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)
serveTarballWithDeps mode replies deps name version filename request respond
serveTarballWithDeps ::
ArtifactServe ->
TarballReplies response ->
PackumentDeps ->
PackageName ->
Version ->
Filename ->
Request ->
(response -> IO ResponseReceived) ->
Handler ResponseReceived
serveTarballWithDeps :: forall response.
ArtifactServe
-> TarballReplies response
-> PackumentDeps
-> PackageName
-> Version
-> Filename
-> Request
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
serveTarballWithDeps ArtifactServe
mode TarballReplies response
replies 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 (TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
forall response.
TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
tarballError TarballReplies response
replies (Int -> ByteString -> Status
mkStatus Int
401 ByteString
"Unauthorized") [] 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
let validators = ResponseHeaders -> ResponseHeaders
forwardValidators (Request -> ResponseHeaders
requestHeaders Request
request)
privateHit <- streamPrivateArtifact mode replies 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
-> TarballReplies response
-> ServeRuntime
-> PackumentDeps
-> ResponseHeaders
-> PackageName
-> Version
-> Text
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
forall response.
ArtifactServe
-> TarballReplies response
-> ServeRuntime
-> PackumentDeps
-> ResponseHeaders
-> PackageName
-> Version
-> Text
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
servePublicArtifact ArtifactServe
mode TarballReplies response
replies ServeRuntime
rt PackumentDeps
deps ResponseHeaders
validators PackageName
name Version
version Text
file response -> IO ResponseReceived
respond
where
clientToken :: Maybe Secret
clientToken = Request -> Maybe Secret
forwardedToken Request
request
streamPrivateArtifact ::
ArtifactServe ->
TarballReplies response ->
ServeRuntime ->
PackumentDeps ->
Maybe Secret ->
RequestHeaders ->
PackageName ->
Text ->
(response -> IO ResponseReceived) ->
Handler (Maybe ResponseReceived)
streamPrivateArtifact :: forall response.
ArtifactServe
-> TarballReplies response
-> ServeRuntime
-> PackumentDeps
-> Maybe Secret
-> ResponseHeaders
-> PackageName
-> Text
-> (response -> IO ResponseReceived)
-> Handler (Maybe ResponseReceived)
streamPrivateArtifact ArtifactServe
mode TarballReplies response
replies ServeRuntime
rt PackumentDeps
deps Maybe Secret
token ResponseHeaders
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 -> ResponseHeaders -> IO (Status, ResponseHeaders))
-> RelayResponder ResponseReceived
-> IO (Maybe ResponseReceived)
forall response.
ArtifactServe
-> Manager
-> Request
-> (Status -> Bool)
-> (Status -> ResponseHeaders -> IO (Status, ResponseHeaders))
-> RelayResponder response
-> IO (Maybe response)
relayUpstreamWhen
ArtifactServe
mode
(ServeRuntime -> Manager
srPrivateManager ServeRuntime
rt)
Request
req
Status -> Bool
acceptArtifact
(\Status
status ResponseHeaders
headers -> (Status, ResponseHeaders) -> IO (Status, ResponseHeaders)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Status -> ResponseHeaders -> (Status, ResponseHeaders)
relayArtifact Status
status ResponseHeaders
headers))
(TarballReplies response
-> (response -> IO ResponseReceived)
-> RelayResponder ResponseReceived
forall response received.
TarballReplies response
-> (response -> IO received) -> RelayResponder received
relayResponder TarballReplies response
replies 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 = case PackumentDeps -> Maybe Text
pdPrivateBaseUrl PackumentDeps
deps of
Maybe Text
Nothing -> Maybe Request
forall a. Maybe a
Nothing
Just Text
privateBase
| Origin -> PackumentDeps -> Maybe HostPort -> Maybe HostPort -> Bool
tarballHostHonoured Origin
TrustedOrigin PackumentDeps
deps Maybe HostPort
privateHostPort Maybe HostPort
privateHostPort ->
ResponseHeaders -> Request -> Request
withValidators ResponseHeaders
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) Text
privateBase Maybe Secret
token PackageName
name Text
file)
| Bool
otherwise -> Maybe Request
forall a. Maybe a
Nothing
where
privateHostPort :: Maybe HostPort
privateHostPort = TarballHostGate -> Maybe HostPort
thgPrivateHostPort (PackumentDeps -> TarballHostGate
pdTarballHostGate PackumentDeps
deps)
servePublicArtifact ::
ArtifactServe ->
TarballReplies response ->
ServeRuntime ->
PackumentDeps ->
RequestHeaders ->
PackageName ->
Version ->
Text ->
(response -> IO ResponseReceived) ->
Handler ResponseReceived
servePublicArtifact :: forall response.
ArtifactServe
-> TarballReplies response
-> ServeRuntime
-> PackumentDeps
-> ResponseHeaders
-> PackageName
-> Version
-> Text
-> (response -> IO ResponseReceived)
-> Handler ResponseReceived
servePublicArtifact ArtifactServe
mode TarballReplies response
replies ServeRuntime
rt PackumentDeps
deps ResponseHeaders
validators PackageName
name Version
version Text
file response -> IO ResponseReceived
respond = do
let metrics :: MetricsPort
metrics = ServeRuntime -> MetricsPort
srMetrics ServeRuntime
rt
advisoryEtag <- IO (Maybe DbEtag) -> Handler (Maybe DbEtag)
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (PackumentDeps -> IO (Maybe DbEtag)
pdAdvisoryEtag PackumentDeps
deps)
withServeAdmission metrics (srAdmission rt) (gatePublicVersion rt deps name version file advisoryEtag) >>= \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)
((forall a. Handler a -> IO a) -> IO ResponseReceived)
-> Handler ResponseReceived
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 ResponseReceived)
-> Handler ResponseReceived)
-> ((forall a. Handler a -> IO a) -> IO ResponseReceived)
-> Handler ResponseReceived
forall a b. (a -> b) -> a -> b
$ \forall a. Handler a -> IO a
runInIO ->
ArtifactServe
-> TarballReplies response
-> ServeRuntime
-> PackumentDeps
-> ResponseHeaders
-> PackageName
-> Version
-> Artifact
-> (RelayVerdict -> IO ())
-> (response -> IO ResponseReceived)
-> IO ResponseReceived
forall response.
ArtifactServe
-> TarballReplies response
-> ServeRuntime
-> PackumentDeps
-> ResponseHeaders
-> PackageName
-> Version
-> Artifact
-> (RelayVerdict -> IO ())
-> (response -> IO ResponseReceived)
-> IO ResponseReceived
streamPublicArtifact ArtifactServe
mode TarballReplies response
replies ServeRuntime
rt PackumentDeps
deps ResponseHeaders
validators PackageName
name Version
version Artifact
artifact (Handler () -> IO ()
forall a. Handler a -> IO a
runInIO (Handler () -> IO ())
-> (RelayVerdict -> Handler ()) -> RelayVerdict -> IO ()
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MetricsPort -> PackageName -> Version -> RelayVerdict -> Handler ()
forall (m :: * -> *).
KatipContext m =>
MetricsPort -> PackageName -> Version -> RelayVerdict -> m ()
observeRelayAnomaly MetricsPort
metrics PackageName
name Version
version) response -> IO ResponseReceived
respond
Just (Refused ServeDecision
decision) -> 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 (ServeDecision -> Decision
serveDecisionClass ServeDecision
decision))
PackageName -> Maybe DbEtag -> [VersionVerdict] -> Handler ()
forall (m :: * -> *).
KatipContext m =>
PackageName -> Maybe DbEtag -> [VersionVerdict] -> m ()
logDenials PackageName
name Maybe DbEtag
advisoryEtag [Text -> ServeDecision -> VersionVerdict
VersionVerdict (Version -> Text
renderVersion Version
version) ServeDecision
decision]
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
decision])
IO ResponseReceived -> Handler ResponseReceived
forall a. IO a -> Handler a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (response -> IO ResponseReceived
respond (TarballReplies response
-> PackumentDeps -> ArtifactStatus -> ServeDecision -> response
forall response.
TarballReplies response
-> PackumentDeps -> ArtifactStatus -> ServeDecision -> response
artifactError TarballReplies response
replies 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 (TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
forall response.
TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
tarballError TarballReplies response
replies Status
shedStatus [Header
shedRetryAfter] Text
"server is busy; retry later")
data PublicArtifactGate
=
Admitted Artifact
|
Refused ServeDecision
gatePublicVersion :: ServeRuntime -> PackumentDeps -> PackageName -> Version -> Text -> Maybe DbEtag -> Handler PublicArtifactGate
gatePublicVersion :: ServeRuntime
-> PackumentDeps
-> PackageName
-> Version
-> Text
-> Maybe DbEtag
-> Handler PublicArtifactGate
gatePublicVersion ServeRuntime
rt PackumentDeps
deps PackageName
name Version
version Text
file Maybe DbEtag
advisoryEtag = do
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) (Maybe DbEtag -> IO (Maybe DbEtag)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe DbEtag
advisoryEtag))
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
admission <- EvalContext
-> [PreparedRule]
-> MinIntegrity
-> Text
-> PackageDetails
-> IO ArtifactAdmission
admitArtifact EvalContext
ctx (PackumentDeps -> [PreparedRule]
pdRules PackumentDeps
deps) (PackumentDeps -> MinIntegrity
pdMinIntegrity PackumentDeps
deps) Text
file PackageDetails
details
pure $ case admission of
AdmissionAdmit Artifact
artifact NonEmpty Hash
_ -> Artifact -> PublicArtifactGate
Admitted Artifact
artifact
AdmissionDenied Decision
decision -> ServeDecision -> PublicArtifactGate
Refused (PackageDetails -> Decision -> ServeDecision
serveDecisionOf PackageDetails
details Decision
decision)
AdmissionUndecidable Decision
decision -> ServeDecision -> PublicArtifactGate
Refused (PackageDetails -> Decision -> ServeDecision
serveDecisionOf PackageDetails
details Decision
decision)
ArtifactAdmission
AdmissionFileAbsent -> ServeDecision -> PublicArtifactGate
Refused ServeDecision
versionAbsent
ArtifactAdmission
AdmissionBelowFloor -> ServeDecision -> PublicArtifactGate
Refused ServeDecision
integrityBelowFloor
ArtifactAdmission
AdmissionIntegrityMissing -> 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 ->
TarballReplies response ->
ServeRuntime ->
PackumentDeps ->
RequestHeaders ->
PackageName ->
Version ->
Artifact ->
(RelayVerdict -> IO ()) ->
(response -> IO ResponseReceived) ->
IO ResponseReceived
streamPublicArtifact :: forall response.
ArtifactServe
-> TarballReplies response
-> ServeRuntime
-> PackumentDeps
-> ResponseHeaders
-> PackageName
-> Version
-> Artifact
-> (RelayVerdict -> IO ())
-> (response -> IO ResponseReceived)
-> IO ResponseReceived
streamPublicArtifact ArtifactServe
mode TarballReplies response
replies ServeRuntime
rt PackumentDeps
deps ResponseHeaders
validators PackageName
name Version
version Artifact
artifact RelayVerdict -> IO ()
observeVerdict response -> IO ResponseReceived
respond
| Bool -> Bool
not Bool
hostHonoured = response -> IO ResponseReceived
respond (TarballReplies response -> response
forall response. TarballReplies response -> response
crossHostRefused TarballReplies response
replies)
| Bool
otherwise = case Either UrlFormationError Request
publicRequest of
Left UrlFormationError
_ -> response -> IO ResponseReceived
respond (TarballReplies response -> response
forall response. TarballReplies response -> response
internalArtifactError TarballReplies response
replies)
Right Request
req -> do
verdictRef <- Maybe RelayVerdict -> IO (IORef (Maybe RelayVerdict))
forall (m :: * -> *) a. MonadIO m => a -> m (IORef a)
newIORef Maybe RelayVerdict
forall a. Maybe a
Nothing
let verdictingRelay Status
status ResponseHeaders
headers = do
IORef (Maybe RelayVerdict) -> Maybe RelayVerdict -> m ()
forall (m :: * -> *) a. MonadIO m => IORef a -> a -> m ()
atomicWriteIORef IORef (Maybe RelayVerdict)
verdictRef (RelayVerdict -> Maybe RelayVerdict
forall a. a -> Maybe a
Just (Status -> ResponseHeaders -> RelayVerdict
relayVerdict Status
status ResponseHeaders
headers))
(Status, ResponseHeaders) -> m (Status, ResponseHeaders)
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Status -> ResponseHeaders -> (Status, ResponseHeaders)
relayArtifact Status
status ResponseHeaders
headers)
relayUpstreamWhen mode (srPublicManager rt) req (const True) verdictingRelay (relayResponder replies respond) >>= \case
Just ResponseReceived
received -> do
verdict <- RelayVerdict -> Maybe RelayVerdict -> RelayVerdict
forall a. a -> Maybe a -> a
fromMaybe (Text -> RelayVerdict
RelayedOddShape Text
"the relay committed without classifying (invariant)") (Maybe RelayVerdict -> RelayVerdict)
-> IO (Maybe RelayVerdict) -> IO RelayVerdict
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> IORef (Maybe RelayVerdict) -> IO (Maybe RelayVerdict)
forall (m :: * -> *) a. MonadIO m => IORef a -> m a
readIORef IORef (Maybe RelayVerdict)
verdictRef
observeVerdict verdict
case (verdict, pdMirror deps) of
(RelayVerdict
RelayedArtifact, MirrorOnAdmit Text
_) -> ArtifactServe -> IO () -> IO ()
enqueueOnFull ArtifactServe
mode (ServeRuntime
-> PackumentDeps -> PackageName -> Version -> Artifact -> IO ()
enqueueMirror ServeRuntime
rt PackumentDeps
deps PackageName
name Version
version Artifact
artifact)
(RelayVerdict
RelayedArtifact, MirrorServePlan
NoMirrorWrite) -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
(RelayedOddShape Text
_, MirrorServePlan
_) -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
(RelayedNonSuccess Status
_, MirrorServePlan
_) -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
pure received
Maybe ResponseReceived
Nothing -> response -> IO ResponseReceived
respond (TarballReplies response
-> PackumentDeps -> ArtifactStatus -> ServeDecision -> response
forall response.
TarballReplies response
-> PackumentDeps -> ArtifactStatus -> ServeDecision -> response
artifactError TarballReplies response
replies PackumentDeps
deps (ServeDecision -> ArtifactStatus
artifactStatus ServeDecision
upstreamUnavailable) ServeDecision
upstreamUnavailable)
where
hostHonoured :: Bool
hostHonoured = Origin -> PackumentDeps -> Maybe HostPort -> Maybe HostPort -> Bool
tarballHostHonoured Origin
UntrustedOrigin PackumentDeps
deps (TarballHostGate -> Maybe HostPort
thgPublicHostPort (PackumentDeps -> TarballHostGate
pdTarballHostGate PackumentDeps
deps)) (Text -> Maybe HostPort
hostPortAddress (Artifact -> Text
artUrl Artifact
artifact))
publicRequest :: Either UrlFormationError Request
publicRequest = ResponseHeaders -> Request -> Request
withValidators ResponseHeaders
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)
relayResponder :: TarballReplies response -> (response -> IO received) -> RelayResponder received
relayResponder :: forall response received.
TarballReplies response
-> (response -> IO received) -> RelayResponder received
relayResponder TarballReplies response
replies response -> IO received
respond =
(Status -> ResponseHeaders -> StreamingBody -> IO received)
-> (Status -> ResponseHeaders -> IO received)
-> RelayResponder received
forall response.
(Status -> ResponseHeaders -> StreamingBody -> IO response)
-> (Status -> ResponseHeaders -> IO response)
-> RelayResponder response
RelayResponder
(\Status
status ResponseHeaders
headers StreamingBody
body -> response -> IO received
respond (TarballReplies response
-> Status -> ResponseHeaders -> StreamingBody -> response
forall response.
TarballReplies response
-> Status -> ResponseHeaders -> StreamingBody -> response
tarballStream TarballReplies response
replies Status
status ResponseHeaders
headers StreamingBody
body))
(\Status
status ResponseHeaders
headers -> response -> IO received
respond (TarballReplies response -> Status -> ResponseHeaders -> response
forall response.
TarballReplies response -> Status -> ResponseHeaders -> response
tarballEmpty TarballReplies response
replies Status
status ResponseHeaders
headers))
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 =
case PackumentDeps -> Text -> Either Text RegistryUrl
pdEgressUrl PackumentDeps
deps (Artifact -> Text
artUrl Artifact
artifact) of
Left Text
_ -> MetricsPort -> IO ()
mpMirrorEnqueueFailure (ServeRuntime -> MetricsPort
srMetrics ServeRuntime
rt)
Right RegistryUrl
egressUrl ->
IO (Either QueueFault ()) -> IO ()
forall (f :: * -> *) a. Functor f => f a -> f ()
void (IO (Either QueueFault ()) -> IO ())
-> ((Maybe RemoteSpanContext -> IO (Either QueueFault ()))
-> IO (Either QueueFault ()))
-> (Maybe RemoteSpanContext -> IO (Either QueueFault ()))
-> 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 QueueFault () -> Maybe Text
enqueueErrorDetail ((Maybe RemoteSpanContext -> IO (Either QueueFault ())) -> IO ())
-> (Maybe RemoteSpanContext -> IO (Either QueueFault ())) -> IO ()
forall a b. (a -> b) -> a -> b
$
RegistryUrl -> Maybe RemoteSpanContext -> IO (Either QueueFault ())
enqueueJob RegistryUrl
egressUrl
where
enqueueJob :: RegistryUrl -> Maybe RemoteSpanContext -> IO (Either QueueFault ())
enqueueJob RegistryUrl
egressUrl Maybe RemoteSpanContext
traceContext = do
enqueued <- MirrorQueue -> MirrorJob -> IO (Either QueueFault ())
enqueue (ServeRuntime -> MirrorQueue
srQueue ServeRuntime
rt) (RegistryUrl -> Maybe RemoteSpanContext -> MirrorJob
mirrorJob RegistryUrl
egressUrl Maybe RemoteSpanContext
traceContext)
either (const (mpMirrorEnqueueFailure (srMetrics rt))) (const (mpMirrorEnqueued (srMetrics rt))) enqueued
pure enqueued
mirrorJob :: RegistryUrl -> Maybe RemoteSpanContext -> MirrorJob
mirrorJob RegistryUrl
egressUrl Maybe RemoteSpanContext
traceContext =
MirrorJob
{ jobPackage :: PackageName
jobPackage = PackageName
name
, jobVersion :: Version
jobVersion = Version
version
, jobArtifactUrl :: RegistryUrl
jobArtifactUrl = RegistryUrl
egressUrl
, jobArtifactFilename :: Text
jobArtifactFilename = Artifact -> Text
artFilename Artifact
artifact
,
jobTraceContext :: Maybe RemoteSpanContext
jobTraceContext = Maybe RemoteSpanContext
traceContext
}
enqueueErrorDetail :: Either QueueFault () -> Maybe Text
enqueueErrorDetail :: Either QueueFault () -> Maybe Text
enqueueErrorDetail = (QueueFault -> Maybe Text)
-> (() -> Maybe Text) -> Either QueueFault () -> 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)
-> (QueueFault -> Text) -> QueueFault -> Maybe Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. QueueFault -> Text
enqueueFailureDetail) (Maybe Text -> () -> Maybe Text
forall a b. a -> b -> a
const Maybe Text
forall a. Maybe a
Nothing)
enqueueFailureDetail :: QueueFault -> Text
enqueueFailureDetail :: QueueFault -> Text
enqueueFailureDetail QueueFault
fault = Text
"mirror enqueue failed: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> QueueFault -> Text
qfDetail QueueFault
fault
crossHostRefused :: TarballReplies response -> response
crossHostRefused :: forall response. TarballReplies response -> response
crossHostRefused TarballReplies response
replies =
TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
forall response.
TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
tarballError TarballReplies response
replies (Int -> ByteString -> Status
mkStatus Int
403 ByteString
"Forbidden") [] Text
"the upstream artifact host is not permitted by the tarball-host policy"
artifactError :: TarballReplies response -> PackumentDeps -> ArtifactStatus -> ServeDecision -> response
artifactError :: forall response.
TarballReplies response
-> PackumentDeps -> ArtifactStatus -> ServeDecision -> response
artifactError TarballReplies response
replies PackumentDeps
deps ArtifactStatus
status ServeDecision
decision =
TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
forall response.
TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
tarballError TarballReplies response
replies (ArtifactStatus -> Status
toStatus ArtifactStatus
actualStatus) ResponseHeaders
retryHeaders (Maybe HelpMessage -> Text -> Text
appendHelp (PackumentDeps -> Maybe HelpMessage
pdHelp PackumentDeps
deps) Text
message)
where
retryHeaders :: ResponseHeaders
retryHeaders :: ResponseHeaders
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 :: TarballReplies response -> response
internalArtifactError :: forall response. TarballReplies response -> response
internalArtifactError TarballReplies response
replies =
TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
forall response.
TarballReplies response
-> Status -> ResponseHeaders -> Text -> response
tarballError TarballReplies response
replies (Int -> ByteString -> Status
mkStatus Int
500 ByteString
"Internal Server Error") [] Text
"could not form the upstream artifact URL"