module Hadolint.Rule.DL3010 (rule) where
import Data.Foldable (toList)
import Data.Function ((&))
import Data.List.NonEmpty (NonEmpty)
import qualified Data.Set as Set
import qualified Data.Sequence as Seq
import qualified Data.Text as Text
import Hadolint.Rule
import qualified Hadolint.Shell as Shell
import Language.Docker.Syntax
data Acc
= Acc
{ Acc -> Set (Linenumber, Text)
archives :: Set.Set (Linenumber, Text.Text),
:: Set.Set (Linenumber, Text.Text)
}
| Empty
rule :: Rule Shell.ParsedShell
rule :: Rule ParsedShell
rule = forall a args.
(Linenumber -> State a -> Instruction args -> State a)
-> State a -> (State a -> Failures) -> Rule args
veryCustomRule Linenumber -> State Acc -> Instruction ParsedShell -> State Acc
check (forall a. a -> State a
emptyState Acc
Empty) State Acc -> Failures
markFailures
where
code :: RuleCode
code = RuleCode
"DL3010"
severity :: DLSeverity
severity = DLSeverity
DLInfoC
message :: Text
message = Text
"Use `ADD` for extracting archives into an image"
check :: Linenumber -> State Acc -> Instruction ParsedShell -> State Acc
check Linenumber
_ State Acc
_ (From BaseImage
_) = forall a. a -> State a
emptyState Acc
Empty
check Linenumber
line State Acc
st (Copy (CopyArgs NonEmpty SourcePath
srcs TargetPath
tgt) (CopyFlags Chown
_ Chmod
_ Link
_ CopySource
NoSource)) =
State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (Linenumber -> NonEmpty SourcePath -> TargetPath -> Acc -> Acc
rememberArchives Linenumber
line NonEmpty SourcePath
srcs TargetPath
tgt)
check Linenumber
_ State Acc
st (Run (RunArgs Arguments ParsedShell
args RunFlags
_))
| Acc Set (Linenumber, Text)
archives Set (Linenumber, Text)
_ <- forall a. State a -> a
state State Acc
st,
Set (Linenumber, Text)
ex <- forall a b. (a -> b) -> Arguments a -> b
foldArguments (Set (Linenumber, Text) -> ParsedShell -> Set (Linenumber, Text)
getExtractedArchives Set (Linenumber, Text)
archives) Arguments ParsedShell
args =
State Acc
st forall a b. a -> (a -> b) -> b
|> forall a. (a -> a) -> State a -> State a
modify (Set (Linenumber, Text) -> Acc -> Acc
markExtracted Set (Linenumber, Text)
ex)
| Bool
otherwise = State Acc
st
check Linenumber
_ State Acc
st Instruction ParsedShell
_ = State Acc
st
markFailures :: State Acc -> Failures
markFailures :: State Acc -> Failures
markFailures (State Failures
fails (Acc Set (Linenumber, Text)
_ Set (Linenumber, Text)
e)) =
forall a b. (a -> b -> a) -> a -> Set b -> a
Set.foldl' forall a. Seq a -> a -> Seq a
(Seq.|>) Failures
fails (forall b a. Ord b => (a -> b) -> Set a -> Set b
Set.map (Linenumber, Text) -> CheckFailure
makeFail Set (Linenumber, Text)
e)
markFailures State Acc
st = forall a. State a -> Failures
failures State Acc
st
makeFail :: (Linenumber, Text.Text) -> CheckFailure
makeFail :: (Linenumber, Text) -> CheckFailure
makeFail (Linenumber
line, Text
_) = CheckFailure {Linenumber
Text
RuleCode
DLSeverity
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
..}
{-# INLINEABLE rule #-}
extractsThisArchive :: (Linenumber, Text.Text) -> Shell.Command -> Bool
(Linenumber
_, Text
archive) Command
cmd =
(Command -> Bool
isTarExtractCommand Command
cmd Bool -> Bool -> Bool
|| Command -> Bool
isUnzipCommand Command
cmd) Bool -> Bool -> Bool
&& Text
archive forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text]
arguments
where
arguments :: [Text]
arguments = forall a b. (a -> b) -> [a] -> [b]
map Text -> Text
basename forall a b. (a -> b) -> a -> b
$ Command -> [Text]
Shell.getArgsNoFlags Command
cmd
getExtractedArchives ::
Set.Set (Linenumber, Text.Text) ->
Shell.ParsedShell ->
Set.Set (Linenumber, Text.Text)
Set (Linenumber, Text)
archives ParsedShell
shell =
forall a. (a -> Bool) -> Set a -> Set a
Set.filter
(\(Linenumber, Text)
a -> forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any ((Linenumber, Text) -> Command -> Bool
extractsThisArchive (Linenumber, Text)
a) [Command]
cmds)
Set (Linenumber, Text)
archives
where
cmds :: [Command]
cmds = ParsedShell -> [Command]
Shell.presentCommands ParsedShell
shell
isTarExtractCommand :: Shell.Command -> Bool
isTarExtractCommand :: Command -> Bool
isTarExtractCommand cmd :: Command
cmd@(Shell.Command Text
name [CmdPart]
_ [CmdPart]
_) =
Text
name forall a. Eq a => a -> a -> Bool
== Text
"tar" Bool -> Bool -> Bool
&& (forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any forall {a}. (Eq a, IsString a) => a -> Bool
longExtractFlags [Text]
args Bool -> Bool -> Bool
|| forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any Text -> Bool
shortExtractFlags [Text]
args)
where
longExtractFlags :: a -> Bool
longExtractFlags a
f = a
f forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [a
"--extract", a
"--get"]
shortExtractFlags :: Text -> Bool
shortExtractFlags Text
f = Text
"-" Text -> Text -> Bool
`Text.isPrefixOf` Text
f Bool -> Bool -> Bool
&& Text
"x" Text -> Text -> Bool
`Text.isInfixOf` Text
f
args :: [Text]
args = Command -> [Text]
Shell.getArgs Command
cmd
isUnzipCommand :: Shell.Command -> Bool
isUnzipCommand :: Command -> Bool
isUnzipCommand (Shell.Command Text
name [CmdPart]
_ [CmdPart]
_) =
Text
name forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem`
[ Text
"unzip",
Text
"gunzip",
Text
"bunzip2",
Text
"unlzma",
Text
"unxz",
Text
"zgz",
Text
"uncompress",
Text
"zcat",
Text
"gzcat"
]
markExtracted :: Set.Set (Linenumber, Text.Text) -> Acc -> Acc
Set (Linenumber, Text)
_ Acc
Empty = Acc
Empty
markExtracted Set (Linenumber, Text)
exarcv Acc {Set (Linenumber, Text)
archives :: Set (Linenumber, Text)
archives :: Acc -> Set (Linenumber, Text)
archives, Set (Linenumber, Text)
extracted :: Set (Linenumber, Text)
extracted :: Acc -> Set (Linenumber, Text)
extracted} =
Acc { Set (Linenumber, Text)
archives :: Set (Linenumber, Text)
archives :: Set (Linenumber, Text)
archives, extracted :: Set (Linenumber, Text)
extracted = forall a. Ord a => Set a -> Set a -> Set a
Set.union Set (Linenumber, Text)
exarcv Set (Linenumber, Text)
extracted }
rememberArchives ::
Linenumber ->
NonEmpty SourcePath ->
TargetPath ->
Acc ->
Acc
rememberArchives :: Linenumber -> NonEmpty SourcePath -> TargetPath -> Acc -> Acc
rememberArchives Linenumber
line NonEmpty SourcePath
paths TargetPath
target Acc
Empty =
if Text -> Bool
isArchive forall a b. (a -> b) -> a -> b
$ TargetPath -> Text
unTargetPath TargetPath
target
then Acc
{ archives :: Set (Linenumber, Text)
archives = forall a. a -> Set a
Set.singleton (Linenumber
line, Text -> Text
basename forall a b. (a -> b) -> a -> b
$ TargetPath -> Text
unTargetPath TargetPath
target),
extracted :: Set (Linenumber, Text)
extracted = forall a. Set a
Set.empty
}
else Acc
{ archives :: Set (Linenumber, Text)
archives =
NonEmpty SourcePath
paths
forall a b. a -> (a -> b) -> b
& forall (t :: * -> *) a. Foldable t => t a -> [a]
toList
forall a b. a -> (a -> b) -> b
& forall a b. (a -> b) -> [a] -> [b]
map (Text -> Text
basename forall b c a. (b -> c) -> (a -> b) -> a -> c
. SourcePath -> Text
unSourcePath)
forall a b. a -> (a -> b) -> b
& forall a. Ord a => [a] -> Set a
Set.fromList
forall a b. a -> (a -> b) -> b
& forall a. (a -> Bool) -> Set a -> Set a
Set.filter Text -> Bool
isArchive
forall a b. a -> (a -> b) -> b
& forall b a. Ord b => (a -> b) -> Set a -> Set b
Set.map (Linenumber
line,),
extracted :: Set (Linenumber, Text)
extracted = forall a. Set a
Set.empty
}
rememberArchives Linenumber
line NonEmpty SourcePath
paths TargetPath
target Acc {Set (Linenumber, Text)
archives :: Set (Linenumber, Text)
archives :: Acc -> Set (Linenumber, Text)
archives, Set (Linenumber, Text)
extracted :: Set (Linenumber, Text)
extracted :: Acc -> Set (Linenumber, Text)
extracted} =
if Text -> Bool
isArchive forall a b. (a -> b) -> a -> b
$ TargetPath -> Text
unTargetPath TargetPath
target
then Acc
{ archives :: Set (Linenumber, Text)
archives =
forall a. Ord a => a -> Set a -> Set a
Set.insert (Linenumber
line, Text -> Text
basename forall a b. (a -> b) -> a -> b
$ TargetPath -> Text
unTargetPath TargetPath
target) Set (Linenumber, Text)
archives,
Set (Linenumber, Text)
extracted :: Set (Linenumber, Text)
extracted :: Set (Linenumber, Text)
extracted
}
else Acc
{ archives :: Set (Linenumber, Text)
archives =
NonEmpty SourcePath
paths
forall a b. a -> (a -> b) -> b
& forall (t :: * -> *) a. Foldable t => t a -> [a]
toList
forall a b. a -> (a -> b) -> b
& forall a b. (a -> b) -> [a] -> [b]
map (Text -> Text
basename forall b c a. (b -> c) -> (a -> b) -> a -> c
. SourcePath -> Text
unSourcePath)
forall a b. a -> (a -> b) -> b
& forall a. Ord a => [a] -> Set a
Set.fromList
forall a b. a -> (a -> b) -> b
& forall a. (a -> Bool) -> Set a -> Set a
Set.filter Text -> Bool
isArchive
forall a b. a -> (a -> b) -> b
& forall b a. Ord b => (a -> b) -> Set a -> Set b
Set.map (Linenumber
line,)
forall a b. a -> (a -> b) -> b
& forall a. Ord a => Set a -> Set a -> Set a
Set.union Set (Linenumber, Text)
archives,
Set (Linenumber, Text)
extracted :: Set (Linenumber, Text)
extracted :: Set (Linenumber, Text)
extracted
}
basename :: Text.Text -> Text.Text
basename :: Text -> Text
basename = (Char -> Bool) -> Text -> Text
Text.takeWhileEnd (\Char
c -> Char
c forall a. Eq a => a -> a -> Bool
/= Char
'/' Bool -> Bool -> Bool
&& Char
c forall a. Eq a => a -> a -> Bool
/= Char
'\\') forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
dropQuotes
isArchive :: Text.Text -> Bool
isArchive :: Text -> Bool
isArchive Text
src =
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (Text -> Text -> Bool
`Text.isSuffixOf` Text -> Text
dropQuotes Text
src) [Text]
archiveFileFormatExtensions