hadolint 1.14.0 → 1.15.0
raw patch · 10 files changed
+163/−154 lines, 10 filesdep ~language-dockerdep ~megaparsec
Dependency ranges changed: language-docker, megaparsec
Files
- hadolint.cabal +8/−8
- src/Hadolint/Formatter/Checkstyle.hs +20/−23
- src/Hadolint/Formatter/Codacy.hs +19/−27
- src/Hadolint/Formatter/Codeclimate.hs +17/−20
- src/Hadolint/Formatter/Format.hs +36/−12
- src/Hadolint/Formatter/Json.hs +10/−13
- src/Hadolint/Formatter/TTY.hs +6/−12
- src/Hadolint/Lint.hs +4/−5
- src/Hadolint/Rules.hs +25/−27
- test/Spec.hs +18/−7
hadolint.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: a3ad665f70a51301b8daa833c740191a21e0230d890d804af8bdbb21c93e5a18+-- hash: 87e6b48cec5e99eb4cf60b8c1d95138858601ec3516c35a5f36c2ff74e2d7f0e name: hadolint-version: 1.14.0+version: 1.15.0 synopsis: Dockerfile Linter JavaScript API description: A smarter Dockerfile linter that helps you build best practice Docker images. category: Development@@ -51,8 +51,8 @@ , containers , directory >=1.3.0 , filepath- , language-docker >=7.0.0 && <8- , megaparsec >=6.4+ , language-docker >=8.0.0 && <9+ , megaparsec >=7.0 , mtl , split >=0.2 , text@@ -72,8 +72,8 @@ , containers , gitrev >=1.3.1 , hadolint- , language-docker >=7.0.0 && <8- , megaparsec >=6.4+ , language-docker >=8.0.0 && <9+ , megaparsec >=7.0 , optparse-applicative >=0.14.0 , text if !(os(osx))@@ -96,8 +96,8 @@ , bytestring >=0.10 , hadolint , hspec- , language-docker >=7.0.0 && <8- , megaparsec >=6.4+ , language-docker >=8.0.0 && <9+ , megaparsec >=7.0 , split >=0.2 , text default-language: Haskell2010
src/Hadolint/Formatter/Checkstyle.hs view
@@ -11,17 +11,14 @@ import Data.Char import Data.Foldable (toList) import Data.List (groupBy)-import qualified Data.List.NonEmpty as NE import Data.Monoid ((<>), mconcat) import qualified Data.Text as Text import Hadolint.Formatter.Format import Hadolint.Rules (Metadata(..), RuleCheck(..)) import ShellCheck.Interface+import Text.Megaparsec (Stream) import Text.Megaparsec.Error- (ParseError, ShowErrorComponent, ShowToken, errorPos,- parseErrorTextPretty)-import Text.Megaparsec.Pos- (sourceColumn, sourceLine, sourceName, unPos)+import Text.Megaparsec.Pos (sourceColumn, sourceLine, sourceName, unPos) data CheckStyle = CheckStyle { file :: String@@ -32,29 +29,29 @@ , source :: String } -errorToCheckStyle :: (ShowToken t, Ord t, ShowErrorComponent e) => ParseError t e -> CheckStyle+errorToCheckStyle :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> CheckStyle errorToCheckStyle err = CheckStyle- { file = sourceName pos- , line = unPos (sourceLine pos)- , column = unPos (sourceColumn pos)- , impact = severityText ErrorC- , msg = stripNewlines (parseErrorTextPretty err)- , source = "DL1000"- }+ { file = sourceName pos+ , line = unPos (sourceLine pos)+ , column = unPos (sourceColumn pos)+ , impact = severityText ErrorC+ , msg = errorBundlePretty err+ , source = "DL1000"+ } where- pos = NE.head (errorPos err)+ pos = errorPosition err ruleToCheckStyle :: RuleCheck -> CheckStyle ruleToCheckStyle RuleCheck {..} = CheckStyle- { file = Text.unpack filename- , line = linenumber- , column = 1- , impact = severityText (severity metadata)- , msg = Text.unpack (message metadata)- , source = Text.unpack (code metadata)- }+ { file = Text.unpack filename+ , line = linenumber+ , column = 1+ , impact = severityText (severity metadata)+ , msg = Text.unpack (message metadata)+ , source = Text.unpack (code metadata)+ } toXml :: [CheckStyle] -> Builder.Builder toXml checks = wrap fileName (foldMap convert checks)@@ -85,7 +82,7 @@ else "&#" ++ show (ord c) ++ ";" isOk x = any (\check -> check x) [isAsciiUpper, isAsciiLower, isDigit, (`elem` [' ', '.', '/'])] -formatResult :: (ShowToken t, Ord t, ShowErrorComponent e) => Result t e -> Builder.Builder+formatResult :: (Stream s, ShowErrorComponent e) => Result s e -> Builder.Builder formatResult (Result errors checks) = "<?xml version='1.0' encoding='UTF-8'?><checkstyle version='4.3'>" <> xmlBody <> "</checkstyle>" where@@ -96,5 +93,5 @@ checkstyleChecks = fmap ruleToCheckStyle checks sameFileName CheckStyle {file = f1} CheckStyle {file = f2} = f1 == f2 -printResult :: (ShowToken t, Ord t, ShowErrorComponent e) => Result t e -> IO ()+printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO () printResult result = B.putStr (Builder.toLazyByteString (formatResult result))
src/Hadolint/Formatter/Codacy.hs view
@@ -8,17 +8,14 @@ import Data.Aeson hiding (Result) import qualified Data.ByteString.Lazy.Char8 as B-import qualified Data.List.NonEmpty as NE import Data.Monoid ((<>)) import Data.Sequence (Seq) import qualified Data.Text as Text-import Hadolint.Formatter.Format (Result(..))+import Hadolint.Formatter.Format (Result(..), errorPosition) import Hadolint.Rules (Metadata(..), RuleCheck(..))+import Text.Megaparsec (Stream) import Text.Megaparsec.Error- (ParseError, ShowErrorComponent, ShowToken, errorPos,- parseErrorTextPretty)-import Text.Megaparsec.Pos- (sourceLine, sourceName, unPos)+import Text.Megaparsec.Pos (sourceLine, sourceName, unPos) data Issue = Issue { filename :: String@@ -29,42 +26,37 @@ instance ToJSON Issue where toJSON Issue {..} =- object- [ "filename" .= filename- , "patternId" .= patternId- , "message" .= msg- , "line" .= line- ]+ object ["filename" .= filename, "patternId" .= patternId, "message" .= msg, "line" .= line] -errorToIssue :: (ShowToken t, Ord t, ShowErrorComponent e) => ParseError t e -> Issue+errorToIssue :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> Issue errorToIssue err = Issue- { filename = sourceName pos- , patternId = "DL1000"- , msg = parseErrorTextPretty err- , line = linenumber- }+ { filename = sourceName pos+ , patternId = "DL1000"+ , msg = errorBundlePretty err+ , line = linenumber+ } where- pos = NE.head (errorPos err)+ pos = errorPosition err linenumber = unPos (sourceLine pos)- + checkToIssue :: RuleCheck -> Issue checkToIssue RuleCheck {..} = Issue- { filename = Text.unpack filename- , patternId = Text.unpack (code metadata)- , msg = Text.unpack (message metadata)- , line = linenumber- }+ { filename = Text.unpack filename+ , patternId = Text.unpack (code metadata)+ , msg = Text.unpack (message metadata)+ , line = linenumber+ } -formatResult :: (ShowToken t, Ord t, ShowErrorComponent e) => Result t e -> Seq Issue+formatResult :: (Stream s, ShowErrorComponent e) => Result s e -> Seq Issue formatResult (Result errors checks) = allIssues where allIssues = errorMessages <> checkMessages errorMessages = fmap errorToIssue errors checkMessages = fmap checkToIssue checks -printResult :: (ShowToken t, Ord t, ShowErrorComponent e) => Result t e -> IO ()+printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO () printResult result = mapM_ output (formatResult result) where output value = B.putStrLn (encode value)
src/Hadolint/Formatter/Codeclimate.hs view
@@ -9,19 +9,16 @@ import Data.Aeson hiding (Result) import qualified Data.ByteString.Lazy as B-import qualified Data.List.NonEmpty as NE import Data.Monoid ((<>)) import Data.Sequence (Seq) import qualified Data.Text as Text import GHC.Generics-import Hadolint.Formatter.Format (Result(..))+import Hadolint.Formatter.Format (Result(..), errorPosition) import Hadolint.Rules (Metadata(..), RuleCheck(..)) import ShellCheck.Interface+import Text.Megaparsec (Stream) import Text.Megaparsec.Error- (ParseError, ShowErrorComponent, ShowToken, errorPos,- parseErrorTextPretty)-import Text.Megaparsec.Pos- (sourceColumn, sourceLine, sourceName, unPos)+import Text.Megaparsec.Pos (sourceColumn, sourceLine, sourceName, unPos) data Issue = Issue { checkName :: String@@ -59,27 +56,27 @@ , "severity" .= impact ] -errorToIssue :: (ShowToken t, Ord t, ShowErrorComponent e) => ParseError t e -> Issue+errorToIssue :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> Issue errorToIssue err = Issue- { checkName = "DL1000"- , description = parseErrorTextPretty err- , location = LocPos (sourceName pos) Pos {..}- , impact = severityText ErrorC- }+ { checkName = "DL1000"+ , description = errorBundlePretty err+ , location = LocPos (sourceName pos) Pos {..}+ , impact = severityText ErrorC+ } where- pos = NE.head (errorPos err)+ pos = errorPosition err line = unPos (sourceLine pos) column = unPos (sourceColumn pos) checkToIssue :: RuleCheck -> Issue checkToIssue RuleCheck {..} = Issue- { checkName = Text.unpack (code metadata)- , description = Text.unpack (message metadata)- , location = LocLine (Text.unpack filename) linenumber- , impact = severityText (severity metadata)- }+ { checkName = Text.unpack (code metadata)+ , description = Text.unpack (message metadata)+ , location = LocLine (Text.unpack filename) linenumber+ , impact = severityText (severity metadata)+ } severityText :: Severity -> String severityText severity =@@ -89,14 +86,14 @@ InfoC -> "info" StyleC -> "minor" -formatResult :: (ShowToken t, Ord t, ShowErrorComponent e) => Result t e -> Seq Issue+formatResult :: (Stream s, ShowErrorComponent e) => Result s e -> Seq Issue formatResult (Result errors checks) = allIssues where allIssues = errorMessages <> checkMessages errorMessages = fmap errorToIssue errors checkMessages = fmap checkToIssue checks -printResult :: (ShowToken t, Ord t, ShowErrorComponent e) => Result t e -> IO ()+printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO () printResult result = mapM_ output (formatResult result) where output value = do
src/Hadolint/Formatter/Format.hs view
@@ -1,35 +1,48 @@ module Hadolint.Formatter.Format ( severityText , stripNewlines+ , errorMessageLine+ , errorPosition+ , errorPositionPretty+ , Text.Megaparsec.Error.errorBundlePretty , Result(..)+ , isEmpty , toResult ) where import Data.List (sort)+import qualified Data.List.NonEmpty as NE import Data.Monoid (Monoid) import Data.Semigroup-import Data.Sequence (Seq, fromList, singleton)+import qualified Data.Sequence as Seq+import Data.Sequence (Seq) import Hadolint.Rules import ShellCheck.Interface-import Text.Megaparsec.Error (ParseError)+import Text.Megaparsec (Stream(..))+import Text.Megaparsec.Error+import Text.Megaparsec.Pos (SourcePos, sourcePosPretty) -data Result t e = Result- { errors :: Seq (ParseError t e)+data Result s e = Result+ { errors :: Seq (ParseErrorBundle s e) , checks :: Seq RuleCheck- } deriving (Eq)+ } -instance Semigroup (Result t e) where+instance Semigroup (Result s e) where (Result e1 c1) <> (Result e2 c2) = Result (e1 <> e2) (c1 <> c2) -instance Monoid (Result t e) where+instance Monoid (Result s e) where mappend = (<>) mempty = Result mempty mempty -toResult :: Either (ParseError t e) [RuleCheck] -> Result t e+isEmpty :: Result s e -> Bool+isEmpty (Result Seq.Empty Seq.Empty) = True+isEmpty _ = False++toResult :: Either (ParseErrorBundle s e) [RuleCheck] -> Result s e toResult res = case res of- Left err -> Result (singleton err) mempty- Right c -> Result mempty (fromList (sort c))+ Left err -> Result (Seq.singleton err) mempty+ Right c -> Result mempty (Seq.fromList (sort c)) severityText :: Severity -> String severityText s =@@ -41,8 +54,19 @@ stripNewlines :: String -> String stripNewlines =- map- (\c ->+ map (\c -> if c == '\n' then ' ' else c)++errorMessageLine :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> String+errorMessageLine err@(ParseErrorBundle e _) =+ errorPositionPretty err ++ " " ++ parseErrorTextPretty (NE.head e)++errorPositionPretty :: Stream s => ParseErrorBundle s e -> String+errorPositionPretty err = sourcePosPretty (errorPosition err)++errorPosition :: Stream s => ParseErrorBundle s e -> Text.Megaparsec.Pos.SourcePos+errorPosition (ParseErrorBundle e s) =+ let (pos, _, _) = reachOffset (errorOffset (NE.head e)) s+ in pos
src/Hadolint/Formatter/Json.hs view
@@ -8,22 +8,19 @@ import Data.Aeson hiding (Result) import qualified Data.ByteString.Lazy.Char8 as B-import qualified Data.List.NonEmpty as NE import Data.Monoid ((<>))-import Hadolint.Formatter.Format (Result(..), severityText)+import Hadolint.Formatter.Format (Result(..), errorPosition, severityText) import Hadolint.Rules (Metadata(..), RuleCheck(..)) import ShellCheck.Interface+import Text.Megaparsec (Stream) import Text.Megaparsec.Error- (ParseError, ShowErrorComponent, ShowToken, errorPos,- parseErrorTextPretty)-import Text.Megaparsec.Pos- (sourceColumn, sourceLine, sourceName, unPos)+import Text.Megaparsec.Pos (sourceColumn, sourceLine, sourceName, unPos) -data JsonFormat t e+data JsonFormat s e = JsonCheck RuleCheck- | JsonParseError (ParseError t e)+ | JsonParseError (ParseErrorBundle s e) -instance (ShowToken t, Ord t, ShowErrorComponent e) => ToJSON (JsonFormat t e) where+instance (Stream s, ShowErrorComponent e) => ToJSON (JsonFormat s e) where toJSON (JsonCheck RuleCheck {..}) = object [ "file" .= filename@@ -40,17 +37,17 @@ , "column" .= unPos (sourceColumn pos) , "level" .= severityText ErrorC , "code" .= ("DL1000" :: String)- , "message" .= parseErrorTextPretty err+ , "message" .= errorBundlePretty err ] where- pos = NE.head (errorPos err)+ pos = errorPosition err -formatResult :: (ShowToken t, Ord t, ShowErrorComponent e) => Result t e -> Value+formatResult :: (Stream s, ShowErrorComponent e) => Result s e -> Value formatResult (Result errors checks) = toJSON allMessages where allMessages = errorMessages <> checkMessages errorMessages = fmap JsonParseError errors checkMessages = fmap JsonCheck checks -printResult :: (ShowToken t, Ord t, ShowErrorComponent e) => Result t e -> IO ()+printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO () printResult result = B.putStrLn (encode (formatResult result))
src/Hadolint/Formatter/TTY.hs view
@@ -4,28 +4,22 @@ module Hadolint.Formatter.TTY ( printResult , formatError+ , formatChecks ) where -import qualified Data.List.NonEmpty as NE import Data.Semigroup ((<>)) import qualified Data.Text as Text import Hadolint.Formatter.Format import Hadolint.Rules import Language.Docker.Syntax+import Text.Megaparsec (Stream(..)) import Text.Megaparsec.Error- (ParseError, ShowErrorComponent, ShowToken, errorPos,- parseErrorTextPretty)-import Text.Megaparsec.Pos (sourcePosPretty) -formatErrors ::- (ShowToken t, Ord t, ShowErrorComponent e, Functor f) => f (ParseError t e) -> f String+formatErrors :: (Stream s, ShowErrorComponent e, Functor f) => f (ParseErrorBundle s e) -> f String formatErrors = fmap formatError -formatError :: (ShowToken t, Ord t, ShowErrorComponent e) => ParseError t e -> String-formatError err = posPart ++ " " ++ stripNewlines (parseErrorTextPretty err)- where- pos = NE.head (errorPos err)- posPart = sourcePosPretty pos+formatError :: (Stream s, ShowErrorComponent e) => ParseErrorBundle s e -> String+formatError err = stripNewlines (errorMessageLine err) formatChecks :: Functor f => f RuleCheck -> f Text.Text formatChecks = fmap formatCheck@@ -36,7 +30,7 @@ formatPos :: Filename -> Linenumber -> Text.Text formatPos source line = source <> ":" <> Text.pack (show line) <> " " -printResult :: (ShowToken t, Ord t, ShowErrorComponent e) => Result t e -> IO ()+printResult :: (Stream s, ShowErrorComponent e) => Result s e -> IO () printResult Result {errors, checks} = printErrors >> printChecks where printErrors = mapM_ putStrLn (formatErrors errors)
src/Hadolint/Lint.hs view
@@ -34,10 +34,10 @@ | Codacy deriving (Show, Eq) -printResultsAndExit :: OutputFormat -> Format.Result Char DockerfileError -> IO ()+printResultsAndExit :: OutputFormat -> Format.Result Text DockerfileError -> IO () printResultsAndExit format allResults = do printResult allResults- if allResults /= mempty+ if not . Format.isEmpty $ allResults then exitFailure else exitSuccess where@@ -52,7 +52,7 @@ -- | Performs the process of parsing the dockerfile and analyzing it with all the applicable -- rules, depending on the list of ignored rules. -- Depending on the preferred printing format, it will output the results to stdout-lint :: LintOptions -> NonEmpty.NonEmpty String -> IO (Format.Result Char DockerfileError)+lint :: LintOptions -> NonEmpty.NonEmpty String -> IO (Format.Result Text DockerfileError) lint LintOptions {ignoreRules = ignoreList, rulesConfig} dFiles = do processedFiles <- mapM (lintDockerfile ignoreList) (NonEmpty.toList dFiles) return (results processedFiles)@@ -69,8 +69,7 @@ ignoredRules = ignoreFilter ignoreRules -- | Returns true if the rule should be ignored ignoreFilter :: [IgnoreRule] -> Rules.RuleCheck -> Bool- ignoreFilter rules (Rules.RuleCheck (Rules.Metadata code _ _) _ _ _) =- code `notElem` rules+ ignoreFilter rules (Rules.RuleCheck (Rules.Metadata code _ _) _ _ _) = code `notElem` rules -- | Support UNIX convention of passing "-" instead of "/dev/stdin" parseFilename :: String -> IO (Either Error Dockerfile) parseFilename "-" = Docker.parseStdin
src/Hadolint/Rules.hs view
@@ -4,8 +4,7 @@ module Hadolint.Rules where import Control.Arrow ((&&&))-import Data.List- (dropWhile, foldl', isInfixOf, isPrefixOf, mapAccumL, nub)+import Data.List (dropWhile, foldl', isInfixOf, isPrefixOf, mapAccumL, nub) import Data.List.NonEmpty (toList) import qualified Hadolint.Shell as Shell import Language.Docker.Syntax@@ -88,7 +87,7 @@ mapInstructions :: CheckerWithState state -> state -> Rule mapInstructions f initialState dockerfile = let (_, results) = mapAccumL applyRule initialState dockerfile- in concat results+ in concat results where applyRule state (InstructionPos (OnBuild i) source linenumber) = applyWithState state source linenumber i -- All rules applying to instructions also apply to ONBUILD,@@ -98,7 +97,7 @@ applyWithState state source linenumber i -- Otherwise, normal instructions are not unwrapped applyWithState state source linenumber instruction = let (newState, res) = f state linenumber instruction- in (newState, [RuleCheck m source linenumber False | m <- res])+ in (newState, [RuleCheck m source linenumber False | m <- res]) instructionRule :: Text.Text -> Severity -> Text.Text -> (Instruction Shell.ParsedShell -> Bool) -> Rule@@ -118,9 +117,9 @@ meta = Metadata code severity message constMetadataCheck st ln instr = let (newSt, success) = f st ln instr- in if not success- then (newSt, [meta])- else (newSt, [])+ in if not success+ then (newSt, [meta])+ else (newSt, []) withState :: a -> b -> (a, b) withState st res = (st, res)@@ -234,17 +233,14 @@ aliasMustBe :: (Text.Text -> Bool) -> Instruction a -> Bool aliasMustBe predicate fromInstr = case fromInstr of- From (UntaggedImage _ _ (Just (ImageAlias alias))) -> predicate alias- From (TaggedImage _ _ _ (Just (ImageAlias alias))) -> predicate alias+ From BaseImage {alias = Just (ImageAlias as)} -> predicate as _ -> True fromName :: BaseImage -> Text.Text-fromName (UntaggedImage Image {imageName} _ _) = imageName-fromName (TaggedImage Image {imageName} _ _ _) = imageName+fromName BaseImage {image = Image {imageName}} = imageName fromAlias :: BaseImage -> Maybe ImageAlias-fromAlias (UntaggedImage _ _ alias) = alias-fromAlias (TaggedImage _ _ _ alias) = alias+fromAlias BaseImage {alias} = alias ------------- -- RULES --@@ -328,7 +324,7 @@ detectDoubleUsage state args = let newArgs = extractCommands args newState = Set.union state newArgs- in withState newState (Set.size newState < 2)+ in withState newState (Set.size newState < 2) extractCommands args = Set.fromList [w | w <- Shell.findCommandNames args, w == "curl" || w == "wget"] @@ -365,7 +361,7 @@ rootStages = let indexedInstructions = map (instruction &&& lineNumber) dockerfile (_, usersMap) = foldl' buildMap (Nothing, Map.empty) indexedInstructions- in usersMap+ in usersMap -- -- buildMap (_, st) (From from, _) = (Just from, st) -- Remember the FROM we are currently inspecting@@ -414,8 +410,9 @@ code = "DL3006" severity = WarningC message = "Always tag the version of an image explicitly"- check _ (From (UntaggedImage (Image _ "scratch") _ _)) = True- check line (From (UntaggedImage (Image _ i) _ _)) =+ check _ (From BaseImage {image = (Image _ "scratch")}) = True+ check _ (From BaseImage {digest = Just _}) = True+ check line (From BaseImage {image = (Image _ i), tag = Nothing}) = i `elem` previouslyDefinedAliases line dockerfile check _ _ = True @@ -427,7 +424,7 @@ message = "Using latest is prone to errors if the image will ever update. Pin the version explicitly \ \to a release tag"- check (From (TaggedImage _ tag _ _)) = tag /= "latest"+ check (From BaseImage {tag = Just t}) = t /= "latest" check _ = True aptGetVersionPinned :: Rule@@ -566,7 +563,8 @@ \<package>==<version>`" check (Run args) = argumentsRule (Shell.noCommands forgotToPinVersion) args check _ = True- forgotToPinVersion cmd = isPipInstall cmd && not (hasBuildConstraint cmd) && not (all versionFixed (packages cmd))+ forgotToPinVersion cmd =+ isPipInstall cmd && not (hasBuildConstraint cmd) && not (all versionFixed (packages cmd)) -- Check if the command is a pip* install command, and that specific pacakges are being listed isPipInstall cmd = case Shell.getCommandName cmd of@@ -607,7 +605,8 @@ \<package>@<version>`" check (Run args) = argumentsRule (Shell.noCommands forgotToPinVersion) args check _ = True- forgotToPinVersion cmd = isNpmInstall cmd && installIsFirst cmd && not (all versionFixed (packages cmd))+ forgotToPinVersion cmd =+ isNpmInstall cmd && installIsFirst cmd && not (all versionFixed (packages cmd)) isNpmInstall = Shell.cmdHasArgs "npm" ["install"] installIsFirst cmd = ["install"] `isPrefixOf` Shell.getArgsNoFlags cmd packages cmd = stripInstallPrefix (Shell.getArgsNoFlags cmd)@@ -637,8 +636,8 @@ isAptGetInstall = Shell.cmdHasArgs "apt-get" ["install"] hasYesOption cmd = "y" `elem` allFlags cmd ||- "yes" `elem` allFlags cmd || length (filter (== "q") (allFlags cmd)) > 1 ||- "assume-yes" `elem` allFlags cmd+ "yes" `elem` allFlags cmd ||+ length (filter (== "q") (allFlags cmd)) > 1 || "assume-yes" `elem` allFlags cmd allFlags cmd = snd <$> Shell.getAllFlags cmd aptGetNoRecommends :: Rule@@ -760,11 +759,11 @@ message = "Set the SHELL option -o pipefail before RUN with a pipe in it" check _ _ From {} = (False, True) -- Reset the state each time we find a new FROM check _ _ (Shell args)- | argumentsRule isPowerShell args = (True, True)- | otherwise = (argumentsRule hasPipefailOption args, True)+ | argumentsRule isPowerShell args = (True, True)+ | otherwise = (argumentsRule hasPipefailOption args, True) check False _ (Run args) = (False, argumentsRule notHasPipes args) check st _ _ = (st, True)- isPowerShell (Shell.ParsedShell orig _)= "pwsh" `Text.isPrefixOf` orig+ isPowerShell (Shell.ParsedShell orig _) = "pwsh" `Text.isPrefixOf` orig notHasPipes script = not (Shell.hasPipes script) hasPipefailOption script = not $@@ -784,8 +783,7 @@ code = "DL3026" severity = ErrorC message = "Use only an allowed registry in the FROM image"- check st _ (From (UntaggedImage img _ alias)) = withState (Set.insert alias st) (doCheck st img)- check st _ (From (TaggedImage img _ _ alias)) = withState (Set.insert alias st) (doCheck st img)+ check st _ (From BaseImage {image, alias}) = withState (Set.insert alias st) (doCheck st image) check st _ _ = (st, True) -- |Transforms an Image into a Maybe ImageAlias by using the Image name toImageAlias = Just . ImageAlias . imageName
test/Spec.hs view
@@ -2,8 +2,9 @@ {-# LANGUAGE OverloadedLists #-} import Test.HUnit hiding (Label) import Test.Hspec+import Control.Monad (when, unless) -import Hadolint.Formatter.TTY (formatError)+import Hadolint.Formatter.TTY (formatError, formatChecks) import Hadolint.Rules import Language.Docker.Parser@@ -26,6 +27,10 @@ \7959ed6f7e35f8b1aaa06d1d8259d4ee25aa85a086d5c125480c333183f9deeb" it "explicit tagged with name" $ ruleCatchesNot noLatestTag "FROM debian:jessie AS builder"+ it "untagged digest is not an error" $+ ruleCatchesNot noUntagged "FROM ruby@sha256:f1dbca0f5dbc9"+ it "untagged digest is not an error" $+ ruleCatchesNot noUntagged "FROM ruby:2" it "local aliases are OK to be untagged" $ let dockerFile = [ "FROM golang:1.9.3-alpine3.7 AS build"@@ -1009,24 +1014,30 @@ ruleCatches rule s = assertChecks rule s f where f checks = do- assertEqual "No check for rule found" 1 $ length checks+ when (length checks /= 1) $+ assertFailure $ "Too many errors found: \n" ++ (Text.unpack . Text.unlines . formatChecks $ checks) assertBool "Incorrect line number for result" $ null [c | c <- checks, linenumber c <= 0] onBuildRuleCatches :: HasCallStack => Rule -> Text.Text -> Assertion onBuildRuleCatches rule s = assertOnBuildChecks rule s f where f checks = do- assertEqual "No check for rule found" 1 $ length checks+ when (length checks /= 1) $+ assertFailure (Text.unpack . Text.unlines . formatChecks $ checks) assertBool "Incorrect line number for result" $ null [c | c <- checks, linenumber c <= 0] ruleCatchesNot :: HasCallStack => Rule -> Text.Text -> Assertion ruleCatchesNot rule s = assertChecks rule s f where- f checks = assertEqual ("Error: " ++ errorMessages checks) 0 $ length checks- errorMessages checks = Text.unpack . Text.unlines $ map (message . metadata) checks+ f checks =+ unless (null checks) $+ assertFailure $ "Not expecting the following errors: \n" +++ (Text.unpack . Text.unlines . formatChecks $ checks) onBuildRuleCatchesNot :: HasCallStack => Rule -> Text.Text -> Assertion onBuildRuleCatchesNot rule s = assertOnBuildChecks rule s f where- f checks = assertEqual ("Error: " ++ errorMessages checks) 0 $ length checks- errorMessages checks = Text.unpack . Text.unlines $ map (message . metadata) checks+ f checks =+ unless (null checks) $+ assertFailure $ "Not expecting the following errors: \n" +++ (Text.unpack . Text.unlines . formatChecks $ checks)