packages feed

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 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)