module Ecluse.Core.Server.Pipeline.Internal (
logDecodeFailure,
logNameMismatch,
admitByIntegrity,
packumentServeDecision,
serveDecisionClass,
denialLabels,
evalTier,
transienceCause,
recordDenials,
recordEffectfulFailures,
) where
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Katip (KatipContext, Severity (WarningS), katipAddContext, logFM, ls, sl)
import Ecluse.Core.Package (
PackageDetails (pkgArtifacts),
PackageInfo (infoDistTags, infoVersions),
PackageName,
renderPackageName,
)
import Ecluse.Core.Package.Integrity (
IntegrityFloor,
VersionIntegrity (BelowFloor, MeetsFloor, NoIntegrity),
classifyArtifacts,
)
import Ecluse.Core.Rules (PreparedRule (prepResilience))
import Ecluse.Core.Rules.Types (Decision (Undecidable))
import Ecluse.Core.Server.Response (
PackumentStatus (PackumentForbidden, PackumentOk),
RejectReason (BelowIntegrityFloor, ByPolicy, MissingIntegrity, Unavailable, UpstreamInvalid),
Rejection (Rejection),
RuleName (RuleName),
ServeDecision (Admit, Reject),
Transience (WillResolve, WontResolve),
packumentStatus,
)
import Ecluse.Core.Telemetry.Metrics qualified as Metric
import Ecluse.Core.Telemetry.Record (MetricsPort, mpRuleDenial, mpRuleEffectfulFailure)
import Ecluse.Core.Version (renderVersion)
pipelineInternalModule :: Text
pipelineInternalModule :: Text
pipelineInternalModule = Text
"Ecluse.Server.Pipeline.Internal"
logDecodeFailure :: (KatipContext m) => PackageName -> m ()
logDecodeFailure :: forall (m :: * -> *). KatipContext m => PackageName -> m ()
logDecodeFailure PackageName
name =
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
pipelineInternalModule 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)
message :: Text
message :: Text
message = Text
"refused an upstream metadata document: it did not decode into a usable packument"
logNameMismatch :: (KatipContext m) => PackageName -> Text -> Text -> m ()
logNameMismatch :: forall (m :: * -> *).
KatipContext m =>
PackageName -> Text -> Text -> m ()
logNameMismatch PackageName
requested Text
origin Text
reported =
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
pipelineInternalModule
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
requested)
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"origin" Text
origin
SimpleLogPayload -> SimpleLogPayload -> SimpleLogPayload
forall a. Semigroup a => a -> a -> a
<> Text -> Text -> SimpleLogPayload
forall a. ToJSON a => Text -> a -> SimpleLogPayload
sl Text
"upstreamName" Text
reported
message :: Text
message :: Text
message = Text
"dropped an upstream contribution: its packument self-reported a name for a different package"
admitByIntegrity ::
(IntegrityFloor floor) =>
floor ->
ServeDecision ->
ServeDecision ->
PackageInfo ->
(PackageInfo, [ServeDecision])
admitByIntegrity :: forall floor.
IntegrityFloor floor =>
floor
-> ServeDecision
-> ServeDecision
-> PackageInfo
-> (PackageInfo, [ServeDecision])
admitByIntegrity floor
floorSpec ServeDecision
belowFloorRefusal ServeDecision
missingRefusal PackageInfo
info =
( PackageInfo
info
{ infoVersions = Map.restrictKeys (infoVersions info) admissibleKeys
, infoDistTags = Map.filter ((`Set.member` admissibleKeys) . renderVersion) (infoDistTags info)
}
, [ServeDecision]
refusals
)
where
classified :: Map Text VersionIntegrity
classified :: Map Text VersionIntegrity
classified = (PackageDetails -> VersionIntegrity)
-> Map Text PackageDetails -> Map Text VersionIntegrity
forall a b k. (a -> b) -> Map k a -> Map k b
Map.map (floor -> NonEmpty Artifact -> VersionIntegrity
forall floor.
IntegrityFloor floor =>
floor -> NonEmpty Artifact -> VersionIntegrity
classifyArtifacts floor
floorSpec (NonEmpty Artifact -> VersionIntegrity)
-> (PackageDetails -> NonEmpty Artifact)
-> PackageDetails
-> VersionIntegrity
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageDetails -> NonEmpty Artifact
pkgArtifacts) (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
info)
admissibleKeys :: Set Text
admissibleKeys :: Set Text
admissibleKeys = Map Text VersionIntegrity -> Set Text
forall k a. Map k a -> Set k
Map.keysSet ((VersionIntegrity -> Bool)
-> Map Text VersionIntegrity -> Map Text VersionIntegrity
forall a k. (a -> Bool) -> Map k a -> Map k a
Map.filter (VersionIntegrity -> VersionIntegrity -> Bool
forall a. Eq a => a -> a -> Bool
== VersionIntegrity
MeetsFloor) Map Text VersionIntegrity
classified)
refusals :: [ServeDecision]
refusals :: [ServeDecision]
refusals = [ServeDecision]
below [ServeDecision] -> [ServeDecision] -> [ServeDecision]
forall a. Semigroup a => a -> a -> a
<> [ServeDecision]
missing
where
([ServeDecision]
below, [ServeDecision]
missing) = (VersionIntegrity
-> ([ServeDecision], [ServeDecision])
-> ([ServeDecision], [ServeDecision]))
-> ([ServeDecision], [ServeDecision])
-> Map Text VersionIntegrity
-> ([ServeDecision], [ServeDecision])
forall a b k. (a -> b -> b) -> b -> Map k a -> b
Map.foldr VersionIntegrity
-> ([ServeDecision], [ServeDecision])
-> ([ServeDecision], [ServeDecision])
bucket ([], []) Map Text VersionIntegrity
classified
bucket :: VersionIntegrity
-> ([ServeDecision], [ServeDecision])
-> ([ServeDecision], [ServeDecision])
bucket VersionIntegrity
BelowFloor ([ServeDecision]
b, [ServeDecision]
m) = (ServeDecision
belowFloorRefusal ServeDecision -> [ServeDecision] -> [ServeDecision]
forall a. a -> [a] -> [a]
: [ServeDecision]
b, [ServeDecision]
m)
bucket VersionIntegrity
NoIntegrity ([ServeDecision]
b, [ServeDecision]
m) = ([ServeDecision]
b, ServeDecision
missingRefusal ServeDecision -> [ServeDecision] -> [ServeDecision]
forall a. a -> [a] -> [a]
: [ServeDecision]
m)
bucket VersionIntegrity
MeetsFloor ([ServeDecision], [ServeDecision])
acc = ([ServeDecision], [ServeDecision])
acc
packumentServeDecision :: [ServeDecision] -> Metric.Decision
packumentServeDecision :: [ServeDecision] -> Decision
packumentServeDecision [ServeDecision]
decisions = case [ServeDecision] -> PackumentStatus
packumentStatus [ServeDecision]
decisions of
PackumentStatus
PackumentForbidden -> Decision
Metric.Deny
PackumentStatus
PackumentOk -> Decision
Metric.Admit
PackumentStatus
_ -> Decision
Metric.Unavailable
serveDecisionClass :: ServeDecision -> Metric.Decision
serveDecisionClass :: ServeDecision -> Decision
serveDecisionClass = \case
ServeDecision
Admit -> Decision
Metric.Admit
Reject (Rejection RejectReason
reason Text
_) -> case RejectReason
reason of
ByPolicy{} -> Decision
Metric.Deny
RejectReason
MissingIntegrity -> Decision
Metric.Deny
RejectReason
BelowIntegrityFloor -> Decision
Metric.Deny
Unavailable{} -> Decision
Metric.Unavailable
RejectReason
UpstreamInvalid -> Decision
Metric.Unavailable
denialLabels :: RejectReason -> (Maybe Text, Metric.ReasonClass)
denialLabels :: RejectReason -> (Maybe Text, ReasonClass)
denialLabels = \case
ByPolicy (RuleName Text
name) -> (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
name, ReasonClass
Metric.ReasonPolicy)
RejectReason
MissingIntegrity -> (Maybe Text
forall a. Maybe a
Nothing, ReasonClass
Metric.ReasonMissingIntegrity)
RejectReason
BelowIntegrityFloor -> (Maybe Text
forall a. Maybe a
Nothing, ReasonClass
Metric.ReasonMissingIntegrity)
Unavailable Transience
_ -> (Maybe Text
forall a. Maybe a
Nothing, ReasonClass
Metric.ReasonUnavailable)
RejectReason
UpstreamInvalid -> (Maybe Text
forall a. Maybe a
Nothing, ReasonClass
Metric.ReasonUnavailable)
evalTier :: [PreparedRule] -> Metric.Tier
evalTier :: [PreparedRule] -> Tier
evalTier [PreparedRule]
rules = if (PreparedRule -> Bool) -> [PreparedRule] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Maybe Resilience -> Bool
forall a. Maybe a -> Bool
isJust (Maybe Resilience -> Bool)
-> (PreparedRule -> Maybe Resilience) -> PreparedRule -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PreparedRule -> Maybe Resilience
prepResilience) [PreparedRule]
rules then Tier
Metric.Effectful else Tier
Metric.Structural
transienceCause :: Transience -> Metric.Cause
transienceCause :: Transience -> Cause
transienceCause = \case
WillResolve Maybe RetryAfter
_ -> Cause
Metric.Connection
Transience
WontResolve -> Cause
Metric.OtherCause
recordDenials :: MetricsPort -> [ServeDecision] -> IO ()
recordDenials :: MetricsPort -> [ServeDecision] -> IO ()
recordDenials MetricsPort
metrics = (ServeDecision -> IO ()) -> [ServeDecision] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ ServeDecision -> IO ()
recordOne
where
recordOne :: ServeDecision -> IO ()
recordOne :: ServeDecision -> IO ()
recordOne = \case
ServeDecision
Admit -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
Reject (Rejection RejectReason
reason Text
_) ->
let (Maybe Text
rule, ReasonClass
reasonClass) = RejectReason -> (Maybe Text, ReasonClass)
denialLabels RejectReason
reason
in MetricsPort -> Maybe Text -> ReasonClass -> IO ()
mpRuleDenial MetricsPort
metrics Maybe Text
rule ReasonClass
reasonClass
recordEffectfulFailures :: MetricsPort -> [Decision] -> IO ()
recordEffectfulFailures :: MetricsPort -> [Decision] -> IO ()
recordEffectfulFailures MetricsPort
metrics = (Decision -> IO ()) -> [Decision] -> IO ()
forall (t :: * -> *) (f :: * -> *) a b.
(Foldable t, Applicative f) =>
(a -> f b) -> t a -> f ()
traverse_ Decision -> IO ()
recordOne
where
recordOne :: Decision -> IO ()
recordOne :: Decision -> IO ()
recordOne = \case
Undecidable Transience
transience Text
_ -> MetricsPort -> Cause -> IO ()
mpRuleEffectfulFailure MetricsPort
metrics (Transience -> Cause
transienceCause Transience
transience)
Decision
_ -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass