hadolint 1.3.0 → 1.4.0
raw patch · 4 files changed
+171/−12 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Hadolint.Rules: aliasMustBe :: (String -> Bool) -> Instruction -> Bool
+ Hadolint.Rules: allAliasedImages :: Dockerfile -> [(Linenumber, ImageAlias)]
+ Hadolint.Rules: copyFromAnother :: Rule
+ Hadolint.Rules: copyFromExists :: Dockerfile -> [RuleCheck]
+ Hadolint.Rules: fromAliasUnique :: Dockerfile -> [RuleCheck]
+ Hadolint.Rules: instructionRuleLine :: String -> Severity -> String -> (Linenumber -> Instruction -> Bool) -> Rule
+ Hadolint.Rules: instructionRuleState :: String -> Severity -> String -> (state -> Linenumber -> Instruction -> (state, Bool)) -> state -> Rule
+ Hadolint.Rules: previouslyDefinedAliases :: Linenumber -> Dockerfile -> [String]
- Hadolint.Rules: mapInstructions :: Metadata -> (Instruction -> Bool) -> Rule
+ Hadolint.Rules: mapInstructions :: Metadata -> (state -> Linenumber -> Instruction -> (state, Bool)) -> state -> Rule
- Hadolint.Rules: noUntagged :: Rule
+ Hadolint.Rules: noUntagged :: Dockerfile -> [RuleCheck]
Files
- README.md +3/−0
- hadolint.cabal +2/−2
- src/Hadolint/Rules.hs +90/−9
- test/Spec.hs +76/−1
README.md view
@@ -122,6 +122,9 @@ | [DL3019](https://github.com/hadolint/hadolint/wiki/DL3019) | Use the `--no-cache` switch to avoid the need to use `--update` and remove `/var/cache/apk/*` when done installing packages. | | [DL3020](https://github.com/hadolint/hadolint/wiki/DL3020) | Use `COPY` instead of `ADD` for files and folders. | | [DL3021](https://github.com/hadolint/hadolint/wiki/DL3021) | `COPY` with more than 2 arguments requires the last argument to end with `/` |+| [DL3022](https://github.com/hadolint/hadolint/wiki/DL3022) | `COPY --from` should reference a previously defined `FROM` alias |+| [DL3023](https://github.com/hadolint/hadolint/wiki/DL3023) | `COPY --from` cannot reference its own `FROM` alias |+| [DL3024](https://github.com/hadolint/hadolint/wiki/DL3024) | `FROM` aliases (stage names) must be unique | | [DL4000](https://github.com/hadolint/hadolint/wiki/DL4000) | MAINTAINER is deprecated. | | [DL4001](https://github.com/hadolint/hadolint/wiki/DL4001) | Either use Wget or Curl but not both. | | [DL4003](https://github.com/hadolint/hadolint/wiki/DL4003) | Multiple `CMD` instructions found. |
hadolint.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 36374cf97e8c22f6971eb0142316c4cb1e42f9d83fba7b06c6558bb0ce1ccf24+-- hash: 27e3b32590f30c0792f229a5aebdd2796f20c9ef9889029a1f2c6dbd2d986bcf name: hadolint-version: 1.3.0+version: 1.4.0 synopsis: Dockerfile Linter JavaScript API description: A smarter Dockerfile linter that helps you build best practice Docker images. category: Development
src/Hadolint/Rules.hs view
@@ -1,6 +1,8 @@ module Hadolint.Rules where -import Data.List (intercalate, isInfixOf, isPrefixOf, isSuffixOf)+import Control.Arrow ((&&&))+import Data.List+ (intercalate, isInfixOf, isPrefixOf, isSuffixOf, mapAccumL) import Data.List.NonEmpty (toList) import Data.List.Split (splitOn, splitOneOf) import Data.Maybe (fromMaybe, isJust, mapMaybe)@@ -41,14 +43,35 @@ -- Apply a function on each instruction and create a check -- for the according line number-mapInstructions :: Metadata -> (Instruction -> Bool) -> Rule-mapInstructions metadata f = map applyRule+mapInstructions ::+ Metadata -> (state -> Linenumber -> Instruction -> (state, Bool)) -> state -> Rule+mapInstructions metadata f initialState dockerfile =+ let (_, results) = mapAccumL applyRule initialState dockerfile+ in results where- applyRule (InstructionPos i source linenumber) = RuleCheck metadata source linenumber (f i)+ applyRule state (InstructionPos i source linenumber) =+ let (newState, res) = f state linenumber i+ in (newState, RuleCheck metadata source linenumber res) instructionRule :: String -> Severity -> String -> (Instruction -> Bool) -> Rule-instructionRule code severity message = mapInstructions $ Metadata code severity message+instructionRule code severity message check =+ instructionRuleLine code severity message (const check) +instructionRuleLine :: String -> Severity -> String -> (Linenumber -> Instruction -> Bool) -> Rule+instructionRuleLine code severity message check =+ instructionRuleState code severity message checkAndDropState ()+ where+ checkAndDropState state line instr = (state, check line instr)++instructionRuleState ::+ String+ -> Severity+ -> String+ -> (state -> Linenumber -> Instruction -> (state, Bool))+ -> state+ -> Rule+instructionRuleState code severity message = mapInstructions (Metadata code severity message)+ dockerfileRule :: String -> Severity -> String -> ([Instruction] -> Bool) -> Rule dockerfileRule code severity message f = rule where@@ -69,6 +92,9 @@ , invalidCmd , copyInsteadAdd , copyEndingSlash+ , copyFromExists+ , copyFromAnother+ , fromAliasUnique , noRootUser , noCd , noSudo@@ -112,6 +138,31 @@ bashCommands :: [String] -> [[String]] bashCommands = splitOneOf [";", "|", "&&"] +allAliasedImages :: Dockerfile -> [(Linenumber, ImageAlias)]+allAliasedImages dockerfile =+ [(l, a) | (l, From (UntaggedImage _ (Just a))) <- instr] +++ [(l, a) | (l, From (TaggedImage _ _ (Just a))) <- instr] +++ [(l, a) | (l, From (DigestedImage _ _ (Just a))) <- instr]+ where+ instr = fmap (lineNumber &&& instruction) dockerfile++-- | Returns a list of all image aliases in FROM instructions that+-- are defined before the given line number.+previouslyDefinedAliases :: Linenumber -> Dockerfile -> [String]+previouslyDefinedAliases line dockerfile =+ [i | (l, ImageAlias i) <- allAliasedImages dockerfile, l < line]++-- | Returns the result of running the check function on the image alias+-- name, if the passed instruction is a FROM instruction with a stage alias.+-- Otherwise, returns True.+aliasMustBe :: (String -> Bool) -> Instruction -> Bool+aliasMustBe predicate fromInstr =+ case fromInstr of+ From (UntaggedImage _ (Just (ImageAlias alias))) -> predicate alias+ From (TaggedImage _ _ (Just (ImageAlias alias))) -> predicate alias+ From (DigestedImage _ _ (Just (ImageAlias alias))) -> predicate alias+ _ -> True+ absoluteWorkdir = instructionRule code severity message check where code = "DL3000"@@ -211,14 +262,14 @@ check (Run args) = not $ isInfixOf ["apt-get", "upgrade"] args check _ = True -noUntagged = instructionRule code severity message check+noUntagged dockerfile = instructionRuleLine code severity message check dockerfile where code = "DL3006" severity = WarningC message = "Always tag the version of an image explicitly."- check (From (UntaggedImage image _)) = image == "scratch"- check (From TaggedImage {}) = True- check _ = True+ check line (From (UntaggedImage "scratch" _)) = True+ check line (From (UntaggedImage i _)) = i `elem` previouslyDefinedAliases line dockerfile+ check _ _ = True noLatestTag = instructionRule code severity message check where@@ -474,6 +525,36 @@ | otherwise = True check _ = True endsWithSlash (TargetPath t) = last t == '/' -- it is safe to use last, as the target is never empty++copyFromExists dockerfile = instructionRuleLine code severity message check dockerfile+ where+ code = "DL3022"+ severity = WarningC+ message = "COPY --from should reference a previously defined FROM alias"+ check l (Copy (CopyArgs _ _ _ (CopySource s))) = s `elem` previouslyDefinedAliases l dockerfile+ check _ _ = True++copyFromAnother = instructionRuleState code severity message check Nothing+ where+ code = "DL3023"+ severity = ErrorC+ message = "COPY --from should reference a previously defined FROM alias"+ withState st res = (st, res)+ -- | 'check' returns a tuple (state, check_result)+ -- The state in this case is the FROM instruction where the current instruction we are+ -- inspecting is nested in.+ check _ _ f@(From _) = withState (Just f) True -- Remember the last FROM instruction found+ check st@(Just fromInstr) _ (Copy (CopyArgs _ _ _ (CopySource stageName))) =+ withState st (aliasMustBe (/= stageName) fromInstr) -- Cannot copy from itself!+ check state _ _ = withState state True++fromAliasUnique dockerfile = instructionRuleLine code severity message check dockerfile+ where+ code = "DL3024"+ severity = ErrorC+ message = "FROM aliases (stage names) must be unique"+ check line = aliasMustBe (not . alreadyTaken line)+ alreadyTaken line alias = alias `elem` previouslyDefinedAliases line dockerfile useShell = instructionRule code severity message check where
test/Spec.hs view
@@ -23,7 +23,27 @@ "FROM debian@sha256:\ \7959ed6f7e35f8b1aaa06d1d8259d4ee25aa85a086d5c125480c333183f9deeb" it "explicit tagged with name" $- ruleCatchesNot noLatestTag "FROM debian:jessie AS builder"+ ruleCatchesNot noLatestTag "FROM debian:jessie AS builder"+ it "local aliases are OK to be untagged" $+ let dockerfile =+ [ "FROM golang:1.9.3-alpine3.7 AS build"+ , "RUN foo"+ , "FROM build as unit-test"+ , "RUN bar"+ , "FROM alpine:3.7"+ , "RUN baz"+ ]+ in ruleCatchesNot noUntagged $ unlines dockerfile+ it "other untagged cases are not ok" $+ let dockerfile =+ [ "FROM golang:1.9.3-alpine3.7 AS build"+ , "RUN foo"+ , "FROM node as unit-test"+ , "RUN bar"+ , "FROM alpine:3.7"+ , "RUN baz"+ ]+ in ruleCatches noUntagged $ unlines dockerfile -- describe "no root or sudo rules" $ do it "sudo" $ ruleCatches noSudo "RUN sudo apt-get update"@@ -266,6 +286,61 @@ it "no warn on 2 args" $ ruleCatchesNot copyEndingSlash "COPY foo bar" it "warn on 3 args" $ ruleCatches copyEndingSlash "COPY foo bar baz" it "no warn on 3 args" $ ruleCatchesNot copyEndingSlash "COPY foo bar baz/"+ --+ 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 =+ [ "FROM scratch"+ , "COPY --from=build foo ."+ , "FROM node as build"+ , "RUN baz"+ ]+ in ruleCatches copyFromExists $ unlines dockerfile+ it "don't warn on correctly defined aliases" $+ let dockerfile =+ [ "FROM scratch as build"+ , "RUN foo"+ , "FROM node"+ , "COPY --from=build foo ."+ , "RUN baz"+ ]+ in ruleCatchesNot copyFromExists $ unlines dockerfile+ --+ describe "copy from own FROM" $ do+ it "warn on copying from your the same FROM" $+ let dockerfile =+ [ "FROM node as foo"+ , "COPY --from=foo bar ."+ ]+ in ruleCatches copyFromAnother $ unlines dockerfile+ it "don't warn on copying form other sources" $+ let dockerfile =+ [ "FROM scratch as build"+ , "RUN foo"+ , "FROM node as run"+ , "COPY --from=build foo ."+ , "RUN baz"+ ]+ in ruleCatchesNot copyFromAnother $ unlines dockerfile+ --+ describe "Duplicate aliases" $ do+ it "warn on duplicate aliases" $+ let dockerfile =+ [ "FROM node as foo"+ , "RUN something"+ , "FROM scratch as foo"+ , "RUN something"+ ]+ in ruleCatches fromAliasUnique $ unlines dockerfile+ it "don't warn on unique aliases" $+ let dockerfile =+ [ "FROM scratch as build"+ , "RUN foo"+ , "FROM node as run"+ , "RUN baz"+ ]+ in ruleCatchesNot fromAliasUnique $ unlines dockerfile -- describe "format error" $ it "display error after line pos" $ do