module Hadolint.Rule.DL3009 (rule) where
import Hadolint.Rule
import Language.Docker.Syntax
import qualified Data.Map.Strict as Map
import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Hadolint.Shell as Shell
data Acc
= Acc
{ Acc -> BaseImage
lastFrom :: BaseImage,
Acc -> Bool
dockerClean :: Bool,
Acc -> Map Text Linenumber
stages :: Map.Map Text.Text Linenumber,
Acc -> Map Linenumber BaseImage
forgets :: Map.Map Linenumber BaseImage
}
| Empty
deriving (Acc -> Acc -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Acc -> Acc -> Bool
$c/= :: Acc -> Acc -> Bool
== :: Acc -> Acc -> Bool
$c== :: Acc -> Acc -> Bool
Eq, Linenumber -> Acc -> ShowS
[Acc] -> ShowS
Acc -> String
forall a.
(Linenumber -> a -> ShowS)
-> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Acc] -> ShowS
$cshowList :: [Acc] -> ShowS
show :: Acc -> String
$cshow :: Acc -> String
showsPrec :: Linenumber -> Acc -> ShowS
$cshowsPrec :: Linenumber -> Acc -> ShowS
Show)
rule :: Rule Shell.ParsedShell
rule :: Rule ParsedShell
rule = Rule ParsedShell
dl3009 forall a. Semigroup a => a -> a -> a
<> forall args. Rule args -> Rule args
onbuild Rule ParsedShell
dl3009
{-# INLINEABLE rule #-}
dl3009 :: Rule Shell.ParsedShell
dl3009 :: Rule ParsedShell
dl3009 = forall a args.
(Linenumber -> State a -> Instruction args -> State a)
-> State a -> (State a -> Seq CheckFailure) -> Rule args
veryCustomRule Linenumber -> State Acc -> Instruction ParsedShell -> State Acc
check (forall a. a -> State a
emptyState Acc
Empty) State Acc -> Seq CheckFailure
markFailures
where
code :: RuleCode
code = RuleCode
"DL3009"
severity :: DLSeverity
severity = DLSeverity
DLInfoC
message :: Text
message = Text
"Delete the apt-get lists after installing something"
check :: Linenumber -> State Acc -> Instruction ParsedShell -> State Acc
check Linenumber
line State Acc
st (From BaseImage
from) = State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (Linenumber -> BaseImage -> Acc -> Acc
rememberStage Linenumber
line BaseImage
from)
check Linenumber
line State Acc
st (Run (RunArgs Arguments ParsedShell
args RunFlags
flags))
| Bool -> Bool
not (forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Bool
forgotToCleanup Arguments ParsedShell
args) =
if forall a b. (a -> b) -> Arguments a -> b
foldArguments ParsedShell -> Bool
disabledDockerClean Arguments ParsedShell
args
then State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify Acc -> Acc
rememberDockerClean
else State Acc
st
| Text -> RunFlags -> Bool
hasCacheDirectory Text
"/var/lib/apt/lists" RunFlags
flags = State Acc
st
| Text -> RunFlags -> Bool
hasCacheDirectory Text
"/var/lib/apt" RunFlags
flags
Bool -> Bool -> Bool
&& Text -> RunFlags -> Bool
hasCacheDirectory Text
"/var/cache/apt" RunFlags
flags = State Acc
st
| Bool
otherwise = State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (Linenumber -> Acc -> Acc
rememberLine Linenumber
line)
check Linenumber
_ State Acc
st Instruction ParsedShell
_ = State Acc
st
markFailures :: State Acc -> Seq CheckFailure
markFailures (State Seq CheckFailure
fails Acc
Empty) = Seq CheckFailure
fails
markFailures (State Seq CheckFailure
_ Acc {Bool
Map Linenumber BaseImage
Map Text Linenumber
BaseImage
forgets :: Map Linenumber BaseImage
stages :: Map Text Linenumber
dockerClean :: Bool
lastFrom :: BaseImage
forgets :: Acc -> Map Linenumber BaseImage
stages :: Acc -> Map Text Linenumber
dockerClean :: Acc -> Bool
lastFrom :: Acc -> BaseImage
..}) = Map Linenumber BaseImage
forgets forall a b. a -> (a -> b) -> b
|> forall m k a. Monoid m => (k -> a -> m) -> Map k a -> m
Map.foldMapWithKey forall {f :: * -> *}.
(Applicative f, Monoid (f CheckFailure)) =>
Linenumber -> BaseImage -> f CheckFailure
mapFail
where
mapFail :: Linenumber -> BaseImage -> f CheckFailure
mapFail Linenumber
line BaseImage
from
| BaseImage
from forall a. Eq a => a -> a -> Bool
== BaseImage
lastFrom = forall (f :: * -> *) a. Applicative f => a -> f a
pure CheckFailure {Linenumber
Text
RuleCode
DLSeverity
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
..}
| BaseImage {$sel:alias:BaseImage :: BaseImage -> Maybe ImageAlias
alias = Just (ImageAlias Text
als)} <- BaseImage
from,
Just Linenumber
_ <- forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Text
als Map Text Linenumber
stages =
forall (f :: * -> *) a. Applicative f => a -> f a
pure CheckFailure {Linenumber
Text
RuleCode
DLSeverity
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
..}
| Bool
otherwise = forall a. Monoid a => a
mempty
{-# INLINEABLE dl3009 #-}
rememberStage :: Linenumber -> BaseImage -> Acc -> Acc
rememberStage :: Linenumber -> BaseImage -> Acc -> Acc
rememberStage Linenumber
line from :: BaseImage
from@BaseImage {$sel:image:BaseImage :: BaseImage -> Image
image = Image Maybe Registry
_ Text
als} (Acc BaseImage
_ Bool
_ Map Text Linenumber
stages Map Linenumber BaseImage
o) = BaseImage
-> Bool -> Map Text Linenumber -> Map Linenumber BaseImage -> Acc
Acc BaseImage
from Bool
True (forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Text
als Linenumber
line Map Text Linenumber
stages) Map Linenumber BaseImage
o
rememberStage Linenumber
line from :: BaseImage
from@BaseImage {$sel:image:BaseImage :: BaseImage -> Image
image = Image Maybe Registry
_ Text
als} Acc
Empty = BaseImage
-> Bool -> Map Text Linenumber -> Map Linenumber BaseImage -> Acc
Acc BaseImage
from Bool
True (forall k a. k -> a -> Map k a
Map.singleton Text
als Linenumber
line) forall k a. Map k a
Map.empty
rememberLine :: Linenumber -> Acc -> Acc
rememberLine :: Linenumber -> Acc -> Acc
rememberLine Linenumber
line (Acc BaseImage
from Bool
clean Map Text Linenumber
stages Map Linenumber BaseImage
o) = BaseImage
-> Bool -> Map Text Linenumber -> Map Linenumber BaseImage -> Acc
Acc BaseImage
from Bool
clean Map Text Linenumber
stages (forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Linenumber
line BaseImage
from Map Linenumber BaseImage
o)
rememberLine Linenumber
line Acc
Empty = BaseImage
-> Bool -> Map Text Linenumber -> Map Linenumber BaseImage -> Acc
Acc BaseImage
emptyImage Bool
True forall a. Monoid a => a
mempty (forall k a. k -> a -> Map k a
Map.singleton Linenumber
line BaseImage
emptyImage)
rememberDockerClean :: Acc -> Acc
rememberDockerClean :: Acc -> Acc
rememberDockerClean Acc
Empty = BaseImage
-> Bool -> Map Text Linenumber -> Map Linenumber BaseImage -> Acc
Acc BaseImage
emptyImage Bool
False forall a. Monoid a => a
mempty forall a. Monoid a => a
mempty
rememberDockerClean (Acc BaseImage
from Bool
_ Map Text Linenumber
stages Map Linenumber BaseImage
forget) = BaseImage
-> Bool -> Map Text Linenumber -> Map Linenumber BaseImage -> Acc
Acc BaseImage
from Bool
False Map Text Linenumber
stages Map Linenumber BaseImage
forget
forgotToCleanup :: Shell.ParsedShell -> Bool
forgotToCleanup :: ParsedShell -> Bool
forgotToCleanup ParsedShell
args
| ParsedShell -> Bool
hasUpdate ParsedShell
args Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
hasCleanup = Bool
True
| Bool
otherwise = Bool
False
where
hasCleanup :: Bool
hasCleanup =
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Text -> [Text] -> Command -> Bool
Shell.cmdHasArgs Text
"rm" [Text
"-rf", Text
"/var/lib/apt/lists/*"]) (ParsedShell -> [Command]
Shell.presentCommands ParsedShell
args)
hasUpdate :: Shell.ParsedShell -> Bool
hasUpdate :: ParsedShell -> Bool
hasUpdate ParsedShell
args =
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Text -> [Text] -> Command -> Bool
Shell.cmdHasArgs Text
"apt-get" [Text
"update"]) (ParsedShell -> [Command]
Shell.presentCommands ParsedShell
args)
disabledDockerClean :: Shell.ParsedShell -> Bool
disabledDockerClean :: ParsedShell -> Bool
disabledDockerClean ParsedShell
args
| Bool
removesScript Bool -> Bool -> Bool
|| Bool
keepsPackages = Bool
True
| Bool
otherwise = Bool
False
where
removesScript :: Bool
removesScript =
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any
(Text -> [Text] -> Command -> Bool
Shell.cmdHasArgs Text
"rm" [Text
"/etc/apt/apt.conf.d/docker-clean"])
(ParsedShell -> [Command]
Shell.presentCommands ParsedShell
args)
keepsPackages :: Bool
keepsPackages =
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any
( Text -> [Text] -> Command -> Bool
Shell.cmdHasArgs
Text
"echo"
[Text
"\'Binary::apt::APT::Keep-Downloaded-Packages \"true\";\'"]
)
(ParsedShell -> [Command]
Shell.presentCommands ParsedShell
args)
hasCacheDirectory :: Text.Text -> RunFlags -> Bool
hasCacheDirectory :: Text -> RunFlags -> Bool
hasCacheDirectory Text
dir RunFlags { Set RunMount
$sel:mount:RunFlags :: RunFlags -> Set RunMount
mount :: Set RunMount
mount } =
Bool -> Bool
not ( forall (t :: * -> *) a. Foldable t => t a -> Bool
null forall a b. (a -> b) -> a -> b
$ forall a. (a -> Bool) -> Set a -> Set a
Set.filter (Text -> RunMount -> Bool
isCacheMount Text
dir) Set RunMount
mount)
isCacheMount :: Text.Text -> RunMount -> Bool
isCacheMount :: Text -> RunMount -> Bool
isCacheMount Text
dir (CacheMount CacheOpts {$sel:cTarget:CacheOpts :: CacheOpts -> TargetPath
cTarget = TargetPath {$sel:unTargetPath:TargetPath :: TargetPath -> Text
unTargetPath = Text
t}}) = Text
dir Text -> Text -> Bool
`Text.isPrefixOf` Text
t
isCacheMount Text
_ RunMount
_ = Bool
False
emptyImage :: BaseImage
emptyImage :: BaseImage
emptyImage = (BaseImage {$sel:image:BaseImage :: Image
image = Image
"scratch", $sel:tag:BaseImage :: Maybe Tag
tag = forall a. Maybe a
Nothing, $sel:digest:BaseImage :: Maybe Digest
digest = forall a. Maybe a
Nothing, $sel:alias:BaseImage :: Maybe ImageAlias
alias = forall a. Maybe a
Nothing, $sel:platform:BaseImage :: Maybe Text
platform = forall a. Maybe a
Nothing})