packages feed

hadolint 1.6.4 → 1.6.5

raw patch · 9 files changed

+145/−99 lines, 9 filesdep ~language-docker

Dependency ranges changed: language-docker

Files

app/Main.hs view
@@ -17,7 +17,7 @@ import Development.GitRev (gitDescribe) import GHC.Generics import Options.Applicative hiding (ParseError)-import Paths_hadolint (version) -- version from hadolint.cabal file+import qualified Paths_hadolint as Version -- version from hadolint.cabal file import System.Directory        (XdgDirectory(..), doesFileExist, getCurrentDirectory,         getXdgDirectory)@@ -145,18 +145,18 @@ parseFilename s = s  lintDockerfile :: [IgnoreRule] -> String -> IO (Either ParseError [RuleCheck])-lintDockerfile ignoreRules dockerfile = do-    ast <- parseFile $ parseFilename dockerfile+lintDockerfile ignoreRules dockerFile = do+    ast <- parseFile $ parseFilename dockerFile     return (processedFile ast)   where     processedFile = fmap processRules-    processRules dockerfile = filter ignored (analyzeAll dockerfile)-    ignored = ignoreFilter ignoreRules+    processRules fileLines = filter ignoredRules (analyzeAll fileLines)+    ignoredRules = ignoreFilter ignoreRules  getVersion :: String getVersion     | $(gitDescribe) == "UNKNOWN" =-        "Haskell Dockerfile Linter " ++ V.showVersion version ++ "-no-git"+        "Haskell Dockerfile Linter " ++ V.showVersion Version.version ++ "-no-git"     | otherwise = "Haskell Dockerfile Linter " ++ $(gitDescribe)  lint :: LintOptions -> IO ()@@ -185,5 +185,5 @@  -- Helper to analyze AST quickly in GHCI analyzeEither :: Either t Dockerfile -> [RuleCheck]-analyzeEither (Left err) = []-analyzeEither (Right dockerfile) = analyzeAll dockerfile+analyzeEither (Left _) = []+analyzeEither (Right dockerFile) = analyzeAll dockerFile
hadolint.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: f7ed30bdbbcff79f1034336129985743db40eb1859e0c84f7567b0978c70e535+-- hash: b523045510203dbac76d6488afec9838250f551597ef35d8b888adcbdc39e619  name:           hadolint-version:        1.6.4+version:        1.6.5 synopsis:       Dockerfile Linter JavaScript API description:    A smarter Dockerfile linter that helps you build best practice Docker images. category:       Development@@ -28,13 +28,14 @@ library   hs-source-dirs:       src+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints   build-depends:       ShellCheck >=0.4.7     , aeson     , base >=4.8 && <5     , bytestring     , dlist-    , language-docker >=3.0.0+    , language-docker >=5.0.0 && <6     , parsec >=3.1     , split >=0.2     , text@@ -54,13 +55,14 @@   main-is: Main.hs   hs-source-dirs:       app+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints   build-depends:       base >=4.8 && <5     , directory >=1.3.0     , filepath     , gitrev >=1.3.1     , hadolint-    , language-docker >=3.0.0+    , language-docker >=5.0.0 && <6     , optparse-applicative     , parsec >=3.1     , yaml@@ -75,6 +77,7 @@   main-is: Spec.hs   hs-source-dirs:       test+  ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints   build-depends:       HUnit >=1.2     , ShellCheck >=0.4.7@@ -83,7 +86,7 @@     , bytestring >=0.10     , hadolint     , hspec-    , language-docker >=3.0.0+    , language-docker >=5.0.0 && <6     , parsec >=3.1     , split >=0.2   other-modules:
src/Hadolint/Formatter/Checkstyle.hs view
@@ -16,7 +16,7 @@ import Hadolint.Rules (Metadata(..), RuleCheck(..)) import ShellCheck.Interface import Text.Parsec (ParseError)-import Text.Parsec.Error (ParseError, errorPos)+import Text.Parsec.Error (errorPos) import Text.Parsec.Pos  data CheckStyle = CheckStyle@@ -64,12 +64,15 @@         attr "message" msg <>         attr "source" source <>         "/>"-    fileName = case checks of-      [] -> ""-      h:_ -> file h+    fileName =+        case checks of+            [] -> ""+            h:_ -> file h +attr :: String -> String -> Builder.Builder attr name value = Builder.string8 name <> "='" <> Builder.string8 (escape value) <> "' " +escape :: String -> String escape = concatMap doEscape   where     doEscape c =
src/Hadolint/Formatter/Codeclimate.hs view
@@ -15,7 +15,7 @@ import Hadolint.Formatter.Format (Result(..), formatErrorReason) import Hadolint.Rules (Metadata(..), RuleCheck(..)) import ShellCheck.Interface-import Text.Parsec.Error (errorPos)+import Text.Parsec.Error (ParseError, errorPos) import Text.Parsec.Pos  data Issue = Issue@@ -54,16 +54,21 @@             , "severity" .= impact             ] +errorToIssue :: ParseError -> Issue errorToIssue err =     Issue     { checkName = "DL1000"     , description = formatErrorReason err-    , location = LocPos (sourceName pos) (Pos (sourceLine pos) (sourceColumn pos))+    , location = LocPos (sourceName pos) Pos{..}     , impact = severityText ErrorC     }   where     pos = errorPos err+    line = sourceLine pos+    column = sourceColumn pos ++checkToIssue :: RuleCheck -> Issue checkToIssue RuleCheck {..} =     Issue     { checkName = code metadata
src/Hadolint/Formatter/Format.hs view
@@ -8,7 +8,8 @@  import Data.DList (DList, fromList, singleton) import Data.List (sort)-import Data.Monoid+import Data.Monoid (Monoid)+import Data.Semigroup import Hadolint.Rules import ShellCheck.Interface import Text.Parsec.Error@@ -19,9 +20,12 @@     , checks :: DList RuleCheck     } deriving (Eq) +instance Semigroup Result where+    (Result e1 c1) <> (Result e2 c2) = Result (e1 <> e2) (c1 <> c2)+ instance Monoid Result where+    mappend = (<>)     mempty = Result mempty mempty-    mappend (Result e1 c1) (Result e2 c2) = Result (e1 <> e2) (c1 <> c2)  toResult :: Either ParseError [RuleCheck] -> Result toResult res =@@ -30,8 +34,8 @@         Right c -> Result mempty (fromList (sort c))  severityText :: Severity -> String-severityText severity =-    case severity of+severityText s =+    case s of         ErrorC -> "error"         WarningC -> "warning"         InfoC -> "info"
src/Hadolint/Formatter/Json.hs view
@@ -14,7 +14,7 @@ import Hadolint.Rules (Metadata(..), RuleCheck(..)) import ShellCheck.Interface import Text.Parsec (ParseError)-import Text.Parsec.Error (ParseError, errorPos)+import Text.Parsec.Error (errorPos) import Text.Parsec.Pos  data JsonFormat
src/Hadolint/Formatter/TTY.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE NamedFieldPuns #-} module Hadolint.Formatter.TTY     ( printResult     , formatError@@ -21,17 +22,17 @@ formatChecks :: Functor t => t RuleCheck -> t String formatChecks = fmap formatCheck   where-    formatCheck (RuleCheck metadata source linenumber _) =-        formatPos source linenumber ++ code metadata ++ " " ++ message metadata+    formatCheck (RuleCheck meta source line _) =+        formatPos source line ++ code meta ++ " " ++ message meta  formatPos :: Filename -> Linenumber -> String-formatPos source linenumber =-    if linenumber >= 0-        then source ++ ":" ++ show linenumber ++ " "+formatPos source line =+    if line >= 0+        then source ++ ":" ++ show line ++ " "         else source ++ " "  printResult :: Result -> IO ()-printResult (Result errors checks) = printErrors >> printChecks+printResult Result{errors, checks} = printErrors >> printChecks   where     printErrors = mapM_ putStrLn (formatErrors errors)     printChecks = mapM_ putStrLn (formatChecks checks)
src/Hadolint/Rules.hs view
@@ -92,7 +92,7 @@  -- Enforce rules on a dockerfile and return failed checks analyze :: [Rule] -> Dockerfile -> [RuleCheck]-analyze rules dockerfile = filter failed $ concat [r dockerfile | r <- rules]+analyze list dockerfile = filter failed $ concat [r dockerfile | r <- list]   where     failed RuleCheck {metadata = Metadata {code}, linenumber, success} =         not success && not (wasIgnored code linenumber)@@ -119,6 +119,7 @@     space = Parsec.char ' ' <|> Parsec.char '\t'     ruleName = Parsec.many1 (Parsec.choice $ map Parsec.char "DLSC0123456789") +rules :: [Rule] rules =     [ absoluteWorkdir     , shellcheckBash@@ -212,6 +213,7 @@ fromAlias (TaggedImage _ _ alias) = alias fromAlias (DigestedImage _ _ alias) = alias +absoluteWorkdir :: Rule absoluteWorkdir = instructionRule code severity message check   where     code = "DL3000"@@ -222,6 +224,7 @@     check (Workdir _) = False     check _ = True +hasNoMaintainer :: Rule hasNoMaintainer = dockerfileRule code severity message check   where     code = "DL4000"@@ -232,8 +235,10 @@     isMaintainer _ = False  -- Check if a command contains a program call in the Run instruction+usingProgram :: String -> [String] -> Bool usingProgram prog args = or [True | cmd:_ <- bashCommands args, cmd == prog] +multipleCmds :: Rule multipleCmds = dockerfileRule code severity message check   where     code = "DL4003"@@ -245,6 +250,7 @@     isCmd (Cmd _) = True     isCmd _ = False +multipleEntrypoints :: Rule multipleEntrypoints = dockerfileRule code severity message check   where     code = "DL4004"@@ -256,6 +262,7 @@     isEntrypoint (Entrypoint _) = True     isEntrypoint _ = False +wgetOrCurl :: Rule wgetOrCurl = dockerfileRule code severity message check   where     code = "DL4001"@@ -267,6 +274,7 @@     usingCmd cmd (Run (Arguments args)) = cmd `elem` args     usingCmd _ _ = False +invalidCmd :: Rule invalidCmd = instructionRule code severity message check   where     code = "DL3001"@@ -278,6 +286,7 @@     check _ = True     invalidCmds = ["ssh", "vim", "shutdown", "service", "ps", "free", "top", "kill", "mount"] +noRootUser :: Rule noRootUser = instructionRule code severity message check   where     code = "DL3002"@@ -287,6 +296,7 @@         not (isPrefixOf "root:" user || isPrefixOf "0:" user || user == "root" || user == "0")     check _ = True +noCd :: Rule noCd = instructionRule code severity message check   where     code = "DL3003"@@ -295,6 +305,7 @@     check (Run (Arguments args)) = not $ usingProgram "cd" args     check _ = True +noSudo :: Rule noSudo = instructionRule code severity message check   where     code = "DL3004"@@ -305,6 +316,7 @@     check (Run (Arguments args)) = not $ usingProgram "sudo" args     check _ = True +noAptGetUpgrade :: Rule noAptGetUpgrade = instructionRule code severity message check   where     code = "DL3005"@@ -313,16 +325,18 @@     check (Run (Arguments args)) = not $ isInfixOf ["apt-get", "upgrade"] args     check _ = True +noUntagged :: Rule noUntagged dockerfile = instructionRuleLine code severity message check dockerfile   where     code = "DL3006"     severity = WarningC     message = "Always tag the version of an image explicitly"-    check line (From (UntaggedImage (Image _ "scratch") _)) = True+    check _ (From (UntaggedImage (Image _ "scratch") _)) = True     check line (From (UntaggedImage (Image _ i) _)) =         i `elem` previouslyDefinedAliases line dockerfile     check _ _ = True +noLatestTag :: Rule noLatestTag = instructionRule code severity message check   where     code = "DL3007"@@ -333,6 +347,7 @@     check (From (TaggedImage _ tag _)) = tag /= "latest"     check _ = True +aptGetVersionPinned :: Rule aptGetVersionPinned = instructionRule code severity message check   where     code = "DL3008"@@ -354,6 +369,7 @@   where     noOption arg = arg `notElem` ["apt-get", "install"] && not ("-" `isPrefixOf` arg) +aptGetCleanup :: Rule aptGetCleanup dockerfile = instructionRuleState code severity message check Nothing dockerfile   where     code = "DL3009"@@ -384,11 +400,12 @@                 alias `elem` [i | (l, i) <- allImageNames dockerfile, l > line]  dropOptionsWithArg :: [String] -> [String] -> [String]-dropOptionsWithArg os [] = []+dropOptionsWithArg _ [] = [] dropOptionsWithArg os (x:xs)     | x `elem` os = dropOptionsWithArg os (drop 1 xs)     | otherwise = x : dropOptionsWithArg os xs +noApkUpgrade :: Rule noApkUpgrade = instructionRule code severity message check   where     code = "DL3017"@@ -400,6 +417,7 @@ isApkAdd :: [String] -> Bool isApkAdd cmd = ["apk"] `isInfixOf` cmd && ["add"] `isInfixOf` cmd +apkAddVersionPinned :: Rule apkAddVersionPinned = instructionRule code severity message check   where     code = "DL3018"@@ -421,6 +439,7 @@     noOption arg = arg `notElem` options && not ("--" `isPrefixOf` arg)     options = ["apk", "add", "-q", "-p", "-v", "-f", "-t"] +apkAddNoCache :: Rule apkAddNoCache = instructionRule code severity message check   where     code = "DL3019"@@ -432,6 +451,7 @@     check _ = True     hasNoCacheOption cmd = ["--no-cache"] `isInfixOf` cmd +useAdd :: Rule useAdd = instructionRule code severity message check   where     code = "DL3010"@@ -446,6 +466,7 @@     check _ = True     archiveFormats = [".tar", ".gz", ".bz2", "xz"] +invalidPort :: Rule invalidPort = instructionRule code severity message check   where     code = "DL3011"@@ -453,9 +474,10 @@     message = "Valid UNIX ports range from 0 to 65535"     check (Expose (Ports ports)) =         and [p <= 65535 | Port p _ <- ports] &&-        and [l <= 65535 && m <= 65535 | PortRange l m <- ports]+        and [l <= 65535 && m <= 65535 | PortRange l m _ <- ports]     check _ = True +pipVersionPinned :: Rule pipVersionPinned = instructionRule code severity message check   where     code = "DL3013"@@ -513,6 +535,7 @@     npm install git[+http|+https]://<git-host>/<git-user>/<repo-name>[#<commit>|#semver:<semver>]     npm install git+ssh://<git-host>:<git-user>/<repo-name>[#<commit>|#semver:<semver>] -}+npmVersionPinned :: Rule npmVersionPinned = instructionRule code severity message check   where     code = "DL3016"@@ -536,13 +559,15 @@     isVersionedGit package = "#" `isInfixOf` package     hasVersionSymbol package = "@" `isInfixOf` dropScope package       where-        dropScope package =-            if "@" `isPrefixOf` package-                then dropWhile ('/' <) package-                else package+        dropScope pkg =+            if "@" `isPrefixOf` pkg+                then dropWhile ('/' <) pkg+                else pkg +isAptGetInstall :: [String] -> Bool isAptGetInstall cmd = ["apt-get"] `isInfixOf` cmd && ["install"] `isInfixOf` cmd +aptGetYes :: Rule aptGetYes = instructionRule code severity message check   where     code = "DL3014"@@ -555,6 +580,7 @@         ["--yes"] `isInfixOf` cmd || ["-qq"] `isInfixOf` cmd || startsWithYesFlag cmd     startsWithYesFlag cmd = True `elem` ["-y" `isInfixOf` arg | arg <- cmd] +aptGetNoRecommends :: Rule aptGetNoRecommends = instructionRule code severity message check   where     code = "DL3015"@@ -590,6 +616,7 @@ isUrl :: String -> Bool isUrl path = True `elem` [proto `isPrefixOf` path | proto <- ["https://", "http://"]] +copyInsteadAdd :: Rule copyInsteadAdd = instructionRule code severity message check   where     code = "DL3020"@@ -599,6 +626,7 @@         and [isArchive src || isUrl src | SourcePath src <- toList srcs]     check _ = True +copyEndingSlash :: Rule copyEndingSlash = instructionRule code severity message check   where     code = "DL3021"@@ -610,6 +638,7 @@     check _ = True     endsWithSlash (TargetPath t) = last t == '/' -- it is safe to use last, as the target is never empty +copyFromExists :: Rule copyFromExists dockerfile = instructionRuleLine code severity message check dockerfile   where     code = "DL3022"@@ -618,6 +647,7 @@     check l (Copy (CopyArgs _ _ _ (CopySource s))) = s `elem` previouslyDefinedAliases l dockerfile     check _ _ = True +copyFromAnother :: Rule copyFromAnother = instructionRuleState code severity message check Nothing   where     code = "DL3023"@@ -632,6 +662,7 @@         withState st (aliasMustBe (/= stageName) fromInstr) -- Cannot copy from itself!     check state _ _ = withState state True +fromAliasUnique :: Rule fromAliasUnique dockerfile = instructionRuleLine code severity message check dockerfile   where     code = "DL3024"@@ -640,6 +671,7 @@     check line = aliasMustBe (not . alreadyTaken line)     alreadyTaken line alias = alias `elem` previouslyDefinedAliases line dockerfile +useShell :: Rule useShell = instructionRule code severity message check   where     code = "DL4005"
test/Spec.hs view
@@ -6,9 +6,6 @@  import Language.Docker.Parser -import Data.List (find)-import Data.Maybe (fromMaybe, isJust)- main :: IO () main =     hspec $ do@@ -25,7 +22,7 @@             it "explicit tagged with name" $               ruleCatchesNot noLatestTag "FROM debian:jessie AS builder"             it "local aliases are OK to be untagged" $-                let dockerfile =+                let dockerFile =                         [ "FROM golang:1.9.3-alpine3.7 AS build"                         , "RUN foo"                         , "FROM build as unit-test"@@ -33,9 +30,9 @@                         , "FROM alpine:3.7"                         , "RUN baz"                         ]-                in ruleCatchesNot noUntagged $ unlines dockerfile+                in ruleCatchesNot noUntagged $ unlines dockerFile             it "other untagged cases are not ok" $-                let dockerfile =+                let dockerFile =                         [ "FROM golang:1.9.3-alpine3.7 AS build"                         , "RUN foo"                         , "FROM node as unit-test"@@ -43,7 +40,7 @@                         , "FROM alpine:3.7"                         , "RUN baz"                         ]-                in ruleCatches noUntagged $ unlines dockerfile+                in ruleCatches noUntagged $ unlines dockerFile         --         describe "no root or sudo rules" $ do             it "sudo" $ ruleCatches noSudo "RUN sudo apt-get update"@@ -65,74 +62,74 @@             it "apt-get version pinning" $                 ruleCatches aptGetVersionPinned "RUN apt-get update && apt-get install python"             it "apt-get no cleanup" $-                let dockerfile =+                let dockerFile =                         [ "FROM scratch"                         , "RUN apt-get update && apt-get install python"                         ]-                in ruleCatches aptGetCleanup $ unlines dockerfile+                in ruleCatches aptGetCleanup $ unlines dockerFile             it "apt-get cleanup in stage image" $-                let dockerfile =+                let dockerFile =                         [ "FROM ubuntu as foo"                         , "RUN apt-get update && apt-get install python"                         , "FROM scratch"                         , "RUN echo hey!"                         ]-                in ruleCatchesNot aptGetCleanup $ unlines dockerfile+                in ruleCatchesNot aptGetCleanup $ unlines dockerFile             it "apt-get no cleanup in last stage" $-                let dockerfile =+                let dockerFile =                         [ "FROM ubuntu as foo"                         , "RUN hey!"                         , "FROM scratch"                         , "RUN apt-get update && apt-get install python"                         ]-                in ruleCatches aptGetCleanup $ unlines dockerfile+                in ruleCatches aptGetCleanup $ unlines dockerFile             it "apt-get no cleanup in intermediate stage" $-                let dockerfile =+                let dockerFile =                         [ "FROM ubuntu as foo"                         , "RUN apt-get update && apt-get install python"                         , "FROM foo"                         , "RUN hey!"                         ]-                in ruleCatches aptGetCleanup $ unlines dockerfile+                in ruleCatches aptGetCleanup $ unlines dockerFile             it "now warn apt-get cleanup in intermediate stage that cleans lists" $-                let dockerfile =+                let dockerFile =                         [ "FROM ubuntu as foo"                         , "RUN apt-get update && apt-get install python && rm -rf /var/lib/apt/lists/*"                         , "FROM foo"                         , "RUN hey!"                         ]-                in ruleCatchesNot aptGetCleanup $ unlines dockerfile+                in ruleCatchesNot aptGetCleanup $ unlines dockerFile             it "no warn apt-get cleanup in intermediate stage when stage not used later" $-                let dockerfile =+                let dockerFile =                         [ "FROM ubuntu as foo"                         , "RUN apt-get update && apt-get install python"                         , "FROM scratch"                         , "RUN hey!"                         ]-                in ruleCatchesNot aptGetCleanup $ unlines dockerfile+                in ruleCatchesNot aptGetCleanup $ unlines dockerFile             it "apt-get cleanup" $-                let dockerfile =+                let dockerFile =                         [ "FROM scratch"                         , "RUN apt-get update && apt-get install python && rm -rf /var/lib/apt/lists/*"                         ]-                in ruleCatchesNot aptGetCleanup $ unlines dockerfile+                in ruleCatchesNot aptGetCleanup $ unlines dockerFile              it "apt-get pinned chained" $-                let dockerfile =+                let dockerFile =                         [ "RUN apt-get update \\"                         , " && apt-get -yqq --no-install-recommends install nodejs=0.10 \\"                         , " && rm -rf /var/lib/apt/lists/*"                         ]-                in ruleCatchesNot aptGetVersionPinned $ unlines dockerfile+                in ruleCatchesNot aptGetVersionPinned $ unlines dockerFile             it "apt-get pinned regression" $-                let dockerfile =+                let dockerFile =                         [ "RUN apt-get update && apt-get install --no-install-recommends -y \\"                         , "python-demjson=2.2.2* \\"                         , "wget=1.16.1* \\"                         , "git=1:2.5.0* \\"                         , "ruby=1:2.1.*"                         ]-                in ruleCatchesNot aptGetVersionPinned $ unlines dockerfile+                in ruleCatchesNot aptGetVersionPinned $ unlines dockerFile             it "has deprecated maintainer" $                 ruleCatches hasNoMaintainer "FROM busybox\nMAINTAINER hudu@mail.com"         --@@ -142,34 +139,34 @@             it "apk add no version pinning single" $                 ruleCatchesNot apkAddVersionPinned "RUN apk add flex=2.6.4-r1"             it "apk add version pinned chained" $-                let dockerfile =+                let dockerFile =                         [ "RUN apk add --no-cache flex=2.6.4-r1 \\"                         , " && pip install -r requirements.txt"                         ]-                in ruleCatchesNot apkAddVersionPinned $ unlines dockerfile+                in ruleCatchesNot apkAddVersionPinned $ unlines dockerFile             it "apk add version pinned regression" $-                let dockerfile =+                let dockerFile =                         [ "RUN apk add --no-cache \\"                         , "flex=2.6.4-r1 \\"                         , "libffi=3.2.1-r3 \\"                         , "python2=2.7.13-r1 \\"                         , "libbz2=1.0.6-r5"                         ]-                in ruleCatchesNot apkAddVersionPinned $ unlines dockerfile+                in ruleCatchesNot apkAddVersionPinned $ unlines dockerFile             it "apk add version pinned regression - one missed" $-                let dockerfile =+                let dockerFile =                         [ "RUN apk add --no-cache \\"                         , "flex=2.6.4-r1 \\"                         , "libffi \\"                         , "python2=2.7.13-r1 \\"                         , "libbz2=1.0.6-r5"                         ]-                in ruleCatches apkAddVersionPinned $ unlines dockerfile+                in ruleCatches apkAddVersionPinned $ unlines dockerFile             it "apk add with --no-cache" $ ruleCatches apkAddNoCache "RUN apk add flex=2.6.4-r1"             it "apk add without --no-cache" $                 ruleCatchesNot apkAddNoCache "RUN apk add --no-cache flex=2.6.4-r1"             it "apk add virtual package" $-                let dockerfile =+                let dockerFile =                         [ "RUN apk add \\"                         , "--virtual build-dependencies \\"                         , "python-dev=1.1.1 build-base=2.2.2 wget=3.3.3 \\"@@ -177,7 +174,7 @@                         , "&& python setup.py install \\"                         , "&& apk del build-dependencies"                         ]-                in ruleCatchesNot apkAddVersionPinned $ unlines dockerfile+                in ruleCatchesNot apkAddVersionPinned $ unlines dockerFile         --         describe "EXPOSE rules" $ do             it "invalid port" $ ruleCatches invalidPort "EXPOSE 80000"@@ -311,7 +308,7 @@                     aptGetVersionPinned                     "RUN apt-get -y --no-install-recommends install nodejs=0.10"             it "apt-get tolerate target-release" $-                let dockerfile =+                let dockerFile =                         [ "RUN set -e &&\\"                         , " echo \"deb http://http.debian.net/debian jessie-backports main\" \                           \> /etc/apt/sources.list.d/jessie-backports.list &&\\"@@ -320,7 +317,7 @@                           \openjdk-8-jdk=8u131-b11-1~bpo8+1 &&\\"                         , " rm -rf /var/lib/apt/lists/*"                         ]-                in ruleCatchesNot aptGetVersionPinned $ unlines dockerfile+                in ruleCatchesNot aptGetVersionPinned $ unlines dockerFile              it "has maintainer" $ ruleCatches hasNoMaintainer "FROM debian\nMAINTAINER Lukas"             it "has maintainer first" $ ruleCatches hasNoMaintainer "MAINTAINER Lukas\nFROM DEBIAN"@@ -353,57 +350,57 @@         describe "copy from existing alias" $ do             it "warn on missing alias" $ ruleCatches copyFromExists "COPY --from=foo bar ."             it "warn on alias defined after" $-                let dockerfile =+                let dockerFile =                         [ "FROM scratch"                         , "COPY --from=build foo ."                         , "FROM node as build"                         , "RUN baz"                         ]-                in ruleCatches copyFromExists $ unlines dockerfile+                in ruleCatches copyFromExists $ unlines dockerFile             it "don't warn on correctly defined aliases" $-                let dockerfile =+                let dockerFile =                         [ "FROM scratch as build"                         , "RUN foo"                         , "FROM node"                         , "COPY --from=build foo ."                         , "RUN baz"                         ]-                in ruleCatchesNot copyFromExists $ unlines dockerfile+                in ruleCatchesNot copyFromExists $ unlines dockerFile         --         describe "copy from own FROM" $ do             it "warn on copying from your the same FROM" $-                let dockerfile =+                let dockerFile =                         [ "FROM node as foo"                         , "COPY --from=foo bar ."                         ]-                in ruleCatches copyFromAnother $ unlines dockerfile+                in ruleCatches copyFromAnother $ unlines dockerFile             it "don't warn on copying form other sources" $-                let dockerfile =+                let dockerFile =                         [ "FROM scratch as build"                         , "RUN foo"                         , "FROM node as run"                         , "COPY --from=build foo ."                         , "RUN baz"                         ]-                in ruleCatchesNot copyFromAnother $ unlines dockerfile+                in ruleCatchesNot copyFromAnother $ unlines dockerFile         --         describe "Duplicate aliases" $ do             it "warn on duplicate aliases" $-                let dockerfile =+                let dockerFile =                         [ "FROM node as foo"                         , "RUN something"                         , "FROM scratch as foo"                         , "RUN something"                         ]-                in ruleCatches fromAliasUnique $ unlines dockerfile+                in ruleCatches fromAliasUnique $ unlines dockerFile             it "don't warn on unique aliases" $-                let dockerfile =+                let dockerFile =                         [ "FROM scratch as build"                         , "RUN foo"                         , "FROM node as run"                         , "RUN baz"                         ]-                in ruleCatchesNot fromAliasUnique $ unlines dockerfile+                in ruleCatchesNot fromAliasUnique $ unlines dockerFile         --         describe "format error" $             it "display error after line pos" $ do@@ -415,51 +412,52 @@         --         describe "Rules can be ignored with inline comments" $ do             it "ignores single rule" $-                let dockerfile =+                let dockerFile =                         [ "FROM ubuntu"                         , "# hadolint ignore=DL3002"                         , "USER root"                         ]-                in ruleCatchesNot noRootUser $ unlines dockerfile+                in ruleCatchesNot noRootUser $ unlines dockerFile             it "ignores only the given rule" $-                let dockerfile =+                let dockerFile =                         [ "# hadolint ignore=DL3001"                         , "USER root"                         ]-                in ruleCatches noRootUser $ unlines dockerfile+                in ruleCatches noRootUser $ unlines dockerFile             it "ignores only the given rule, when multiple passed" $-                let dockerfile =+                let dockerFile =                         [ "# hadolint ignore=DL3001,DL3002"                         , "USER root"                         ]-                in ruleCatchesNot noRootUser $ unlines dockerfile+                in ruleCatchesNot noRootUser $ unlines dockerFile             it "ignores the rule only if directly above the instruction" $-                let dockerfile =+                let dockerFile =                         [ "# hadolint ignore=DL3001,DL3002"                         , "FROM ubuntu"                         , "USER root"                         ]-                in ruleCatches noRootUser $ unlines dockerfile+                in ruleCatches noRootUser $ unlines dockerFile             it "won't ignore the rule if passed invalid rule names" $-                let dockerfile =+                let dockerFile =                         [ "# hadolint ignore=crazy,DL3002"                         , "USER root"                         ]-                in ruleCatches noRootUser $ unlines dockerfile+                in ruleCatches noRootUser $ unlines dockerFile             it "ignores multiple rules correctly, even with some extra whitespace" $-                let dockerfile =+                let dockerFile =                         [ "FROM node as foo"                         , "# hadolint ignore=DL3023, DL3021"                         , "COPY --from=foo bar baz ."                         ]                 in do-                  ruleCatchesNot copyFromAnother $ unlines dockerfile-                  ruleCatchesNot copyEndingSlash $ unlines dockerfile+                  ruleCatchesNot copyFromAnother $ unlines dockerFile+                  ruleCatchesNot copyEndingSlash $ unlines dockerFile +assertChecks :: Rule -> String -> ([RuleCheck] -> IO a) -> IO a assertChecks rule s f =     case parseString (s ++ "\n") of         Left err -> assertFailure $ show err-        Right dockerfile -> f $ analyze [rule] dockerfile+        Right dockerFile -> f $ analyze [rule] dockerFile  -- Assert a failed check exists for rule ruleCatches :: Rule -> String -> Assertion