hadolint 1.18.0 → 1.18.1
raw patch · 5 files changed
+139/−19 lines, 5 filesdep ~language-docker
Dependency ranges changed: language-docker
Files
- README.md +12/−0
- hadolint.cabal +5/−5
- src/Hadolint/Rules.hs +86/−11
- src/Hadolint/Shell.hs +5/−1
- test/Spec.hs +31/−2
README.md view
@@ -121,6 +121,13 @@ hadolint --config /path/to/config.yaml Dockerfile ``` +To pass a custom configuration file (using relative or absolute path) to a container,+use the following command:++```bash+docker run --rm -i -v ./your/path/to/hadolint.yaml:/root/.config/hadolint.yaml hadolint/hadolint < Dockerfile+```+ ## Inline ignores It is also possible to ignore rules by using a special comment directly above the Dockerfile@@ -191,6 +198,11 @@ | [DL3026](https://github.com/hadolint/hadolint/wiki/DL3026) | Use only an allowed registry in the FROM image | | [DL3027](https://github.com/hadolint/hadolint/wiki/DL3027) | Do not use `apt` as it is meant to be a end-user tool, use `apt-get` or `apt-cache` instead | | [DL3028](https://github.com/hadolint/hadolint/wiki/DL3028) | Pin versions in gem install. Instead of `gem install <gem>` use `gem install <gem>:<version>` |+| [DL3029](https://github.com/hadolint/hadolint/wiki/DL3029) | Do not use --platform flag with FROM. |+| [DL3030](https://github.com/hadolint/hadolint/wiki/DL3030) | Use the `-y` switch to avoid manual input `yum install -y <package>` |+| [DL3031](https://github.com/hadolint/hadolint/wiki/DL3031) | Do not use `yum update` |+| [DL3032](https://github.com/hadolint/hadolint/wiki/DL3032) | `yum clean all` missing after yum command. |+| [DL3033](https://github.com/hadolint/hadolint/wiki/DL3033) | Specify version with `yum install -y <package>-<version>` | | [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
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 417883d2c2a9328b25a066609f646f6e0169178dd2f403009304327ddeef2720+-- hash: f2454ac92e402e2007f598a079c8998996473f8fc6501b81e3eb1f9cd98d5f56 name: hadolint-version: 1.18.0+version: 1.18.1 synopsis: Dockerfile Linter JavaScript API description: A smarter Dockerfile linter that helps you build best practice Docker images. category: Development@@ -57,7 +57,7 @@ , containers , directory >=1.3.0 , filepath- , language-docker >=9.1.0 && <10+ , language-docker >=9.1.2 && <10 , megaparsec >=7.0 , mtl , split >=0.2@@ -77,7 +77,7 @@ , containers , gitrev >=1.3.1 , hadolint- , language-docker >=9.1.0 && <10+ , language-docker >=9.1.2 && <10 , megaparsec >=7.0 , optparse-applicative >=0.14.0 , text@@ -103,7 +103,7 @@ , bytestring >=0.10 , hadolint , hspec- , language-docker >=9.1.0 && <10+ , language-docker >=9.1.2 && <10 , megaparsec >=7.0 , split >=0.2 , text
src/Hadolint/Rules.hs view
@@ -185,6 +185,7 @@ , noApkUpgrade , noLatestTag , noUntagged+ , noPlatformFlag , aptGetVersionPinned , aptGetCleanup , apkAddVersionPinned@@ -204,6 +205,10 @@ , usePipefail , noApt , gemVersionPinned+ , yumYes+ , noYumUpdate+ , yumCleanup+ , yumVersionPinned ] optionalRules :: RulesConfig -> [Rule]@@ -589,13 +594,16 @@ check _ = True 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@(Shell.Command name _ _) = "pip" `Text.isPrefixOf` name && relevantInstall cmd+ -- Check if the command is a pip* install command, and that specific packages are being listed+ isPipInstall cmd = (isStdPipInstall cmd || isPythonPipInstall cmd) && not (requirementInstall cmd)+ isStdPipInstall cmd@(Shell.Command name _ _) = "pip" `Text.isPrefixOf` name && ["install"] `isInfixOf` Shell.getArgs cmd+ isPythonPipInstall cmd@(Shell.Command name _ _) = "python" `Text.isPrefixOf` name &&+ ["-m", "pip", "install"] `isInfixOf` Shell.getArgs cmd -- If the user is installing requirements from a file or just the local module, then we are not interested -- in running this rule- relevantInstall cmd =- ["install"] `isInfixOf` Shell.getArgs cmd &&- not (["--requirement"] `isInfixOf` Shell.getArgs cmd || ["-r"] `isInfixOf` Shell.getArgs cmd || ["."] `isInfixOf` Shell.getArgs cmd)+ requirementInstall cmd = ["--requirement"] `isInfixOf` Shell.getArgs cmd ||+ ["-r"] `isInfixOf` Shell.getArgs cmd ||+ ["."] `isInfixOf` Shell.getArgs cmd hasBuildConstraint cmd = Shell.hasFlag "constraint" cmd || Shell.hasFlag "c" cmd packages cmd = stripInstallPrefix $@@ -612,10 +620,12 @@ , "platform" , "prefix" , "progress-bar"+ , "proxy" , "python-version" , "root" , "src" , "t", "target"+ , "trusted-host" , "upgrade-strategy" ] cmd versionFixed package = hasVersionSymbol package || isVersionedGit package@@ -624,7 +634,7 @@ hasVersionSymbol package = or [s `Text.isInfixOf` package | s <- versionSymbols] stripInstallPrefix :: [Text.Text] -> [Text.Text]-stripInstallPrefix = dropWhile (== "install")+stripInstallPrefix cmd = dropWhile (== "install") (dropWhile (/= "install") cmd) {-| Rule for pinning NPM packages to version, tag, or commit@@ -698,8 +708,8 @@ isArchive :: Text.Text -> Bool isArchive path =- True `elem`- [ ftype `Text.isSuffixOf` path+ or+ ([ ftype `Text.isSuffixOf` path | ftype <- [ ".tar" , ".gz"@@ -717,10 +727,10 @@ , ".Z" , ".tZ" ]- ]+ ]) isUrl :: Text.Text -> Bool-isUrl path = True `elem` [proto `Text.isPrefixOf` path | proto <- ["https://", "http://"]]+isUrl path = or ([proto `Text.isPrefixOf` path | proto <- ["https://", "http://"]]) copyInsteadAdd :: Rule copyInsteadAdd = instructionRule code severity message check@@ -742,7 +752,7 @@ | length sources > 1 = endsWithSlash t | otherwise = True check _ = True- endsWithSlash (TargetPath t) = Text.last t == '/' -- it is safe to use last, as the target is never empty+ endsWithSlash (TargetPath t) = not (Text.null t) && Text.last t == '/' copyFromExists :: Rule copyFromExists dockerfile = instructionRuleLine code severity message check dockerfile@@ -862,6 +872,71 @@ check (Run (RunArgs args _)) = argumentsRule (all versionFixed . gems) args check _ = True versionFixed package = ":" `Text.isInfixOf` package++noPlatformFlag :: Rule+noPlatformFlag = instructionRule code severity message check+ where+ code = "DL3029"+ severity = WarningC+ message = "Do not use --platform flag with FROM"+ check (From BaseImage {platform = Just p}) = p == ""+ check _ = True++yumYes :: Rule+yumYes = instructionRule code severity message check+ where+ code = "DL3030"+ severity = WarningC+ message = "Use the -y switch to avoid manual input `yum install -y <package`"+ check (Run (RunArgs args _)) = argumentsRule (Shell.noCommands forgotYumYesOption) args+ check _ = True+ forgotYumYesOption cmd = isYumInstall cmd && not (hasYesOption cmd)+ isYumInstall = Shell.cmdHasArgs "yum" ["install", "groupinstall", "localinstall"]+ hasYesOption = Shell.hasAnyFlag ["y", "assumeyes"]++noYumUpdate :: Rule+noYumUpdate = instructionRule code severity message check+ where+ code = "DL3031"+ severity = ErrorC+ message = "Do not use yum update."+ check (Run (RunArgs args _)) =+ argumentsRule (Shell.noCommands (+ Shell.cmdHasArgs "yum" ["update",+ "update-to",+ "upgrade",+ "upgrade-to"])) args+ check _ = True++yumCleanup :: Rule+yumCleanup = instructionRule code severity message check+ where+ code = "DL3032"+ severity = WarningC+ message = "`yum clean all` missing after yum command."+ check (Run (RunArgs args _)) = argumentsRule (Shell.noCommands yumInstall) args ||+ (argumentsRule (Shell.anyCommands yumInstall) args &&+ argumentsRule (Shell.anyCommands yumClean) args)+ check _ = True+ yumInstall = Shell.cmdHasArgs "yum" ["install"]+ yumClean = Shell.cmdHasArgs "yum" ["clean", "all"]++yumVersionPinned :: Rule+yumVersionPinned = instructionRule code severity message check+ where+ code = "DL3033"+ severity = WarningC+ message = "Specify version with `yum install -y <package>-<version>`."+ check (Run (RunArgs args _)) = argumentsRule (all versionFixed . yumPackages) args+ check _ = True+ versionFixed package = "-" `Text.isInfixOf` package+ || ".rpm" `Text.isSuffixOf` package++yumPackages :: Shell.ParsedShell -> [Text.Text]+yumPackages args = [arg | cmd <- Shell.presentCommands args,+ Shell.cmdHasArgs "yum" ["install"] cmd,+ arg <- Shell.getArgsNoFlags cmd,+ arg /= "install"] gems :: Shell.ParsedShell -> [Text.Text] gems shell =
src/Hadolint/Shell.hs view
@@ -143,6 +143,9 @@ noCommands :: (Command -> Bool) -> ParsedShell -> Bool noCommands check = allCommands (not . check) +anyCommands :: (Command -> Bool) -> ParsedShell -> Bool+anyCommands check script = any check (presentCommands script)+ findCommandNames :: ParsedShell -> [Text] findCommandNames script = map name (presentCommands script) @@ -195,5 +198,6 @@ dropFlagArg :: [Text.Text] -> Command -> Command dropFlagArg flagsToDrop Command {name, arguments, flags} = Command name filterdArgs flags where- idsToDrop = Set.fromList [fId + 2 | CmdPart f fId <- flags, f `elem` flagsToDrop]+ idsToDrop = Set.fromList [getValueId fId arguments | CmdPart f fId <- flags, f `elem` flagsToDrop] filterdArgs = [arg | arg@(CmdPart _ aId) <- arguments, not (aId `Set.member` idsToDrop)]+getValueId fId flags = foldl min (maxBound :: Int) $ filter (>fId) $ map partId flags
test/Spec.hs view
@@ -23,6 +23,8 @@ it "explicit latest" $ ruleCatches noLatestTag "FROM debian:latest" it "explicit latest with name" $ ruleCatches noLatestTag "FROM debian:latest AS builder" it "explicit tagged" $ ruleCatchesNot noLatestTag "FROM debian:jessie"+ it "explicit platform flag" $ ruleCatches noPlatformFlag "FROM --platform=linux debian:jessie"+ it "no platform flag" $ ruleCatchesNot noPlatformFlag "FROM debian:jessie" it "explicit SHA" $ ruleCatchesNot noLatestTag "FROM hub.docker.io/debian@sha256:\@@ -188,6 +190,20 @@ ruleCatchesNot gemVersionPinned "RUN gem install bundler:2.0.1 -- --use-system-libraries=true" onBuildRuleCatchesNot gemVersionPinned "RUN gem install bundler:2.0.1 -- --use-system-libraries=true" --+ describe "yum rules" $ do+ it "yum update" $ do+ ruleCatches noYumUpdate "RUN yum update"+ onBuildRuleCatches noYumUpdate "RUN yum update"+ it "yum version pinning" $ do+ ruleCatches yumVersionPinned "RUN yum install -y tomcat && yum clean all"+ onBuildRuleCatches yumVersionPinned "RUN yum install -y tomcat && yum clean all"+ it "yum no clean all" $ do+ ruleCatches yumCleanup "RUN yum install -y mariadb-10.4"+ onBuildRuleCatches yumCleanup "RUN yum install -y mariadb-10.4"+ it "yum non-interactive" $ do+ ruleCatches yumYes "RUN yum install httpd-2.4.24 && yum clean all"+ onBuildRuleCatches yumYes "RUN yum install httpd-2.4.24 && yum clean all"+ -- describe "apt-get rules" $ do it "apt" $ let dockerFile =@@ -431,9 +447,15 @@ it "pip version pinned with flag --target" $ do ruleCatchesNot pipVersionPinned "RUN pip3 install --target /opt/yamllint yamllint==1.20.0" onBuildRuleCatchesNot pipVersionPinned "RUN pip3 install --target /opt/yamllint yamllint==1.20.0"+ it "pip version pinned with flag --trusted-host" $ do+ ruleCatchesNot pipVersionPinned "RUN pip3 install --trusted-host host example==1.2.2"+ onBuildRuleCatchesNot pipVersionPinned "RUN pip3 install --trusted-host host example==1.2.2" it "pip version pinned with python -m" $ do ruleCatchesNot pipVersionPinned "RUN python -m pip install example==1.2.2" onBuildRuleCatchesNot pipVersionPinned "RUN python -m pip install example==1.2.2"+ it "pip version not pinned with python -m" $ do+ ruleCatches pipVersionPinned "RUN python -m pip install example"+ onBuildRuleCatches pipVersionPinned "RUN python -m pip install --index-url url example" it "pip install git" $ do ruleCatchesNot pipVersionPinned@@ -497,6 +519,13 @@ it "pip install constraints file - short version argument" $ do ruleCatchesNot pipVersionPinned "RUN pip install pykafka -c http://foo.bar.baz" onBuildRuleCatchesNot pipVersionPinned "RUN pip install pykafka -c http://foo.bar.baz"+ it "pip install --index-url with --extra-index-url with basic auth" $ do+ ruleCatchesNot+ pipVersionPinned+ "RUN pip install --index-url https://user:pass@eg.com/foo --extra-index-url https://user:pass@ex-eg.io/foo foobar==1.0.0"+ onBuildRuleCatchesNot+ pipVersionPinned+ "RUN pip install --index-url https://user:pass@eg.com/foo --extra-index-url https://user:pass@ex-eg.io/foo foobar==1.0.0" -- describe "npm pinning" $ do it "version pinned in package.json" $ do@@ -681,7 +710,7 @@ it "Defaults the shell to sh" $ let dockerFile = Text.unlines- [ "RUN echo $RANDOM" -- $RANDOM is not available in sh+ [ "RUN echo $RANDOM" ] in do ruleCatches shellcheck dockerFile@@ -690,7 +719,7 @@ it "Can change the shell check to bash" $ let dockerFile = Text.unlines [ "SHELL [\"/bin/bash\", \"-eo\", \"pipefail\", \"-c\"]"- , "RUN echo $RANDOM" -- $RANDOM is available in bash+ , "RUN echo $RANDOM" ] in do ruleCatchesNot shellcheck dockerFile