packages feed

hadolint 2.13.1 → 2.14.0

raw patch · 24 files changed

+387/−96 lines, 24 filesdep −ilistdep ~language-dockerdep ~optparse-applicativePVP ok

version bump matches the API change (PVP)

Dependencies removed: ilist

Dependency ranges changed: language-docker, optparse-applicative

API changes (from Hackage documentation)

+ Hadolint.Rule.DL3062: rule :: Rule ParsedShell
+ Hadolint.Utils: hasCacheOrTmpfsMount :: RunFlags -> Bool
+ Hadolint.Utils: hasCacheOrTmpfsMountWith :: Text -> RunFlags -> Bool

Files

README.md view
@@ -472,7 +472,7 @@ <!--lint disable maximum-line-length-->  | Rule                                                         | Default Severity | Description                                                                                                                                         |-| :----------------------------------------------------------- | :--------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- |+|:-------------------------------------------------------------|:-----------------| :-------------------------------------------------------------------------------------------------------------------------------------------------- | | [DL1001](https://github.com/hadolint/hadolint/wiki/DL1001)   | Ignore           | Please refrain from using inline ignore pragmas `# hadolint ignore=DLxxxx`.                                                                         | | [DL3000](https://github.com/hadolint/hadolint/wiki/DL3000)   | Error            | Use absolute WORKDIR.                                                                                                                               | | [DL3001](https://github.com/hadolint/hadolint/wiki/DL3001)   | Info             | For some bash commands it makes no sense running them in a Docker container like ssh, vim, shutdown, service, ps, free, top, kill, mount, ifconfig. |@@ -532,6 +532,7 @@ | [DL3059](https://github.com/hadolint/hadolint/wiki/DL3059)   | Info             | Multiple consecutive `RUN` instructions. Consider consolidation.                                                                                    | | [DL3060](https://github.com/hadolint/hadolint/wiki/DL3060)   | Info             | `yarn cache clean` missing after `yarn install` was run.                                                                                            | | [DL3061](https://github.com/hadolint/hadolint/wiki/DL3061)   | Error            | Invalid instruction order. Dockerfile must begin with `FROM`, `ARG` or comment.                                                                     |+| [DL3062](https://github.com/hadolint/hadolint/wiki/DL3061)   | Warning          | Pin versions in go install. Instead of `go install <package>` use `go install <package>@<version>`                                                  | | [DL4000](https://github.com/hadolint/hadolint/wiki/DL4000)   | Error            | `MAINTAINER` is deprecated.                                                                                                                         | | [DL4001](https://github.com/hadolint/hadolint/wiki/DL4001)   | Warning          | Either use Wget or Curl but not both.                                                                                                               | | [DL4003](https://github.com/hadolint/hadolint/wiki/DL4003)   | Warning          | Multiple `CMD` instructions found.                                                                                                                  |@@ -559,7 +560,7 @@ | [SC1095](https://github.com/koalaman/shellcheck/wiki/SC1095) |                  | You need a space or linefeed between the function name and body.                                                                                    | | [SC1097](https://github.com/koalaman/shellcheck/wiki/SC1097) |                  | Unexpected `==`. For assignment, use `=`. For comparison, use `[ .. ]` or `[[ .. ]]`.                                                               | | [SC1098](https://github.com/koalaman/shellcheck/wiki/SC1098) |                  | Quote/escape special characters when using `eval`, e.g. `eval "a=(b)"`.                                                                             |-| [SC1099](https://github.com/koalaman/shellcheck/wiki/SC1099) |                   | You need a space before the `#`.                                                                                                                    |+| [SC1099](https://github.com/koalaman/shellcheck/wiki/SC1099) |                  | You need a space before the `#`.                                                                                                                    | | [SC2002](https://github.com/koalaman/shellcheck/wiki/SC2002) |                  | Useless cat. Consider <code>cmd < file &#124; ..</code> or <code>cmd file &#124; ..</code> instead.                                                 | | [SC2015](https://github.com/koalaman/shellcheck/wiki/SC2015) |                  | Note that <code>A && B &#124;&#124; C</code> is not if-then-else. C may run when A is true.                                                         | | [SC2026](https://github.com/koalaman/shellcheck/wiki/SC2026) |                  | This word is outside of quotes. Did you intend to 'nest '"'single quotes'"' instead'?                                                               |
hadolint.cabal view
@@ -1,6 +1,6 @@ cabal-version:      2.0 name:               hadolint-version:            2.13.1+version:            2.14.0 synopsis:           Dockerfile Linter JavaScript API description:   A smarter Dockerfile linter that helps you build best practice Docker images.@@ -101,6 +101,7 @@     Hadolint.Rule.DL3059     Hadolint.Rule.DL3060     Hadolint.Rule.DL3061+    Hadolint.Rule.DL3062     Hadolint.Rule.DL4000     Hadolint.Rule.DL4001     Hadolint.Rule.DL4003@@ -109,6 +110,7 @@     Hadolint.Rule.DL4006     Hadolint.Rule.Shellcheck     Hadolint.Shell+    Hadolint.Utils    other-modules:      Paths_hadolint   autogen-modules:    Paths_hadolint@@ -147,12 +149,11 @@     , foldl                 >=1.4.18   && <1.5     , gitrev                >=1.3.1    && <1.4     , HsYAML                >=0.2.1    && <0.3-    , ilist                 >=0.3.1    && <0.4-    , language-docker       >=14.0.1   && <15+    , language-docker       >=15.0.0   && <16     , megaparsec            >=9.0.0    && <9.8     , mtl                   >=2.3.1    && <2.4     , network-uri           >=2.6.4    && <2.7-    , optparse-applicative  >=0.19.0   && <0.20+    , optparse-applicative  >=0.18.1   && <0.20     , parallel              >=3.2.2    && <3.3     , parsec                >=3.1.18   && <3.2     , prettyprinter         >=1.7.1    && <1.8@@ -196,7 +197,7 @@     , containers            >=0.7       && <0.8     , data-default          >=0.8.0     && <0.9     , hadolint-    , language-docker       >=14.0.1    && <15+    , language-docker     , megaparsec            >=9.7.0     && <9.8     , optparse-applicative  >=0.19.0    && <0.20     , prettyprinter         >=1.7.1     && <1.8@@ -277,6 +278,7 @@     Hadolint.Rule.DL3059Spec     Hadolint.Rule.DL3060Spec     Hadolint.Rule.DL3061Spec+    Hadolint.Rule.DL3062Spec     Hadolint.Rule.DL4000Spec     Hadolint.Rule.DL4001Spec     Hadolint.Rule.DL4003Spec
src/Hadolint/Process.hs view
@@ -68,6 +68,7 @@ import qualified Hadolint.Rule.DL3059 import qualified Hadolint.Rule.DL3060 import qualified Hadolint.Rule.DL3061+import qualified Hadolint.Rule.DL3062 import qualified Hadolint.Rule.DL4000 import qualified Hadolint.Rule.DL4001 import qualified Hadolint.Rule.DL4003@@ -172,6 +173,7 @@     <> Hadolint.Rule.DL3059.rule     <> Hadolint.Rule.DL3060.rule     <> Hadolint.Rule.DL3061.rule+    <> Hadolint.Rule.DL3062.rule     <> Hadolint.Rule.DL4000.rule     <> Hadolint.Rule.DL4001.rule     <> Hadolint.Rule.DL4003.rule
src/Hadolint/Rule/DL3009.hs view
@@ -3,9 +3,9 @@ import Hadolint.Rule import Language.Docker.Syntax import qualified Data.Map.Strict as Map-import qualified Data.Set as Set import qualified Data.Text as Text import qualified Hadolint.Shell as Shell+import qualified Hadolint.Utils as Utils   data Acc@@ -36,9 +36,9 @@           if foldArguments disabledDockerClean args           then st |> modify rememberDockerClean           else st-      | hasCacheDirectory "/var/lib/apt/lists" flags = st-      | hasCacheDirectory "/var/lib/apt" flags-        && hasCacheDirectory "/var/cache/apt" flags = st+      | Utils.hasCacheOrTmpfsMountWith "/var/lib/apt/lists" flags = st+      | Utils.hasCacheOrTmpfsMountWith "/var/lib/apt" flags+        && Utils.hasCacheOrTmpfsMountWith "/var/cache/apt" flags = st       | otherwise = st |> modify (rememberLine line)     check _ st _ = st @@ -99,14 +99,6 @@             ["\'Binary::apt::APT::Keep-Downloaded-Packages \"true\";\'"]         )         (Shell.presentCommands args)--hasCacheDirectory :: Text.Text -> RunFlags -> Bool-hasCacheDirectory dir RunFlags { mount } =-  not ( null $ Set.filter (isCacheMount dir) mount)--isCacheMount :: Text.Text -> RunMount -> Bool-isCacheMount dir (CacheMount CacheOpts {cTarget = TargetPath {unTargetPath = t}}) = dir `Text.isPrefixOf` t-isCacheMount _ _ = False  -- | Even though dockerfiles without a FROM are not valid, we still want to provide some feedback for this rule -- so we pretend there is a base image at the start of the file if there is none
src/Hadolint/Rule/DL3019.hs view
@@ -3,9 +3,8 @@ import Hadolint.Rule import Hadolint.Shell (ParsedShell) import Language.Docker.Syntax-import qualified Data.Set as Set-import qualified Data.Text as Text import qualified Hadolint.Shell as Shell+import qualified Hadolint.Utils as Utils   rule :: Rule ParsedShell@@ -20,22 +19,12 @@     message =       "Use the `--no-cache` switch to avoid the need to use `--update` and \       \remove `/var/cache/apk/*` when done installing packages"-    check (Run (RunArgs args flags)) = hasCacheMount flags-      || foldArguments (Shell.noCommands forgotCacheOption) args+    check (Run (RunArgs args flags))+      | Utils.hasCacheOrTmpfsMountWith "/var/cache/apk" flags = True+      | foldArguments (Shell.noCommands forgotCacheOption) args = True+      | otherwise = False     check _ = True {-# INLINEABLE dl3019 #-}--hasCacheMount :: RunFlags -> Bool-hasCacheMount RunFlags { mount } =-  not (null $ Set.filter isCacheMount mount)--isCacheMount :: RunMount -> Bool-isCacheMount (CacheMount CacheOpts { cTarget = t })= isVarCacheApk t-isCacheMount _ = False--isVarCacheApk :: TargetPath -> Bool-isVarCacheApk TargetPath { unTargetPath = p }-  = Text.dropWhileEnd (=='/') p == "/var/cache/apk"  forgotCacheOption :: Shell.Command -> Bool forgotCacheOption cmd = Shell.cmdHasArgs "apk" ["add"] cmd
src/Hadolint/Rule/DL3033.hs view
@@ -4,6 +4,7 @@ import Hadolint.Rule import qualified Hadolint.Shell as Shell import Language.Docker.Syntax+import Data.Char (isDigit, isAsciiUpper, isAsciiLower)   rule :: Rule Shell.ParsedShell@@ -32,8 +33,30 @@   ]  packageVersionFixed :: Text.Text -> Bool-packageVersionFixed package =-  "-" `Text.isInfixOf` package || ".rpm" `Text.isSuffixOf` package+packageVersionFixed package+  | length parts <= 1 = False  -- No dashes, definitively no version+  | ".rpm" `Text.isSuffixOf` package = True  -- rpm files always have a version+  | otherwise = isVersionLike $ drop 1 parts+  where+    parts = Text.splitOn "-" package++isVersionLike :: [Text.Text] -> Bool+isVersionLike parts =+  case parts of+    [] -> False  -- No parts after splitting by hyphen+    _ -> all partIsValid parts && any partStartsWithDigit parts+  where+    partIsValid part = Text.all isVersionChar part+    partStartsWithDigit part = case Text.uncons part of+                                 Just (c, _) -> isDigit c+                                 Nothing -> False -- Empty Text++isVersionChar :: Char -> Bool+isVersionChar c =+  isDigit c+    || isAsciiUpper c+    || isAsciiLower c+    || c `elem` ['.', '~', '^', '_', ':', '+']  yumModules :: Shell.ParsedShell -> [Text.Text] yumModules args =
src/Hadolint/Rule/DL3036.hs view
@@ -2,6 +2,7 @@  import Hadolint.Rule import qualified Hadolint.Shell as Shell+import qualified Hadolint.Utils as Utils import Language.Docker.Syntax  @@ -16,11 +17,12 @@     severity = DLWarningC     message = "`zypper clean` missing after zypper use." -    check (Run (RunArgs args _)) =-      foldArguments (Shell.noCommands zypperInstall) args-        || ( foldArguments (Shell.anyCommands zypperInstall) args-               && foldArguments (Shell.anyCommands zypperClean) args-           )+    check (Run (RunArgs args flags))+      | foldArguments (Shell.noCommands zypperInstall) args = True+      | Utils.hasCacheOrTmpfsMountWith "/var/cache/zypp" flags = True+      | foldArguments (Shell.anyCommands zypperInstall) args+          && foldArguments (Shell.anyCommands zypperClean) args = True+      | otherwise = False     check _ = True      zypperInstall = Shell.cmdHasArgs "zypper" ["install", "in"]
src/Hadolint/Rule/DL3040.hs view
@@ -2,6 +2,7 @@  import Hadolint.Rule import qualified Hadolint.Shell as Shell+import qualified Hadolint.Utils as Utils import Language.Docker.Syntax  @@ -16,7 +17,11 @@     severity = DLWarningC     message = "`dnf clean all` missing after dnf command." -    check (Run (RunArgs args _)) = all (checkMissingClean args) dnfCmds+    check (Run (RunArgs args flags))+      | all (checkMissingClean args) dnfCmds = True+      | Utils.hasCacheOrTmpfsMountWith "/var/cache/libdnf5" flags = True+      | Utils.hasCacheOrTmpfsMountWith ".cache/libdnf5" flags = True+      | otherwise = False     check _ = True      checkMissingClean args cmdName =@@ -26,7 +31,7 @@            )      dnfInstall cmdName = Shell.cmdHasArgs cmdName ["install"]-    dnfClean cmdName args = Shell.cmdHasArgs cmdName ["clean", "all"] args -      || Shell.cmdHasArgs "rm" ["-rf", "/var/cache/yum/*"] args+    dnfClean cmdName args = Shell.cmdHasArgs cmdName ["clean", "all"] args+      || Shell.cmdHasArgs "rm" ["-rf", "/var/cache/libdnf5*"] args     dnfCmds = ["dnf", "microdnf"] {-# INLINEABLE dl3040 #-}
src/Hadolint/Rule/DL3041.hs view
@@ -54,7 +54,11 @@                                  Nothing -> False -- Empty Text  isVersionChar :: Char -> Bool-isVersionChar c = isDigit c || isAsciiUpper c || isAsciiLower c || c `elem` ['.', '~', '^', '_']+isVersionChar c =+  isDigit c+    || isAsciiUpper c+    || isAsciiLower c+    || c `elem` ['.', '~', '^', '_', ':', '+']  dnfModules :: Shell.ParsedShell -> [Text.Text] dnfModules args =
src/Hadolint/Rule/DL3042.hs view
@@ -4,12 +4,12 @@ import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe, isNothing, isJust, fromJust)-import Data.Set (Set) import qualified Data.Set as Set import Data.Text (Text) import qualified Data.Text as Text import Hadolint.Rule import qualified Hadolint.Shell as Shell+import qualified Hadolint.Utils as Utils import Language.Docker.Syntax  @@ -38,10 +38,11 @@               \ Use `pip install --no-cache-dir <package>`"     check _ st (From from) = st |> modify (rememberStage from)     check _ st (Env pairs) = st |> modify (registerEnv pairs)-    check line st (Run (RunArgs args _))+    check line st (Run (RunArgs args flags))       | Acc s ncm <- state st, Just True <- Map.lookup s ncm = st       | foldArguments pipNoCacheDirIsSet args = st       | foldArguments (Shell.noCommands forgotNoCacheDir) args = st+      | Utils.hasCacheOrTmpfsMountWith ".cache/pip" flags = st       | otherwise = st |> addFail CheckFailure {..}     check _ st _ = st {-# INLINEABLE dl3042 #-}@@ -119,5 +120,5 @@     isJust val && fromJust val `notElem` truthy = False   | otherwise = True -truthy :: Set Text+truthy :: Set.Set Text truthy = Set.fromList ["1", "true", "True", "TRUE", "on", "On", "ON", "yes", "Yes", "YES"]
src/Hadolint/Rule/DL3044.hs view
@@ -1,6 +1,5 @@ module Hadolint.Rule.DL3044 (rule) where -import Data.List.Index (indexed) import qualified Data.Set as Set import qualified Data.Text as Text import Hadolint.Rule@@ -28,9 +27,12 @@ listOfReferences :: Pairs -> [Text.Text] listOfReferences prs =   [ var-    | (idx, (var, _)) <- indexed prs,-      var `isSubstringOfAny` map (snd . snd) (filter ((/= idx) . fst) (indexed prs))+    | (idx, (var, _)) <- enum prs,+      var `isSubstringOfAny` map (snd . snd) (filter ((/= idx) . fst) (enum prs))   ]+  where+    enum :: Pairs -> [(Integer, (Text.Text, Text.Text))]+    enum = zip [0..]  -- | is a reference of a variable substring of any text? -- matches ${var_name} and $var_name, but not $var_nameblafoo
src/Hadolint/Rule/DL3060.hs view
@@ -4,8 +4,8 @@ import qualified Data.Text as Text import Hadolint.Rule import qualified Hadolint.Shell as Shell+import qualified Hadolint.Utils as Utils import Language.Docker.Syntax-import Data.Set  data Acc   = Acc@@ -16,22 +16,6 @@   | Empty   deriving (Show) --- check if a RunMount is a CacheMount-isCacheMount :: RunMount -> Bool-isCacheMount (CacheMount _) = True-isCacheMount _ = False---- Set of RunMounts must contain at least one CacheMount--containsOneCacheMount :: Set RunMount -> Bool-containsOneCacheMount = Data.Set.foldl foldable False-  where -    foldable a b = a || isCacheMount b---- do not flag this as a problem if using a cacheMount-cacheMount :: RunFlags -> Bool-cacheMount RunFlags {mount} = containsOneCacheMount mount- rule :: Rule Shell.ParsedShell rule = dl3060 <> onbuild dl3060 {-# INLINEABLE rule #-}@@ -48,7 +32,7 @@     check line st (Run (RunArgs args flags))       | foldArguments (Shell.anyCommands yarnInstall) args           && foldArguments (Shell.noCommands yarnCacheClean) args-          && not (cacheMount flags) =+          && not (Utils.hasCacheOrTmpfsMountWith ".cache/yarn" flags) =         st |> modify (rememberLine line)       | otherwise = st     check _ st _ = st
+ src/Hadolint/Rule/DL3062.hs view
@@ -0,0 +1,46 @@+module Hadolint.Rule.DL3062 (rule) where++import Data.Text qualified as Text+import Hadolint.Rule+import Hadolint.Shell qualified as Shell+import Language.Docker.Syntax++rule :: Rule Shell.ParsedShell+rule = dl3062 <> onbuild dl3062+{-# INLINEABLE rule #-}++dl3062 :: Rule Shell.ParsedShell+dl3062 = simpleRule code severity message check+  where+    code = "DL3062"+    severity = DLWarningC+    message = "Pin versions in go. Instead of `go install <package>` use `go install <package>@<version>`"++    check (Run (RunArgs args _)) = foldArguments (all checkGoPackageVersion . getGoPackage) args+    check _ = True+{-# INLINEABLE dl3062 #-}++goCommands :: [Text.Text]+goCommands = ["install", "get", "run"]++isGoCommand :: Shell.Command -> Bool+isGoCommand = Shell.cmdsHaveArgs ["go"] goCommands++getGoPackage :: Shell.ParsedShell -> [Text.Text]+getGoPackage args =+  [ arg+    | cmd <- Shell.presentCommands args,+      isGoCommand cmd,+      arg <- Shell.getArgsNoFlags cmd,+      arg `notElem` goCommands+  ]++hasVersionSymbol :: Text.Text -> Bool+hasVersionSymbol package = "@" `Text.isInfixOf` package++isTagsVersion :: Text.Text -> Bool+isTagsVersion package =+  or [("@" <> tag) `Text.isSuffixOf` package | tag <- ["latest", "none"]]++checkGoPackageVersion :: Text.Text -> Bool+checkGoPackageVersion package = hasVersionSymbol package && not (isTagsVersion package)
src/Hadolint/Shell.hs view
@@ -162,9 +162,7 @@ findCommandNames script = map name (presentCommands script)  cmdHasArgs :: Text.Text -> [Text.Text] -> Command -> Bool-cmdHasArgs expectedName expectedArgs (Command n args _)-  | expectedName /= n = False-  | otherwise = not $ null [arg | CmdPart arg _ <- args, arg `elem` expectedArgs]+cmdHasArgs expectedName = cmdsHaveArgs [expectedName]  cmdsHaveArgs :: [Text.Text] -> [Text.Text] -> Command -> Bool cmdsHaveArgs expectedNames expectedArgs (Command n args _)@@ -204,7 +202,7 @@     notAFlagId pId = pId `notElem` map partId (flags args)  hasFlag :: Text.Text -> Command -> Bool-hasFlag flag Command {flags} = not $ null [f | CmdPart f _ <- flags, f == flag]+hasFlag flag cmd = countFlag flag cmd > 0  countFlag :: Text.Text -> Command -> Int countFlag flag Command {flags} = length [ f | CmdPart f _ <- flags, f == flag ]
+ src/Hadolint/Utils.hs view
@@ -0,0 +1,31 @@+module Hadolint.Utils+  ( hasCacheOrTmpfsMount,+    hasCacheOrTmpfsMountWith+  )+where++import qualified Data.Text as Text+import qualified Data.Set as Set+import Language.Docker.Syntax+++-- Returns true if the given RunFlags contain a mount of type cache or tmpfs at+-- a location that contains the path fragment `frag`.+hasCacheOrTmpfsMountWith :: Text.Text -> RunFlags -> Bool+hasCacheOrTmpfsMountWith frag RunFlags { mount } =+  not ( null $ Set.filter (isCacheOrTmpfsMountWith frag) mount)++isCacheOrTmpfsMountWith :: Text.Text -> RunMount -> Bool+isCacheOrTmpfsMountWith frag (CacheMount CacheOpts {cTarget = TargetPath {unTargetPath = t}}) = frag `Text.isInfixOf` t+isCacheOrTmpfsMountWith frag (TmpfsMount TmpOpts {tTarget = TargetPath {unTargetPath = t}}) = frag `Text.isInfixOf` t+isCacheOrTmpfsMountWith _ _ = False++-- Returns true if the given RunFlags contain a mount of type cache or tmpfs+hasCacheOrTmpfsMount :: RunFlags -> Bool+hasCacheOrTmpfsMount RunFlags { mount } =+  not ( null $ Set.filter isCacheOrTmpfsMount mount)++isCacheOrTmpfsMount :: RunMount -> Bool+isCacheOrTmpfsMount (CacheMount _) = True+isCacheOrTmpfsMount (TmpfsMount _) = True+isCacheOrTmpfsMount _ = False
test/Hadolint/Rule/DL3009Spec.hs view
@@ -132,6 +132,50 @@             ruleCatches "DL3009" $ Text.unlines dockerFile             onBuildRuleCatches "DL3009" $ Text.unlines dockerFile +    it "don't warn: tmpfs mount to apt cache and lists directory" $+      let dockerFile =+            [ "RUN \\",+              "  --mount=type=tmpfs,target=/var/cache/apt \\",+              "  --mount=type=tmpfs,target=/var/lib/apt \\",+              "  apt-get update"+            ]+       in do+            ruleCatchesNot "DL3009" $ Text.unlines dockerFile+            onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile++    it "don't warn: tmpfs mount to apt cache and cache mount to lists directory" $+      let dockerFile =+            [ "RUN \\",+              "  --mount=type=tmpfs,target=/var/cache/apt \\",+              "  --mount=type=cache,target=/var/lib/apt \\",+              "  apt-get update"+            ]+       in do+            ruleCatchesNot "DL3009" $ Text.unlines dockerFile+            onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile++    it "don't warn: cache mount to apt cache and tmpfs mount to lists directory" $+      let dockerFile =+            [ "RUN \\",+              "  --mount=type=cache,target=/var/cache/apt \\",+              "  --mount=type=tmpfs,target=/var/lib/apt \\",+              "  apt-get update"+            ]+       in do+            ruleCatchesNot "DL3009" $ Text.unlines dockerFile+            onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile++    it "don't warn: cache mount to apt cache and lists directory" $+      let dockerFile =+            [ "RUN \\",+              "  --mount=type=cache,target=/var/cache/apt \\",+              "  --mount=type=cache,target=/var/lib/apt \\",+              "  apt-get update"+            ]+       in do+            ruleCatchesNot "DL3009" $ Text.unlines dockerFile+            onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile+     it "apt no cleanup" $       let dockerFile =             [ "FROM scratch",
test/Hadolint/Rule/DL3019Spec.hs view
@@ -10,18 +10,44 @@   let ?config = def    describe "DL3019 - Use the --no-cache switch." $ do-    it "apk add with --no-cache" $ do-      ruleCatches "DL3019" "RUN apk add flex=2.6.4-r1"-      onBuildRuleCatches "DL3019" "RUN apk add flex=2.6.4-r1"-    it "apk add without --no-cache" $ do-      ruleCatchesNot "DL3019" "RUN apk add --no-cache flex=2.6.4-r1"-      onBuildRuleCatchesNot "DL3019" "RUN apk add --no-cache flex=2.6.4-r1"-    it "don't warn: apk add with BuildKit cache mount" $ do-      ruleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/apk apk add -U curl=7.77.0"-      onBuildRuleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/apk apk add -U curl=7.77.0"-    it "don't warn: apk add with BuildKit cache mount in wrong dir and --no-cache" $ do-      ruleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add --no-cache -U curl=7.77.0"-      onBuildRuleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add --no-cache -U curl=7.77.0"-    it "warn: apk add with BuildKit cache mount to wrong dir" $ do-      ruleCatches "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add -U curl=7.77.0"-      onBuildRuleCatches "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add -U curl=7.77.0"+    it "warn: apk add without --no-cache" $+      let line = "RUN apk add flex=2.6.4-r1"+      in do+        ruleCatches "DL3019" line+        onBuildRuleCatches "DL3019" line++    it "don't warn: apk add with --no-cache" $+      let line = "RUN apk add --no-cache flex=2.6.4-r1"+      in do+        ruleCatchesNot "DL3019" line+        onBuildRuleCatchesNot "DL3019" line++    it "don't warn: apk add with BuildKit cache mount" $+      let line = "RUN --mount=type=cache,target=/var/cache/apk apk add -U curl=7.77.0"+      in do+        ruleCatchesNot "DL3019" line+        onBuildRuleCatchesNot "DL3019" line++    it "don't warn: apk add with BuildKit tmpfs mount" $+      let line = "RUN --mount=type=tmpfs,target=/var/cache/apk apk add -U curl=7.77.0"+      in do+        ruleCatchesNot "DL3019" line+        onBuildRuleCatchesNot "DL3019" line++    it "don't warn: apk add with BuildKit cache mount in wrong dir and --no-cache" $+      let line = "RUN --mount=type=cache,target=/var/cache/foo apk add --no-cache -U curl=7.77.0"+      in do+        ruleCatchesNot "DL3019" line+        onBuildRuleCatchesNot "DL3019" line++    it "warn: apk add with BuildKit cache mount to wrong dir" $+      let line = "RUN --mount=type=cache,target=/var/cache/foo apk add -U curl=7.77.0"+      in do+        ruleCatches "DL3019" line+        onBuildRuleCatches "DL3019" line++    it "warn: apk add with BuildKit tmpfs mount to wrong dir" $+      let line = "RUN --mount=type=tmpfs,target=/var/cache/foo apk add -U curl=7.77.0"+      in do+        ruleCatches "DL3019" line+        onBuildRuleCatches "DL3019" line
test/Hadolint/Rule/DL3033Spec.hs view
@@ -13,14 +13,29 @@     it "not ok without yum version pinning" $ do       ruleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"       onBuildRuleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"+     it "ok with yum version pinning" $ do       ruleCatchesNot "DL3033" "RUN yum install -y tomcat-9.2 && yum clean all"       ruleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"       onBuildRuleCatchesNot "DL3033" "RUN yum install -y tomcat-9.2 && yum clean all"       onBuildRuleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"++    it "ok with yum version pinning - version contains epoch" $ do+      ruleCatchesNot "DL3033" "RUN yum install -y openssl-1:1.1.1k"+      onBuildRuleCatchesNot "DL3033" "RUN yum install -y openssl-1:1.1.1k"++    it "ok with yum version pinning - package name contains `-`" $ do+      ruleCatchesNot "DL3033" "RUN yum install -y rpm-sign-4.16.1.3"+      onBuildRuleCatchesNot "DL3033" "RUN yum install -y rpm-sign-4.16.1.3"++    it "ok with yum version pinning - package name contains `-` and `+`" $ do+      ruleCatchesNot "DL3033" "RUN yum install -y gcc-c++-1.1.1"+      onBuildRuleCatchesNot "DL3033" "RUN yum install -y gcc-c++-1.1.1"+     it "not ok without yum version pinning - modules" $ do       ruleCatches "DL3033" "RUN yum module install -y tomcat && yum clean all"       onBuildRuleCatches "DL3033" "RUN yum module install -y tomcat && yum clean all"+     it "ok with yum version pinning - modules" $ do       ruleCatchesNot "DL3033" "RUN yum module install -y tomcat:9 && yum clean all"       ruleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"
test/Hadolint/Rule/DL3036Spec.hs view
@@ -10,11 +10,25 @@   let ?config = def    describe "DL3036 - `zypper clean` missing after zypper use." $ do+     it "not ok without zypper clean" $ do       ruleCatches "DL3036" "RUN zypper install -y mariadb=10.4"       onBuildRuleCatches "DL3036" "RUN zypper install -y mariadb=10.4"+     it "ok with zypper clean" $ do       ruleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper clean"       ruleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper cc"       onBuildRuleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper clean"       onBuildRuleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper cc"++    it "ok when mount type cache is used" $+      let line = "RUN --mount=type=cache,target=/var/cache/zypp zypper install -y mariadb"+      in do+        ruleCatchesNot "DL3036" line+        onBuildRuleCatchesNot "DL3036" line++    it "ok when mount type tmpfs is used" $+      let line = "RUN --mount=type=tmpfs,target=/var/cache/zypp zypper install -y mariadb"+      in do+        ruleCatchesNot "DL3036" line+        onBuildRuleCatchesNot "DL3036" line
test/Hadolint/Rule/DL3040Spec.hs view
@@ -24,7 +24,18 @@       onBuildRuleCatchesNot "DL3040" "RUN microdnf install -y mariadb-10.4 && microdnf clean all"       onBuildRuleCatchesNot "DL3040" "RUN notdnf install mariadb"     it "ok with rm /var/cache/yum" $ do-      ruleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && rm -rf /var/cache/yum/*"-      ruleCatchesNot "DL3040" "RUN microdnf install -y mariadb-10.4 && rm -rf /var/cache/yum/*"-      onBuildRuleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && rm -rf /var/cache/yum/*"-      onBuildRuleCatchesNot "DL3040" "RUN microdnf install -y mariadb-10.4 && rm -rf /var/cache/yum/*"+      ruleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && rm -rf /var/cache/libdnf5"+      ruleCatchesNot "DL3040" "RUN microdnf install -y mariadb-10.4 && rm -rf /var/cache/libdnf5"+      onBuildRuleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && rm -rf /var/cache/libdnf5"+      onBuildRuleCatchesNot "DL3040" "RUN microdnf install -y mariadb-10.4 && rm -rf /var/cache/libdnf5"++    it "ok with cache mount at /var/cache/yum" $ do+      ruleCatchesNot "DL3040" "RUN --mount=type=cache,target=/var/cache/libdnf5 dnf install -y mariadb-10.4"+      ruleCatchesNot "DL3040" "RUN --mount=type=cache,target=/var/cache/libdnf5 microdnf install -y mariadb-10.4"+      onBuildRuleCatchesNot "DL3040" "RUN --mount=type=cache,target=/var/cache/libdnf5 dnf install -y mariadb-10.4"+      onBuildRuleCatchesNot "DL3040" "RUN --mount=type=cache,target=/var/cache/libdnf5 microdnf install -y mariadb-10.4"+    it "ok with tmpfs mount at /var/cache/yum" $ do+      ruleCatchesNot "DL3040" "RUN --mount=type=tmpfs,target=/var/cache/libdnf5 dnf install -y mariadb-10.4"+      ruleCatchesNot "DL3040" "RUN --mount=type=tmpfs,target=/var/cache/libdnf5 microdnf install -y mariadb-10.4"+      onBuildRuleCatchesNot "DL3040" "RUN --mount=type=tmpfs,target=/var/cache/libdnf5 dnf install -y mariadb-10.4"+      onBuildRuleCatchesNot "DL3040" "RUN --mount=type=tmpfs,target=/var/cache/libdnf5 microdnf install -y mariadb-10.4"
test/Hadolint/Rule/DL3041Spec.hs view
@@ -10,32 +10,54 @@   let ?config = def    describe "DL3041 - Specify version with `dnf install -y <package>-<version>`" $ do+     it "not ok without dnf version pinning" $ do       ruleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"       ruleCatches "DL3041" "RUN microdnf install -y tomcat && microdnf clean all"       onBuildRuleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"+     it "not ok without dnf version pinning - package name with `-`" $ do       ruleCatches "DL3041" "RUN dnf install -y rpm-sign && dnf clean all"       ruleCatches "DL3041" "RUN microdnf install -y rpm-sign && microdnf clean all"       onBuildRuleCatches "DL3041" "RUN dnf install -y rpm-sign && dnf clean all"+     it "ok with dnf version pinning" $ do       ruleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"       ruleCatchesNot "DL3041" "RUN microdnf install -y tomcat-9.0.1 && microdnf clean all"-      ruleCatchesNot "DL3041" "RUN notdnf install tomcat"       onBuildRuleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"       onBuildRuleCatchesNot "DL3041" "RUN microdnf install -y tomcat-9.0.1 && microdnf clean all"++    it "ok with version pinning if command is not `dnf` or `microdnf`" $ do+      ruleCatchesNot "DL3041" "RUN notdnf install openssl-1:1.1.1k"+      onBuildRuleCatchesNot "DL3041" "RUN notdnf install openssl-1:1.1.1k"++    it "ok without version pinning if command is not `dnf` or `microdnf`" $ do+      ruleCatchesNot "DL3041" "RUN notdnf install tomcat"       onBuildRuleCatchesNot "DL3041" "RUN notdnf install tomcat"+     it "ok with dnf version pinning - package name with `-`" $ do       ruleCatchesNot "DL3041" "RUN dnf install -y rpm-sign-4.16.1.3 && dnf clean all"       ruleCatchesNot "DL3041" "RUN microdnf install -y rpm-sign-4.16.1.3 && microdnf clean all"-      ruleCatchesNot "DL3041" "RUN notdnf install rpm-sign"       onBuildRuleCatchesNot "DL3041" "RUN dnf install -y rpm-sign-4.16.1.3 && dnf clean all"       onBuildRuleCatchesNot "DL3041" "RUN microdnf install -y rpm-sign-4.16.1.3 && microdnf clean all"-      onBuildRuleCatchesNot "DL3041" "RUN notdnf install rpm-sign"++    it "ok with dnf version pinning - package name with `-` and `+`" $ do+      ruleCatchesNot "DL3041" "RUN dnf install -y gcc-c++-1.1.1"+      ruleCatchesNot "DL3041" "RUN microdnf install -y gcc-c++-1.1.1"+      onBuildRuleCatchesNot "DL3041" "RUN dnf install -y gcc-c++-1.1.1"+      onBuildRuleCatchesNot "DL3041" "RUN microdnf install -y gcc-c++-1.1.1"++    it "ok with dnf version pinning - package version with epoch" $ do+      ruleCatchesNot "DL3041" "RUN dnf install -y openssl-1:1.1.1k"+      ruleCatchesNot "DL3041" "RUN microdnf install -y openssl-1:1.1.1k"+      onBuildRuleCatchesNot "DL3041" "RUN dnf install -y openssl-1:1.1.1k"+      onBuildRuleCatchesNot "DL3041" "RUN microdnf install -y openssl-1:1.1.1k"+     it "not ok without dnf version pinning - modules" $ do       ruleCatches "DL3041" "RUN dnf module install -y tomcat && dnf clean all"       ruleCatches "DL3041" "RUN microdnf module install -y tomcat && microdnf clean all"       onBuildRuleCatches "DL3041" "RUN dnf module install -y tomcat && dnf clean all"+     it "ok with dnf version pinning - modules" $ do       ruleCatchesNot "DL3041" "RUN dnf module install -y tomcat:9 && dnf clean all"       ruleCatchesNot "DL3041" "RUN microdnf module install -y tomcat:9 && microdnf clean all"
test/Hadolint/Rule/DL3042Spec.hs view
@@ -86,3 +86,15 @@             ]        in do         ruleCatches "DL3042" dockerFile++    it "ok with cache mount in cache dir" $+      let line = "RUN --mount=type=cache,target=/root/.cache/pip pip install foobar"+      in do+        ruleCatchesNot "DL3042" line+        onBuildRuleCatchesNot "DL3042" line++    it "ok with tmpfs mount in cache dir" $+      let line = "RUN --mount=type=tmpfs,target=/root/.cache/pip pip install foobar"+      in do+        ruleCatchesNot "DL3042" line+        onBuildRuleCatchesNot "DL3042" line
test/Hadolint/Rule/DL3060Spec.hs view
@@ -21,6 +21,30 @@       ruleCatchesNot "DL3060" "RUN yarn install bar && yarn cache clean"       onBuildRuleCatchesNot "DL3060" "RUN yarn install bar && yarn cache clean" +    it "ok when cache mount is used" $+      let line = "RUN --mount=type=cache,target=/root/.cache/yarn yarn install foobar"+      in do+        ruleCatchesNot "DL3060" line+        onBuildRuleCatchesNot "DL3060" line++    it "ok when tmpfs mount is used" $+      let line = "RUN --mount=type=tmpfs,target=/root/.cache/yarn yarn install foobar"+      in do+        ruleCatchesNot "DL3060" line+        onBuildRuleCatchesNot "DL3060" line++    it "not ok when cache mount is in wrong location" $+      let line = "RUN --mount=type=cache,target=/var/lib/foobar yarn install foobar"+      in do+        ruleCatches "DL3060" line+        onBuildRuleCatches "DL3060" line++    it "not ok when tmpfs mount is in wrong location" $+      let line = "RUN --mount=type=tmpfs,target=/var/lib/foobar yarn install foobar"+      in do+        ruleCatches "DL3060" line+        onBuildRuleCatches "DL3060" line+     it "not ok when yarn install is in last stage w/o yarn clean" $       let dockerFile =             Text.unlines
+ test/Hadolint/Rule/DL3062Spec.hs view
@@ -0,0 +1,41 @@+module Hadolint.Rule.DL3062Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec++spec :: SpecWith ()+spec = do+  let ?config = def++  describe "DL3062 - Pin versions in go install." $ do+    describe "go install" $ do+      it "go version not pinned" $ do+        ruleCatches "DL3062" "RUN go install example.com/pkg"+        onBuildRuleCatches "DL3062" "RUN go install example.com/pkg"+      it "go version pinned as latest" $ do+        ruleCatches "DL3062" "RUN go install example.com/pkg@latest"+        onBuildRuleCatches "DL3062" "RUN go install example.com/pkg@latest"+      it "go version pinned" $ do+        ruleCatchesNot "DL3062" "RUN go install example.com/pkg@v1.2.3"+        onBuildRuleCatchesNot "DL3062" "RUN go install example.com/pkg@v1.2.3"+    describe "go get" $ do+      it "go version not pinned" $ do+        ruleCatches "DL3062" "RUN go get example.com/pkg"+        onBuildRuleCatches "DL3062" "RUN go get example.com/pkg"+      it "go version pinned as latest" $ do+        ruleCatches "DL3062" "RUN go get example.com/pkg@latest"+        onBuildRuleCatches "DL3062" "RUN go get example.com/pkg@latest"+      it "go version pinned" $ do+        ruleCatchesNot "DL3062" "RUN go get example.com/pkg@v1.2.3"+        onBuildRuleCatchesNot "DL3062" "RUN go get example.com/pkg@v1.2.3"+    describe "go run" $ do+      it "go version not pinned" $ do+        ruleCatches "DL3062" "RUN go run example.com/pkg"+        onBuildRuleCatches "DL3062" "RUN go run example.com/pkg"+      it "go version pinned as latest" $ do+        ruleCatches "DL3062" "RUN go run example.com/pkg@latest"+        onBuildRuleCatches "DL3062" "RUN go run example.com/pkg@latest"+      it "go version pinned" $ do+        ruleCatchesNot "DL3062" "RUN go run example.com/pkg@v1.2.3"+        onBuildRuleCatchesNot "DL3062" "RUN go run example.com/pkg@v1.2.3"