module Ecluse.Core.Package.Filter (
FilterPlan (..),
filterPlanFromDecisions,
restrictToSurvivors,
enforceArtifactScheme,
enforceArtifactSchemeDetails,
) where
import Data.Aeson (Value (String))
import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Data.Text qualified as T
import Ecluse.Core.Package (
Artifact (artUrl),
InvalidEntry (..),
InvalidEntryKind (InvalidVersionManifest),
PackageDetails (pkgArtifacts),
PackageInfo (infoDistTags, infoInvalidEntries, infoVersions),
pkgVersion,
)
import Ecluse.Core.Rules.Types (Decision (Admitted))
import Ecluse.Core.Security (hostAddress)
import Ecluse.Core.Security.Egress (registryUrlText, resolveTarballUrl)
import Ecluse.Core.Version (Version, renderVersion, selectLatest, unVersion)
data FilterPlan = FilterPlan
{ FilterPlan -> Set Text
fpSurvivors :: Set Text
, FilterPlan -> Maybe Version
fpLatest :: Maybe Version
, FilterPlan -> [Decision]
fpDecisions :: [Decision]
}
deriving stock (FilterPlan -> FilterPlan -> Bool
(FilterPlan -> FilterPlan -> Bool)
-> (FilterPlan -> FilterPlan -> Bool) -> Eq FilterPlan
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: FilterPlan -> FilterPlan -> Bool
== :: FilterPlan -> FilterPlan -> Bool
$c/= :: FilterPlan -> FilterPlan -> Bool
/= :: FilterPlan -> FilterPlan -> Bool
Eq, Int -> FilterPlan -> ShowS
[FilterPlan] -> ShowS
FilterPlan -> String
(Int -> FilterPlan -> ShowS)
-> (FilterPlan -> String)
-> ([FilterPlan] -> ShowS)
-> Show FilterPlan
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FilterPlan -> ShowS
showsPrec :: Int -> FilterPlan -> ShowS
$cshow :: FilterPlan -> String
show :: FilterPlan -> String
$cshowList :: [FilterPlan] -> ShowS
showList :: [FilterPlan] -> ShowS
Show)
filterPlanFromDecisions :: Map Text Decision -> PackageInfo -> FilterPlan
filterPlanFromDecisions :: Map Text Decision -> PackageInfo -> FilterPlan
filterPlanFromDecisions Map Text Decision
decisions PackageInfo
info =
FilterPlan
{ fpSurvivors :: Set Text
fpSurvivors = Set Text
survivors
, fpLatest :: Maybe Version
fpLatest = Maybe Version -> [Version] -> Maybe Version
selectLatest Maybe Version
chosen [Version]
survivingVersions
, fpDecisions :: [Decision]
fpDecisions = Map Text Decision -> [Decision]
forall k a. Map k a -> [a]
Map.elems Map Text Decision
decisions
}
where
survivors :: Set Text
survivors :: Set Text
survivors = Map Text Decision -> Set Text
forall k a. Map k a -> Set k
Map.keysSet ((Decision -> Bool) -> Map Text Decision -> Map Text Decision
forall a k. (a -> Bool) -> Map k a -> Map k a
Map.filter Decision -> Bool
isApproved Map Text Decision
decisions)
isApproved :: Decision -> Bool
isApproved :: Decision -> Bool
isApproved = \case
Admitted{} -> Bool
True
Decision
_ -> Bool
False
versionOf :: Text -> Maybe Version
versionOf :: Text -> Maybe Version
versionOf Text
raw = PackageDetails -> Version
pkgVersion (PackageDetails -> Version)
-> Maybe PackageDetails -> Maybe Version
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> Map Text PackageDetails -> Maybe PackageDetails
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
raw (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
info)
chosen :: Maybe Version
chosen :: Maybe Version
chosen = Text -> Map Text Version -> Maybe Version
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
"latest" (PackageInfo -> Map Text Version
infoDistTags PackageInfo
info) Maybe Version -> (Version -> Maybe Version) -> Maybe Version
forall a b. Maybe a -> (a -> Maybe b) -> Maybe b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Text -> Maybe Version
versionOf (Text -> Maybe Version)
-> (Version -> Text) -> Version -> Maybe Version
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Version -> Text
unVersion
survivingVersions :: [Version]
survivingVersions :: [Version]
survivingVersions = (Text -> Maybe Version) -> [Text] -> [Version]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe Text -> Maybe Version
versionOf (Set Text -> [Text]
forall a. Set a -> [a]
Set.toList Set Text
survivors)
restrictToSurvivors :: Set Text -> PackageInfo -> PackageInfo
restrictToSurvivors :: Set Text -> PackageInfo -> PackageInfo
restrictToSurvivors Set Text
survivors PackageInfo
info =
PackageInfo
info
{ infoVersions = Map.restrictKeys (infoVersions info) survivors
, infoDistTags = Map.filter ((`Set.member` survivors) . renderVersion) (infoDistTags info)
}
enforceArtifactScheme :: Text -> PackageInfo -> PackageInfo
enforceArtifactScheme :: Text -> PackageInfo -> PackageInfo
enforceArtifactScheme Text
upstreamBaseUrl PackageInfo
info =
case Text -> Maybe Text
httpsUpstreamHost Text
upstreamBaseUrl of
Maybe Text
Nothing -> PackageInfo
info
Just Text
upstreamHost ->
let (Map Text PackageDetails
kept, [InvalidEntry]
drops) = (Text
-> PackageDetails
-> (Map Text PackageDetails, [InvalidEntry])
-> (Map Text PackageDetails, [InvalidEntry]))
-> (Map Text PackageDetails, [InvalidEntry])
-> Map Text PackageDetails
-> (Map Text PackageDetails, [InvalidEntry])
forall k a b. (k -> a -> b -> b) -> b -> Map k a -> b
Map.foldrWithKey (Text
-> Text
-> PackageDetails
-> (Map Text PackageDetails, [InvalidEntry])
-> (Map Text PackageDetails, [InvalidEntry])
step Text
upstreamHost) (Map Text PackageDetails
forall k a. Map k a
Map.empty, []) (PackageInfo -> Map Text PackageDetails
infoVersions PackageInfo
info)
in PackageInfo
info{infoVersions = kept, infoInvalidEntries = infoInvalidEntries info <> drops}
where
step :: Text
-> Text
-> PackageDetails
-> (Map Text PackageDetails, [InvalidEntry])
-> (Map Text PackageDetails, [InvalidEntry])
step Text
upstreamHost Text
rawVersion PackageDetails
details (Map Text PackageDetails
keptAcc, [InvalidEntry]
dropAcc) =
case Text -> PackageDetails -> Either (Text, Text) PackageDetails
resolveDetails Text
upstreamHost PackageDetails
details of
Right PackageDetails
ok -> (Text
-> PackageDetails
-> Map Text PackageDetails
-> Map Text PackageDetails
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Text
rawVersion PackageDetails
ok Map Text PackageDetails
keptAcc, [InvalidEntry]
dropAcc)
Left (Text
reason, Text
badUrl) ->
(Map Text PackageDetails
keptAcc, InvalidEntryKind -> Text -> Value -> Text -> InvalidEntry
InvalidEntry InvalidEntryKind
InvalidVersionManifest Text
rawVersion (Text -> Value
String Text
badUrl) Text
reason InvalidEntry -> [InvalidEntry] -> [InvalidEntry]
forall a. a -> [a] -> [a]
: [InvalidEntry]
dropAcc)
enforceArtifactSchemeDetails :: Text -> PackageDetails -> Maybe PackageDetails
enforceArtifactSchemeDetails :: Text -> PackageDetails -> Maybe PackageDetails
enforceArtifactSchemeDetails Text
upstreamBaseUrl PackageDetails
details =
case Text -> Maybe Text
httpsUpstreamHost Text
upstreamBaseUrl of
Maybe Text
Nothing -> PackageDetails -> Maybe PackageDetails
forall a. a -> Maybe a
Just PackageDetails
details
Just Text
upstreamHost -> Either (Text, Text) PackageDetails -> Maybe PackageDetails
forall l r. Either l r -> Maybe r
rightToMaybe (Text -> PackageDetails -> Either (Text, Text) PackageDetails
resolveDetails Text
upstreamHost PackageDetails
details)
httpsUpstreamHost :: Text -> Maybe Text
httpsUpstreamHost :: Text -> Maybe Text
httpsUpstreamHost Text
baseUrl
| Text
"https://" Text -> Text -> Bool
`T.isPrefixOf` Text -> Text
T.toLower Text
baseUrl = Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Text
hostAddress Text
baseUrl)
| Bool
otherwise = Maybe Text
forall a. Maybe a
Nothing
resolveDetails :: Text -> PackageDetails -> Either (Text, Text) PackageDetails
resolveDetails :: Text -> PackageDetails -> Either (Text, Text) PackageDetails
resolveDetails Text
upstreamHost PackageDetails
details =
(\NonEmpty Artifact
arts -> PackageDetails
details{pkgArtifacts = arts}) (NonEmpty Artifact -> PackageDetails)
-> Either (Text, Text) (NonEmpty Artifact)
-> Either (Text, Text) PackageDetails
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (Artifact -> Either (Text, Text) Artifact)
-> NonEmpty Artifact -> Either (Text, Text) (NonEmpty Artifact)
forall (t :: * -> *) (f :: * -> *) a b.
(Traversable t, Applicative f) =>
(a -> f b) -> t a -> f (t b)
forall (f :: * -> *) a b.
Applicative f =>
(a -> f b) -> NonEmpty a -> f (NonEmpty b)
traverse (Text -> Artifact -> Either (Text, Text) Artifact
resolveArtifact Text
upstreamHost) (PackageDetails -> NonEmpty Artifact
pkgArtifacts PackageDetails
details)
resolveArtifact :: Text -> Artifact -> Either (Text, Text) Artifact
resolveArtifact :: Text -> Artifact -> Either (Text, Text) Artifact
resolveArtifact Text
upstreamHost Artifact
art =
case Text -> Text -> Either Text RegistryUrl
resolveTarballUrl Text
upstreamHost (Artifact -> Text
artUrl Artifact
art) of
Right RegistryUrl
resolved -> Artifact -> Either (Text, Text) Artifact
forall a b. b -> Either a b
Right Artifact
art{artUrl = registryUrlText resolved}
Left Text
reason -> (Text, Text) -> Either (Text, Text) Artifact
forall a b. a -> Either a b
Left (Text
reason, Artifact -> Text
artUrl Artifact
art)