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 +8/−8
- hadolint.cabal +8/−5
- src/Hadolint/Formatter/Checkstyle.hs +7/−4
- src/Hadolint/Formatter/Codeclimate.hs +7/−2
- src/Hadolint/Formatter/Format.hs +8/−4
- src/Hadolint/Formatter/Json.hs +1/−1
- src/Hadolint/Formatter/TTY.hs +7/−6
- src/Hadolint/Rules.hs +40/−8
- test/Spec.hs +59/−61
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