module Ecluse.Core.Worker.Job (
JobOutcome (..),
RetryLeg (..),
mirrorLatest,
outcomeOfAdmission,
outcomeOfFetchFault,
processJob,
) where
import Data.Map.Strict qualified as Map
import Katip (Severity (DebugS, ErrorS, InfoS), katipAddNamespace, logFM, ls)
import UnliftIO (withRunInIO)
import Ecluse.Core.Ecosystem (ecosystemName)
import Ecluse.Core.Fault (tfCause)
import Ecluse.Core.Package (Artifact (artSize), Hash, pkgEcosystem)
import Ecluse.Core.Package.Admission (
ArtifactAdmission (
AdmissionAdmit,
AdmissionBelowFloor,
AdmissionDenied,
AdmissionFileAbsent,
AdmissionIntegrityMissing,
AdmissionUndecidable
),
admissionTransience,
admitArtifact,
)
import Ecluse.Core.Queue (MirrorJob (jobArtifactFilename, jobArtifactUrl, jobPackage, jobTraceContext, jobVersion))
import Ecluse.Core.Registry (
BodyOutcome (SuccessBody, UnreadStatus),
FetchFault (FetchBoundExceeded, FetchTransport, FetchUrlUnformable),
MirrorArtifact (MirrorArtifact, maFilename, maHashes, maSize),
ParseError (ParseError),
PublishFault (PublishFetch, PublishRejected, PublishSourceUnavailable),
renderUrlFormationError,
)
import Ecluse.Core.Registry.Adapter.Capability (AdapterArtifact (artifactByUrl))
import Ecluse.Core.Registry.CachedDocument (CachedDoc)
import Ecluse.Core.Registry.Metadata (
VersionDoc (vdDetails, vdRaw),
VersionEvaluation (VersionMetadataUnavailable, VersionMissing, VersionPresent),
versionTransience,
)
import Ecluse.Core.Registry.Publish (
MirrorPublish (mpProbeMetadata, mpPublishArtifact),
PublishPlan (PublishPlan, ppLatest, ppMetadata, ppVersion),
)
import Ecluse.Core.Rules.Types (Decision (Blocked, Undecidable), Transience (WillResolve, WontResolve), mkEvalContext)
import Ecluse.Core.Security (authorityLabel, hostPortAddress)
import Ecluse.Core.Security.Egress (registryUrlText)
import Ecluse.Core.Server.Path (Filename)
import Ecluse.Core.Telemetry.Record (WorkerMetricsPort (..), timedSeconds)
import Ecluse.Core.Telemetry.Span (JobSpanOutcome (JobSpanOutcome), WorkerTracingPort (..))
import Ecluse.Core.Version (Version, selectLatest)
import Ecluse.Core.Worker.Fetch (fetchArtifactBytes)
import Ecluse.Core.Worker.Integrity (IntegrityResult (..), verifyIntegrity)
import Ecluse.Core.Worker.Types
data JobOutcome
=
Succeeded
|
Dropped Text
|
SourceUnavailable Text
|
DeadLettered Text
|
Retried RetryLeg Text
deriving stock (JobOutcome -> JobOutcome -> Bool
(JobOutcome -> JobOutcome -> Bool)
-> (JobOutcome -> JobOutcome -> Bool) -> Eq JobOutcome
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: JobOutcome -> JobOutcome -> Bool
== :: JobOutcome -> JobOutcome -> Bool
$c/= :: JobOutcome -> JobOutcome -> Bool
/= :: JobOutcome -> JobOutcome -> Bool
Eq, Int -> JobOutcome -> ShowS
[JobOutcome] -> ShowS
JobOutcome -> String
(Int -> JobOutcome -> ShowS)
-> (JobOutcome -> String)
-> ([JobOutcome] -> ShowS)
-> Show JobOutcome
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> JobOutcome -> ShowS
showsPrec :: Int -> JobOutcome -> ShowS
$cshow :: JobOutcome -> String
show :: JobOutcome -> String
$cshowList :: [JobOutcome] -> ShowS
showList :: [JobOutcome] -> ShowS
Show)
data RetryLeg
=
BeforePublish
|
AfterPublish
deriving stock (RetryLeg -> RetryLeg -> Bool
(RetryLeg -> RetryLeg -> Bool)
-> (RetryLeg -> RetryLeg -> Bool) -> Eq RetryLeg
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: RetryLeg -> RetryLeg -> Bool
== :: RetryLeg -> RetryLeg -> Bool
$c/= :: RetryLeg -> RetryLeg -> Bool
/= :: RetryLeg -> RetryLeg -> Bool
Eq, Int -> RetryLeg -> ShowS
[RetryLeg] -> ShowS
RetryLeg -> String
(Int -> RetryLeg -> ShowS)
-> (RetryLeg -> String) -> ([RetryLeg] -> ShowS) -> Show RetryLeg
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> RetryLeg -> ShowS
showsPrec :: Int -> RetryLeg -> ShowS
$cshow :: RetryLeg -> String
show :: RetryLeg -> String
$cshowList :: [RetryLeg] -> ShowS
showList :: [RetryLeg] -> ShowS
Show)
jobSpanOutcome :: JobOutcome -> JobSpanOutcome
jobSpanOutcome :: JobOutcome -> JobSpanOutcome
jobSpanOutcome = \case
JobOutcome
Succeeded -> Text -> Maybe Text -> JobSpanOutcome
JobSpanOutcome Text
"succeeded" Maybe Text
forall a. Maybe a
Nothing
Dropped Text
reason -> Text -> Maybe Text -> JobSpanOutcome
JobSpanOutcome Text
"dropped" (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
reason)
SourceUnavailable Text
reason -> Text -> Maybe Text -> JobSpanOutcome
JobSpanOutcome Text
"source-unavailable" (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
reason)
DeadLettered Text
reason -> Text -> Maybe Text -> JobSpanOutcome
JobSpanOutcome Text
"dead-lettered" (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
reason)
Retried RetryLeg
_ Text
reason -> Text -> Maybe Text -> JobSpanOutcome
JobSpanOutcome Text
"retried" (Text -> Maybe Text
forall a. a -> Maybe a
Just Text
reason)
processJob :: MirrorJob -> WorkerM JobOutcome
processJob :: MirrorJob -> WorkerM JobOutcome
processJob MirrorJob
job = Namespace -> WorkerM JobOutcome -> WorkerM JobOutcome
forall (m :: * -> *) a. KatipContext m => Namespace -> m a -> m a
katipAddNamespace Namespace
"job" (WorkerM JobOutcome -> WorkerM JobOutcome)
-> WorkerM JobOutcome -> WorkerM JobOutcome
forall a b. (a -> b) -> a -> b
$ do
Severity -> LogStr -> WorkerM ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
DebugS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (Text
"starting mirror job for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job))
tracing <- (WorkerRuntime -> WorkerTracingPort) -> WorkerM WorkerTracingPort
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks WorkerRuntime -> WorkerTracingPort
wrTracing
runtime <- ask
withRunInIO $ \forall a. WorkerM a -> IO a
runInIO ->
WorkerTracingPort
-> forall a.
PackageName
-> Version
-> Maybe RemoteSpanContext
-> (a -> JobSpanOutcome)
-> IO a
-> IO a
wtpMirrorJobSpan WorkerTracingPort
tracing (MirrorJob -> PackageName
jobPackage MirrorJob
job) (MirrorJob -> Version
jobVersion MirrorJob
job) (MirrorJob -> Maybe RemoteSpanContext
jobTraceContext MirrorJob
job) JobOutcome -> JobSpanOutcome
jobSpanOutcome (IO JobOutcome -> IO JobOutcome) -> IO JobOutcome -> IO JobOutcome
forall a b. (a -> b) -> a -> b
$
WorkerM JobOutcome -> IO JobOutcome
forall a. WorkerM a -> IO a
runInIO (WorkerM JobOutcome -> IO JobOutcome)
-> WorkerM JobOutcome -> IO JobOutcome
forall a b. (a -> b) -> a -> b
$
WorkerRuntime
-> forall (m :: * -> *) a.
(KatipContext m, MonadIO m) =>
m a -> m a
wrInjectTraceContext WorkerRuntime
runtime (MirrorJob -> WorkerM JobOutcome
reevaluateThenMirror MirrorJob
job)
reevaluateThenMirror :: MirrorJob -> WorkerM JobOutcome
reevaluateThenMirror :: MirrorJob -> WorkerM JobOutcome
reevaluateThenMirror MirrorJob
job = do
policies <- (WorkerRuntime -> WorkerPolicies) -> WorkerM WorkerPolicies
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks WorkerRuntime -> WorkerPolicies
wrPolicies
case Map.lookup (pkgEcosystem (jobPackage job)) policies of
Maybe WorkerPolicy
Nothing -> JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> JobOutcome
Dropped (MirrorJob -> Text
noPolicyReason MirrorJob
job))
Just WorkerPolicy
policy
| WorkerPolicy -> PackageName -> Bool
wpFirstParty WorkerPolicy
policy (MirrorJob -> PackageName
jobPackage MirrorJob
job) -> JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> JobOutcome
Dropped (MirrorJob -> Text
firstPartyReason MirrorJob
job))
| Bool
otherwise -> WorkerPolicy -> MirrorJob -> WorkerM JobOutcome
mirrorUnlessPresent WorkerPolicy
policy MirrorJob
job
noPolicyReason :: MirrorJob -> Text
noPolicyReason :: MirrorJob -> Text
noPolicyReason MirrorJob
job =
Text
"no rule policy is configured for the "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Ecosystem -> Text
ecosystemName (PackageName -> Ecosystem
pkgEcosystem (MirrorJob -> PackageName
jobPackage MirrorJob
job))
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ecosystem; refusing to mirror "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job
firstPartyReason :: MirrorJob -> Text
firstPartyReason :: MirrorJob -> Text
firstPartyReason MirrorJob
job =
Text
"this deployment owns the namespace of "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"; refusing to mirror public content under a first-party name"
mirrorUnlessPresent :: WorkerPolicy -> MirrorJob -> WorkerM JobOutcome
mirrorUnlessPresent :: WorkerPolicy -> MirrorJob -> WorkerM JobOutcome
mirrorUnlessPresent WorkerPolicy
policy MirrorJob
job =
WorkerPolicy -> MirrorJob -> WorkerM (Either JobOutcome [Version])
probeInventory WorkerPolicy
policy MirrorJob
job WorkerM (Either JobOutcome [Version])
-> (Either JobOutcome [Version] -> WorkerM JobOutcome)
-> WorkerM JobOutcome
forall a b. WorkerM a -> (a -> WorkerM b) -> WorkerM b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Left JobOutcome
outcome -> JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure JobOutcome
outcome
Right [Version]
inventory
| MirrorJob -> Version
jobVersion MirrorJob
job Version -> [Version] -> Bool
forall (f :: * -> *) a.
(Foldable f, DisallowElem f, Eq a) =>
a -> f a -> Bool
`elem` [Version]
inventory -> do
Severity -> LogStr -> WorkerM ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
InfoS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (Text
"already present at the mirror target, acking without re-publish: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job))
JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure JobOutcome
Succeeded
| Bool
otherwise -> WorkerPolicy -> MirrorJob -> WorkerM (Either JobOutcome Readmitted)
reevaluatePolicy WorkerPolicy
policy MirrorJob
job WorkerM (Either JobOutcome Readmitted)
-> (Either JobOutcome Readmitted -> WorkerM JobOutcome)
-> WorkerM JobOutcome
forall a b. WorkerM a -> (a -> WorkerM b) -> WorkerM b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (JobOutcome -> WorkerM JobOutcome)
-> (Readmitted -> WorkerM JobOutcome)
-> Either JobOutcome Readmitted
-> WorkerM JobOutcome
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (WorkerPolicy
-> MirrorJob -> [Version] -> Readmitted -> WorkerM JobOutcome
publishAdmitted WorkerPolicy
policy MirrorJob
job [Version]
inventory)
probeInventory :: WorkerPolicy -> MirrorJob -> WorkerM (Either JobOutcome [Version])
probeInventory :: WorkerPolicy -> MirrorJob -> WorkerM (Either JobOutcome [Version])
probeInventory WorkerPolicy
policy MirrorJob
job = do
probed <- IO (Either FetchFault (BodyOutcome (Either ParseError [Version])))
-> WorkerM
(Either FetchFault (BodyOutcome (Either ParseError [Version])))
forall a. IO a -> WorkerM a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (MirrorPublish
-> PackageName
-> IO
(Either FetchFault (BodyOutcome (Either ParseError [Version])))
mpProbeMetadata (WorkerPolicy -> MirrorPublish
wpPublish WorkerPolicy
policy) (MirrorJob -> PackageName
jobPackage MirrorJob
job))
pure $ case probed of
Left FetchFault
fault -> JobOutcome -> Either JobOutcome [Version]
forall a b. a -> Either a b
Left (RetryLeg -> (FetchFault -> Text) -> FetchFault -> JobOutcome
outcomeOfFetchFault RetryLeg
BeforePublish (MirrorJob -> FetchFault -> Text
probeFaultReason MirrorJob
job) FetchFault
fault)
Right (SuccessBody Int
_ (Left (ParseError Text
detail))) -> JobOutcome -> Either JobOutcome [Version]
forall a b. a -> Either a b
Left (RetryLeg -> Text -> JobOutcome
Retried RetryLeg
BeforePublish (MirrorJob -> Text -> Text
probeParseReason MirrorJob
job Text
detail))
Right (SuccessBody Int
_ (Right [Version]
versions)) -> [Version] -> Either JobOutcome [Version]
forall a b. b -> Either a b
Right [Version]
versions
Right (UnreadStatus Int
404) -> [Version] -> Either JobOutcome [Version]
forall a b. b -> Either a b
Right []
Right (UnreadStatus Int
code) -> JobOutcome -> Either JobOutcome [Version]
forall a b. a -> Either a b
Left (RetryLeg -> Text -> JobOutcome
Retried RetryLeg
BeforePublish (MirrorJob -> Int -> Text
probeStatusReason MirrorJob
job Int
code))
probeFaultReason :: MirrorJob -> FetchFault -> Text
probeFaultReason :: MirrorJob -> FetchFault -> Text
probeFaultReason MirrorJob
job = \case
FetchUrlUnformable UrlFormationError
urlErr -> Text
"unformable mirror probe URL: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> UrlFormationError -> Text
renderUrlFormationError UrlFormationError
urlErr
FetchBoundExceeded LimitError
limitErr -> Text
"the mirror target's metadata exceeded the response bound: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> LimitError -> Text
forall b a. (Show a, IsString b) => a -> b
show LimitError
limitErr
FetchTransport TransportFault
fault -> Text
"mirror inventory probe for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" failed: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> TransportCause -> Text
forall b a. (Show a, IsString b) => a -> b
show (TransportFault -> TransportCause
tfCause TransportFault
fault)
probeStatusReason :: MirrorJob -> Int -> Text
probeStatusReason :: MirrorJob -> Int -> Text
probeStatusReason MirrorJob
job Int
code =
Text
"the mirror target answered HTTP "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
code
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" for the inventory probe of "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"; refusing to publish a release tag chosen without it"
probeParseReason :: MirrorJob -> Text -> Text
probeParseReason :: MirrorJob -> Text -> Text
probeParseReason MirrorJob
job Text
detail =
Text
"could not read the mirror target's inventory for "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ("
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
detail
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"); refusing to publish a release tag chosen without it"
data Readmitted = Readmitted
{ Readmitted -> MirrorArtifact
raArtifact :: MirrorArtifact
, Readmitted -> Maybe Version
raUpstreamLatest :: Maybe Version
, Readmitted -> CachedDoc
raMetadata :: CachedDoc
}
reevaluatePolicy :: WorkerPolicy -> MirrorJob -> WorkerM (Either JobOutcome Readmitted)
reevaluatePolicy :: WorkerPolicy -> MirrorJob -> WorkerM (Either JobOutcome Readmitted)
reevaluatePolicy WorkerPolicy
policy MirrorJob
job
| Bool -> Bool
not (WorkerPolicy -> Maybe HostPort -> Bool
wpArtifactHostHonoured WorkerPolicy
policy (Text -> Maybe HostPort
hostPortAddress (RegistryUrl -> Text
registryUrlText (MirrorJob -> RegistryUrl
jobArtifactUrl MirrorJob
job)))) =
Either JobOutcome Readmitted
-> WorkerM (Either JobOutcome Readmitted)
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (JobOutcome -> Either JobOutcome Readmitted
forall a b. a -> Either a b
Left (Text -> JobOutcome
Dropped (MirrorJob -> Text
artifactHostReason MirrorJob
job)))
| Bool
otherwise =
IO VersionEvaluation -> WorkerM VersionEvaluation
forall a. IO a -> WorkerM a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (WorkerPolicy -> PackageName -> Version -> IO VersionEvaluation
wpResolveVersion WorkerPolicy
policy (MirrorJob -> PackageName
jobPackage MirrorJob
job) (MirrorJob -> Version
jobVersion MirrorJob
job)) WorkerM VersionEvaluation
-> (VersionEvaluation -> WorkerM (Either JobOutcome Readmitted))
-> WorkerM (Either JobOutcome Readmitted)
forall a b. WorkerM a -> (a -> WorkerM b) -> WorkerM b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= WorkerPolicy
-> MirrorJob
-> VersionEvaluation
-> WorkerM (Either JobOutcome Readmitted)
admitEvaluation WorkerPolicy
policy MirrorJob
job
artifactHostReason :: MirrorJob -> Text
artifactHostReason :: MirrorJob -> Text
artifactHostReason MirrorJob
job =
Text
"the tarball-host policy refuses the artifact host of "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" ("
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
jobArtifactAuthority MirrorJob
job
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"); refusing to fetch or mirror it"
admitEvaluation :: WorkerPolicy -> MirrorJob -> VersionEvaluation -> WorkerM (Either JobOutcome Readmitted)
admitEvaluation :: WorkerPolicy
-> MirrorJob
-> VersionEvaluation
-> WorkerM (Either JobOutcome Readmitted)
admitEvaluation WorkerPolicy
policy MirrorJob
job VersionEvaluation
evaluation = case VersionEvaluation
evaluation of
VersionEvaluation
VersionMetadataUnavailable ->
Either JobOutcome Readmitted
-> WorkerM (Either JobOutcome Readmitted)
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (JobOutcome -> Either JobOutcome Readmitted
forall a b. a -> Either a b
Left (Text -> JobOutcome
unresolved (Text
"could not re-fetch metadata to re-evaluate current policy for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job)))
VersionEvaluation
VersionMissing ->
Either JobOutcome Readmitted
-> WorkerM (Either JobOutcome Readmitted)
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (JobOutcome -> Either JobOutcome Readmitted
forall a b. a -> Either a b
Left (Text -> JobOutcome
unresolved (Text
"the public upstream no longer offers " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"; refusing to mirror a withdrawn version")))
VersionPresent VersionDoc
doc Maybe Version
upstreamLatest -> do
ctx <- IO EvalContext -> WorkerM EvalContext
forall a. IO a -> WorkerM a
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO UTCTime -> IO (Maybe DbEtag) -> IO EvalContext
mkEvalContext (WorkerPolicy -> IO UTCTime
wpNow WorkerPolicy
policy) (Maybe DbEtag -> IO (Maybe DbEtag)
forall a. a -> IO a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Maybe DbEtag
forall a. Maybe a
Nothing))
admission <- liftIO (admitArtifact ctx (wpRules policy) (wpMinIntegrity policy) (jobArtifactFilename job) (vdDetails doc))
pure $ do
artifact <- outcomeOfAdmission job admission
raw <- maybeToRight (SourceUnavailable (sourceUnavailableReason job "the resolver carried none")) (vdRaw doc)
pure Readmitted{raArtifact = artifact, raUpstreamLatest = upstreamLatest, raMetadata = raw}
where
unresolved :: Text -> JobOutcome
unresolved = Maybe Transience -> Text -> JobOutcome
retryOrDrop (VersionEvaluation -> Maybe Transience
versionTransience VersionEvaluation
evaluation)
sourceUnavailableReason :: MirrorJob -> Text -> Text
sourceUnavailableReason :: MirrorJob -> Text -> Text
sourceUnavailableReason MirrorJob
job Text
detail =
Text
"the source version object for "
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" was unavailable ("
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
detail
Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"); refusing to mirror a reduced manifest"
outcomeOfAdmission :: MirrorJob -> ArtifactAdmission -> Either JobOutcome MirrorArtifact
outcomeOfAdmission :: MirrorJob -> ArtifactAdmission -> Either JobOutcome MirrorArtifact
outcomeOfAdmission MirrorJob
job ArtifactAdmission
admission = case ArtifactAdmission
admission of
AdmissionAdmit Filename
filename Artifact
artifact NonEmpty Hash
digests -> MirrorArtifact -> Either JobOutcome MirrorArtifact
forall a b. b -> Either a b
Right (Filename -> Artifact -> NonEmpty Hash -> MirrorArtifact
readmittedDescriptor Filename
filename Artifact
artifact NonEmpty Hash
digests)
AdmissionDenied (Blocked Text
ruleName Maybe DbEtag
_ Text
reason) ->
Text -> Either JobOutcome MirrorArtifact
refused (Text
"current policy denies " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": blocked by " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
ruleName Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" (" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
reason Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
")")
AdmissionDenied Decision
_ ->
Text -> Either JobOutcome MirrorArtifact
refused (Text
"current policy denies " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": no rule admits it")
AdmissionUndecidable (Undecidable Transience
_ Text
reason) ->
Text -> Either JobOutcome MirrorArtifact
refused (Text
"current policy could not be evaluated for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
reason)
AdmissionUndecidable Decision
_ ->
Text -> Either JobOutcome MirrorArtifact
refused (Text
"current policy could not be evaluated for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job)
ArtifactAdmission
AdmissionFileAbsent ->
Text -> Either JobOutcome MirrorArtifact
refused (Text
"the public upstream no longer offers the admitted artifact file of " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
"; refusing to mirror a withdrawn artifact")
ArtifactAdmission
AdmissionBelowFloor ->
Text -> Either JobOutcome MirrorArtifact
refused (Text
"current admission policy refuses " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": its strongest integrity digest is below the configured public floor")
ArtifactAdmission
AdmissionIntegrityMissing ->
Text -> Either JobOutcome MirrorArtifact
refused (Text
"current admission policy refuses " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": it no longer carries any integrity digest")
where
refused :: Text -> Either JobOutcome MirrorArtifact
refused :: Text -> Either JobOutcome MirrorArtifact
refused = JobOutcome -> Either JobOutcome MirrorArtifact
forall a b. a -> Either a b
Left (JobOutcome -> Either JobOutcome MirrorArtifact)
-> (Text -> JobOutcome) -> Text -> Either JobOutcome MirrorArtifact
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Maybe Transience -> Text -> JobOutcome
retryOrDrop (ArtifactAdmission -> Maybe Transience
admissionTransience ArtifactAdmission
admission)
retryOrDrop :: Maybe Transience -> Text -> JobOutcome
retryOrDrop :: Maybe Transience -> Text -> JobOutcome
retryOrDrop Maybe Transience
transience Text
reason = case Maybe Transience
transience of
Just (WillResolve Maybe RetryAfter
_) -> RetryLeg -> Text -> JobOutcome
Retried RetryLeg
BeforePublish Text
reason
Just Transience
WontResolve -> Text -> JobOutcome
Dropped Text
reason
Maybe Transience
Nothing -> Text -> JobOutcome
Dropped Text
reason
readmittedDescriptor :: Filename -> Artifact -> NonEmpty Hash -> MirrorArtifact
readmittedDescriptor :: Filename -> Artifact -> NonEmpty Hash -> MirrorArtifact
readmittedDescriptor Filename
filename Artifact
artifact NonEmpty Hash
digests =
MirrorArtifact
{ maFilename :: Filename
maFilename = Filename
filename
, maHashes :: NonEmpty Hash
maHashes = NonEmpty Hash
digests
, maSize :: Maybe Int
maSize = Artifact -> Maybe Int
artSize Artifact
artifact
}
outcomeOfFetchFault :: RetryLeg -> (FetchFault -> Text) -> FetchFault -> JobOutcome
outcomeOfFetchFault :: RetryLeg -> (FetchFault -> Text) -> FetchFault -> JobOutcome
outcomeOfFetchFault RetryLeg
leg FetchFault -> Text
render FetchFault
fault = Text -> JobOutcome
verdict (FetchFault -> Text
render FetchFault
fault)
where
verdict :: Text -> JobOutcome
verdict = case FetchFault
fault of
FetchUrlUnformable UrlFormationError
_ -> Text -> JobOutcome
Dropped
FetchBoundExceeded LimitError
_ -> Text -> JobOutcome
DeadLettered
FetchTransport TransportFault
_ -> RetryLeg -> Text -> JobOutcome
Retried RetryLeg
leg
mirrorLatest :: Maybe Version -> [Version] -> Version -> Version
mirrorLatest :: Maybe Version -> [Version] -> Version -> Version
mirrorLatest Maybe Version
upstreamLatest [Version]
inventory Version
published =
Version -> Maybe Version -> Version
forall a. a -> Maybe a -> a
fromMaybe Version
published (Maybe Version -> [Version] -> Maybe Version
selectLatest Maybe Version
upstreamLatest (Version
published Version -> [Version] -> [Version]
forall a. a -> [a] -> [a]
: [Version]
inventory))
publishAdmitted :: WorkerPolicy -> MirrorJob -> [Version] -> Readmitted -> WorkerM JobOutcome
publishAdmitted :: WorkerPolicy
-> MirrorJob -> [Version] -> Readmitted -> WorkerM JobOutcome
publishAdmitted WorkerPolicy
policy MirrorJob
job [Version]
inventory Readmitted
readmitted =
WorkerPolicy
-> MirrorJob -> PublishPlan -> MirrorArtifact -> WorkerM JobOutcome
mirrorArtifact WorkerPolicy
policy MirrorJob
job PublishPlan
plan (Readmitted -> MirrorArtifact
raArtifact Readmitted
readmitted)
where
plan :: PublishPlan
plan =
PublishPlan
{ ppVersion :: Version
ppVersion = MirrorJob -> Version
jobVersion MirrorJob
job
, ppLatest :: Version
ppLatest = Maybe Version -> [Version] -> Version -> Version
mirrorLatest (Readmitted -> Maybe Version
raUpstreamLatest Readmitted
readmitted) [Version]
inventory (MirrorJob -> Version
jobVersion MirrorJob
job)
, ppMetadata :: CachedDoc
ppMetadata = Readmitted -> CachedDoc
raMetadata Readmitted
readmitted
}
mirrorArtifact :: WorkerPolicy -> MirrorJob -> PublishPlan -> MirrorArtifact -> WorkerM JobOutcome
mirrorArtifact :: WorkerPolicy
-> MirrorJob -> PublishPlan -> MirrorArtifact -> WorkerM JobOutcome
mirrorArtifact WorkerPolicy
policy MirrorJob
job PublishPlan
plan MirrorArtifact
admitted = do
Severity -> LogStr -> WorkerM ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
DebugS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (Text
"fetching artifact bytes from " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
jobArtifactAuthority MirrorJob
job))
fetched <- Limits
-> (Maybe ClientCredential
-> Text -> Either UrlFormationError Request)
-> RegistryUrl
-> WorkerM (Either FetchFault ByteString)
fetchArtifactBytes (WorkerPolicy -> Limits
wpArtifactLimits WorkerPolicy
policy) (AdapterArtifact
-> Maybe ClientCredential
-> Text
-> Either UrlFormationError Request
artifactByUrl (WorkerPolicy -> AdapterArtifact
wpArtifact WorkerPolicy
policy)) (MirrorJob -> RegistryUrl
jobArtifactUrl MirrorJob
job)
case fetched of
Left FetchFault
fault -> JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (RetryLeg -> (FetchFault -> Text) -> FetchFault -> JobOutcome
outcomeOfFetchFault RetryLeg
BeforePublish (MirrorJob -> FetchFault -> Text
artifactFetchReason MirrorJob
job) FetchFault
fault)
Right ByteString
bytes -> WorkerPolicy
-> MirrorJob
-> PublishPlan
-> MirrorArtifact
-> ByteString
-> WorkerM JobOutcome
publishIfIntact WorkerPolicy
policy MirrorJob
job PublishPlan
plan MirrorArtifact
admitted ByteString
bytes
artifactFetchReason :: MirrorJob -> FetchFault -> Text
artifactFetchReason :: MirrorJob -> FetchFault -> Text
artifactFetchReason MirrorJob
job = \case
FetchUrlUnformable UrlFormationError
urlErr -> Text
"unformable artifact URL: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> UrlFormationError -> Text
renderUrlFormationError UrlFormationError
urlErr
FetchBoundExceeded LimitError
limitErr -> Text
"artifact exceeded the response bound: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> LimitError -> Text
forall b a. (Show a, IsString b) => a -> b
show LimitError
limitErr
FetchTransport TransportFault
fault -> Text
"artifact fetch from " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
jobArtifactAuthority MirrorJob
job Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" failed: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> TransportCause -> Text
forall b a. (Show a, IsString b) => a -> b
show (TransportFault -> TransportCause
tfCause TransportFault
fault)
publishIfIntact :: WorkerPolicy -> MirrorJob -> PublishPlan -> MirrorArtifact -> ByteString -> WorkerM JobOutcome
publishIfIntact :: WorkerPolicy
-> MirrorJob
-> PublishPlan
-> MirrorArtifact
-> ByteString
-> WorkerM JobOutcome
publishIfIntact WorkerPolicy
policy MirrorJob
job PublishPlan
plan MirrorArtifact
admitted ByteString
bytes = case NonEmpty Hash -> ByteString -> IntegrityResult
verifyIntegrity (MirrorArtifact -> NonEmpty Hash
maHashes MirrorArtifact
admitted) ByteString
bytes of
IntegrityMismatch Text
detail -> do
Severity -> LogStr -> WorkerM ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
ErrorS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (Text
"artifact integrity mismatch, refusing to publish: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
detail))
JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> JobOutcome
Dropped (Text
"integrity mismatch: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
detail))
IntegrityResult
IntegrityVerified -> WorkerPolicy
-> MirrorJob
-> PublishPlan
-> MirrorArtifact
-> ByteString
-> WorkerM JobOutcome
publishVerified WorkerPolicy
policy MirrorJob
job PublishPlan
plan MirrorArtifact
admitted ByteString
bytes
publishVerified :: WorkerPolicy -> MirrorJob -> PublishPlan -> MirrorArtifact -> ByteString -> WorkerM JobOutcome
publishVerified :: WorkerPolicy
-> MirrorJob
-> PublishPlan
-> MirrorArtifact
-> ByteString
-> WorkerM JobOutcome
publishVerified WorkerPolicy
policy MirrorJob
job PublishPlan
plan MirrorArtifact
admitted ByteString
bytes = do
metrics <- (WorkerRuntime -> WorkerMetricsPort) -> WorkerM WorkerMetricsPort
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks WorkerRuntime -> WorkerMetricsPort
wrMetrics
(result, seconds) <- timedSeconds (liftIO (mpPublishArtifact (wpPublish policy) (jobPackage job) plan admitted bytes))
liftIO (wmpMirrorPublishDuration metrics seconds)
outcomeOfPublish job result
outcomeOfPublish :: MirrorJob -> Either PublishFault () -> WorkerM JobOutcome
outcomeOfPublish :: MirrorJob -> Either PublishFault () -> WorkerM JobOutcome
outcomeOfPublish MirrorJob
job = \case
Right () -> JobOutcome
Succeeded JobOutcome -> WorkerM () -> WorkerM JobOutcome
forall a b. a -> WorkerM b -> WorkerM a
forall (f :: * -> *) a b. Functor f => a -> f b -> f a
<$ Severity -> LogStr -> WorkerM ()
forall (m :: * -> *).
(Applicative m, KatipContext m) =>
Severity -> LogStr -> m ()
logFM Severity
InfoS (Text -> LogStr
forall a. StringConv a Text => a -> LogStr
ls (Text
"mirrored artifact published: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> MirrorJob -> Text
renderJob MirrorJob
job))
Left (PublishRejected PublishError
err) -> JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (RetryLeg -> Text -> JobOutcome
Retried RetryLeg
AfterPublish (Text
"registry rejected publish: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> PublishError -> Text
forall b a. (Show a, IsString b) => a -> b
show PublishError
err))
Left (PublishSourceUnavailable Text
detail) -> JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Text -> JobOutcome
SourceUnavailable (MirrorJob -> Text -> Text
sourceUnavailableReason MirrorJob
job Text
detail))
Left (PublishFetch FetchFault
fault) -> JobOutcome -> WorkerM JobOutcome
forall a. a -> WorkerM a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (RetryLeg -> (FetchFault -> Text) -> FetchFault -> JobOutcome
outcomeOfFetchFault RetryLeg
AfterPublish FetchFault -> Text
publishFaultReason FetchFault
fault)
publishFaultReason :: FetchFault -> Text
publishFaultReason :: FetchFault -> Text
publishFaultReason = \case
FetchUrlUnformable UrlFormationError
urlErr -> Text
"unformable publish URL: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> UrlFormationError -> Text
renderUrlFormationError UrlFormationError
urlErr
FetchBoundExceeded LimitError
limitErr -> Text
"the publication target's response exceeded the response bound: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> LimitError -> Text
forall b a. (Show a, IsString b) => a -> b
show LimitError
limitErr
FetchTransport TransportFault
fault -> Text
"publish transport failure: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> TransportFault -> Text
forall b a. (Show a, IsString b) => a -> b
show TransportFault
fault
jobArtifactAuthority :: MirrorJob -> Text
jobArtifactAuthority :: MirrorJob -> Text
jobArtifactAuthority = Text -> Text
authorityLabel (Text -> Text) -> (MirrorJob -> Text) -> MirrorJob -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RegistryUrl -> Text
registryUrlText (RegistryUrl -> Text)
-> (MirrorJob -> RegistryUrl) -> MirrorJob -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MirrorJob -> RegistryUrl
jobArtifactUrl