packages feed

hadolint 2.1.0 → 2.2.0

raw patch · 16 files changed

+320/−24 lines, 16 filesdep ~language-dockerPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: language-docker

API changes (from Hackage documentation)

+ Hadolint.Formatter.Format: errorMessage :: (VisualStream s, ShowErrorComponent e) => ParseErrorBundle s e -> String
+ Hadolint.Rule.DL3042: instance GHC.Classes.Eq Hadolint.Rule.DL3042.Stage
+ Hadolint.Rule.DL3042: instance GHC.Classes.Ord Hadolint.Rule.DL3042.Stage
+ Hadolint.Rule.DL3059: instance GHC.Classes.Eq Hadolint.Rule.DL3059.Acc
+ Hadolint.Rule.DL3059: instance GHC.Show.Show Hadolint.Rule.DL3059.Acc
+ Hadolint.Rule.DL3059: rule :: Rule args
+ Hadolint.Rule.DL3060: rule :: Rule ParsedShell

Files

README.md view
@@ -36,6 +36,10 @@ $ docker run --rm -i ghcr.io/hadolint/hadolint < Dockerfile ``` +or if you are using Windows PowerShell:+```powershell+> cat .\Dockerfile | docker run --rm -i hadolint/hadolint+``` ## Install  You can download prebuilt binaries for OSX, Windows and Linux from the latest@@ -107,6 +111,21 @@   - my-company.com:5000 ``` +If you want to override the severity of specific rules, you can do that too:+```yaml+override:+  error:+    - DL3001+    - DL3002+  warning:+    - DL3042+    - DL3033+  info:+    - DL3032+  style:+    - DL3015+```+ Configuration files can be used globally or per project. By default, `hadolint` will look for a configuration file in the current directory with the name `.hadolint.yaml` @@ -165,6 +184,7 @@ ```yaml label-schema:   author: text+  contact: email   created: rfc3339   version: semver   documentation: url@@ -181,6 +201,7 @@ | url     | A URI as described in [RFC 3986][rfc3986]          | | hash    | Either a short or a long [Git hash][githash]       | | spdx    | An [SPDX license identifier][spdxid]               |+| email   | An email address conforming to [RFC 5322][rfc5322] |  By default, Hadolint ignores any label not specified in the label schema. To warn on such additional labels, turn on strict labels:@@ -239,7 +260,7 @@ | [DL3002](https://github.com/hadolint/hadolint/wiki/DL3002)   | Last user should not be root.                                                                                                                       | | [DL3003](https://github.com/hadolint/hadolint/wiki/DL3003)   | Use WORKDIR to switch to a directory.                                                                                                               | | [DL3004](https://github.com/hadolint/hadolint/wiki/DL3004)   | Do not use sudo as it leads to unpredictable behavior. Use a tool like gosu to enforce root.                                                        |-| [DL3005](https://github.com/hadolint/hadolint/wiki/DL3005)   | Do not use apt-get upgrade or dist-upgrade.                                                                                                         |+| [DL3005](https://github.com/hadolint/hadolint/wiki/DL3005)   | Do not use apt-get dist-upgrade.                                                                                                                    | | [DL3006](https://github.com/hadolint/hadolint/wiki/DL3006)   | Always tag the version of an image explicitly.                                                                                                      | | [DL3007](https://github.com/hadolint/hadolint/wiki/DL3007)   | Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag.                                         | | [DL3008](https://github.com/hadolint/hadolint/wiki/DL3008)   | Pin versions in apt-get install.                                                                                                                    |@@ -293,6 +314,8 @@ | [DL3056](https://github.com/hadolint/hadolint/wiki/DL3056)   | Label `<label>` does not conform to semantic versioning.                                                                                            | | [DL3057](https://github.com/hadolint/hadolint/wiki/DL3057)   | `HEALTHCHECK` instruction missing.                                                                                                                  | | [DL3058](https://github.com/hadolint/hadolint/wiki/DL3058)   | Label `<label>` is not a valid email format - must be conform to RFC5322.                                                                           |+| [DL3059](https://github.com/hadolint/hadolint/wiki/DL3059)   | Multiple consecutive `RUN` instructions. Consider consolidation.                                                                                    |+| [DL3060](https://github.com/hadolint/hadolint/wiki/DL3060)   | `yarn cache clean` missing after `yarn install` was run.                                                                                            | | [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.                                                                                                                  |@@ -417,3 +440,4 @@ [rfc3986]: https://www.ietf.org/rfc/rfc3986.txt [githash]: https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection [spdxid]: https://spdx.org/licenses/+[rfc5322]: https://www.ietf.org/rfc/rfc5322.txt
hadolint.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 3a6af1aecfc32d960fd1df8de865bb60b8682f8877a9b3bb848a3f59c50c063d+-- hash: 391fff086908037463e385c3450cb46068242dcf3932fbfbb995f2bf9ee6a98c  name:           hadolint-version:        2.1.0+version:        2.2.0 synopsis:       Dockerfile Linter JavaScript API description:    A smarter Dockerfile linter that helps you build best practice Docker images. category:       Development@@ -103,6 +103,8 @@       Hadolint.Rule.DL3056       Hadolint.Rule.DL3057       Hadolint.Rule.DL3058+      Hadolint.Rule.DL3059+      Hadolint.Rule.DL3060       Hadolint.Rule.DL4000       Hadolint.Rule.DL4001       Hadolint.Rule.DL4003@@ -135,7 +137,7 @@     , filepath     , foldl     , ilist-    , language-docker >=9.1.3 && <10+    , language-docker >=9.2.0 && <10     , megaparsec >=9.0.0     , mtl     , network-uri@@ -163,7 +165,7 @@     , containers     , gitrev >=1.3.1     , hadolint-    , language-docker >=9.1.3 && <10+    , language-docker >=9.2.0 && <10     , megaparsec >=9.0.0     , optparse-applicative >=0.14.0     , text@@ -235,6 +237,8 @@       DL3056       DL3057       DL3058+      DL3059+      DL3060       DL4000       DL4001       DL4003@@ -260,7 +264,7 @@     , foldl     , hadolint     , hspec-    , language-docker >=9.1.3 && <10+    , language-docker >=9.2.0 && <10     , megaparsec >=9.0.0     , split >=0.2     , text
src/Hadolint/Config.hs view
@@ -145,7 +145,7 @@           "\terror:",           "\t\t- DL3008\n\n",           "The key 'trustedRegistries' should contain the names of the allowed docker registries:\n",-          "allowedRegistries:",+          "trustedRegistries:",           "\t- docker.io",           "\t- my-company.com",           "",
src/Hadolint/Formatter/Format.hs view
@@ -1,6 +1,7 @@ module Hadolint.Formatter.Format   ( severityText,     stripNewlines,+    errorMessage,     errorMessageLine,     errorPosition,     errorPositionPretty,@@ -53,6 +54,10 @@ errorMessageLine :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => ParseErrorBundle s e -> String errorMessageLine err@(ParseErrorBundle e _) =   errorPositionPretty err ++ " " ++ parseErrorTextPretty (NE.head e)++errorMessage :: (VisualStream s, ShowErrorComponent e) => ParseErrorBundle s e -> String+errorMessage (ParseErrorBundle e _) =+  reverse . dropWhile (== '\n') . reverse $ parseErrorTextPretty (NE.head e)  errorPositionPretty :: TraversableStream s => ParseErrorBundle s e -> String errorPositionPretty err = sourcePosPretty (errorPosition err)
src/Hadolint/Formatter/Json.hs view
@@ -9,7 +9,12 @@ import qualified Data.ByteString.Lazy.Char8 as B import Data.Sequence (Seq) import qualified Data.Text as Text-import Hadolint.Formatter.Format (Result (..), errorPosition, severityText)+import Hadolint.Formatter.Format+  ( Result (..),+    errorPosition,+    severityText,+    errorMessage+  ) import Hadolint.Rule (CheckFailure (..), DLSeverity (..), unRuleCode) import Text.Megaparsec (TraversableStream) import Text.Megaparsec.Error@@ -37,7 +42,7 @@         "column" .= unPos (sourceColumn pos),         "level" .= severityText DLErrorC,         "code" .= ("DL1000" :: Text.Text),-        "message" .= errorBundlePretty err+        "message" .= errorMessage err       ]     where       pos = errorPosition err
src/Hadolint/Process.hs view
@@ -67,6 +67,8 @@ import qualified Hadolint.Rule.DL3056 import qualified Hadolint.Rule.DL3057 import qualified Hadolint.Rule.DL3058+import qualified Hadolint.Rule.DL3059+import qualified Hadolint.Rule.DL3060 import qualified Hadolint.Rule.DL4000 import qualified Hadolint.Rule.DL4001 import qualified Hadolint.Rule.DL4003@@ -194,8 +196,10 @@     <> Hadolint.Rule.DL3054.rule labelSchema     <> Hadolint.Rule.DL3055.rule labelSchema     <> Hadolint.Rule.DL3056.rule labelSchema-    <> Hadolint.Rule.DL3058.rule labelSchema     <> Hadolint.Rule.DL3057.rule+    <> Hadolint.Rule.DL3058.rule labelSchema+    <> Hadolint.Rule.DL3059.rule+    <> Hadolint.Rule.DL3060.rule     <> Hadolint.Rule.DL4000.rule     <> Hadolint.Rule.DL4001.rule     <> Hadolint.Rule.DL4003.rule
src/Hadolint/Rule/DL3005.hs view
@@ -10,8 +10,8 @@   where     code = "DL3005"     severity = DLErrorC-    message = "Do not use apt-get upgrade or dist-upgrade"+    message = "Do not use apt-get dist-upgrade"     check (Run (RunArgs args _)) =-      foldArguments (Shell.noCommands (Shell.cmdHasArgs "apt-get" ["upgrade", "dist-upgrade"])) args+      foldArguments (Shell.noCommands (Shell.cmdHasArgs "apt-get" ["dist-upgrade"])) args     check _ = True {-# INLINEABLE rule #-}
src/Hadolint/Rule/DL3042.hs view
@@ -1,25 +1,66 @@ module Hadolint.Rule.DL3042 (rule) where  import Data.List (isInfixOf)+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 Language.Docker.Syntax ++-- This data encapsulates the name of a build stage. It may be None withing an+-- `ONBUILD` context.+data Stage+  = Stage {stage :: Text}+  | None+  deriving (Eq, Ord)++data Acc+  = Acc {current :: Stage, noCacheMap :: Map Stage Bool}+  | Empty++ rule :: Rule Shell.ParsedShell-rule = simpleRule code severity message check+rule = customRule check (emptyState Empty)   where     code = "DL3042"     severity = DLWarningC-    message =-      "Avoid use of cache directory with pip. Use `pip install --no-cache-dir <package>`"-    check (Run (RunArgs args _)) = foldArguments (Shell.noCommands forgotNoCacheDir) args-    check _ = True-    forgotNoCacheDir cmd =-      Shell.isPipInstall cmd && not (usesNoCacheDir cmd) && not (isPipWrapper cmd)-    usesNoCacheDir cmd = "--no-cache-dir" `elem` Shell.getArgs cmd+    message = "Avoid use of cache directory with pip.\+              \ 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 _))+      | Acc s ncm <- state st, Just True <- Map.lookup s ncm = st+      | foldArguments pipNoCacheDirIsSet args = st+      | foldArguments (Shell.noCommands forgotNoCacheDir) args = st+      | otherwise = st |> addFail CheckFailure {..}+    check _ st _ = st {-# INLINEABLE rule #-} +forgotNoCacheDir :: Shell.Command -> Bool+forgotNoCacheDir cmd =  Shell.isPipInstall cmd+    && not (usesNoCacheDir cmd)+    && not (isPipWrapper cmd)++usesNoCacheDir :: Shell.Command -> Bool+usesNoCacheDir cmd = "--no-cache-dir" `elem` Shell.getArgs cmd++pipNoCacheDirIsSet :: Shell.ParsedShell -> Bool+pipNoCacheDirIsSet shell = any+  (`Text.isPrefixOf` Text.drop 1+    (Text.dropWhile (/= '=')+      (snd+        (Text.breakOn "PIP_NO_CACHE_DIR=" (Shell.original shell)+        )+      )+    )+  ) truthy+ isPipWrapper :: Shell.Command -> Bool isPipWrapper cmd@(Shell.Command name _ _) = isWrapper "pipx" || isWrapper "pipenv"   where@@ -27,3 +68,52 @@     isWrapper w =       w `Text.isInfixOf` name         || ("python" `Text.isPrefixOf` name && ["-m", w] `isInfixOf` Shell.getArgs cmd)++rememberStage :: BaseImage -> Acc -> Acc+rememberStage BaseImage {alias = Just als} Empty =+  Acc+    { current = Stage {stage = unImageAlias als},+      noCacheMap = mempty+    }+rememberStage BaseImage {alias = Nothing, image} Empty =+  Acc+    { current = Stage {stage = imageName image},+      noCacheMap = mempty+    }+rememberStage BaseImage {alias = Just als, image} Acc {..} =+  Acc+    { current = Stage {stage = unImageAlias als},+      noCacheMap =+        let parentValue =+              Map.lookup (Stage {stage = imageName image}) noCacheMap |> fromMaybe False+         in noCacheMap |> Map.insert (Stage {stage = unImageAlias als}) parentValue+    }+rememberStage BaseImage {alias = Nothing, image} Acc {..} =+  Acc+    { current = Stage {stage = imageName image},+      noCacheMap =+        let parentValue =+              Map.lookup (Stage {stage = imageName image}) noCacheMap |> fromMaybe False+         in noCacheMap |> Map.insert (Stage {stage = imageName image}) parentValue+    }++registerEnv :: Pairs -> Acc -> Acc+registerEnv pairs Empty+  | pipNoCacheDirSet pairs =+    Acc {current = None, noCacheMap = Map.insert None True Map.empty}+  | otherwise = Empty+registerEnv pairs Acc {..}+  | pipNoCacheDirSet pairs =+    Acc {current, noCacheMap = Map.insert current True noCacheMap}+  | otherwise = Acc {..}++pipNoCacheDirSet :: Pairs -> Bool+pipNoCacheDirSet [] = False+pipNoCacheDirSet pairs+  | isNothing (lookup "PIP_NO_CACHE_DIR" pairs) = False+  | val <- lookup "PIP_NO_CACHE_DIR" pairs,+    isJust val && fromJust val `notElem` truthy = False+  | otherwise = True++truthy :: Set Text+truthy = Set.fromList ["1", "true", "True", "TRUE", "on", "On", "ON", "yes", "Yes", "YES"]
+ src/Hadolint/Rule/DL3059.hs view
@@ -0,0 +1,29 @@+module Hadolint.Rule.DL3059 (rule) where++import Hadolint.Rule+import Language.Docker.Syntax+++data Acc+  = Acc RunFlags+  | Empty+  deriving (Eq, Show)++rule :: Rule args+rule = customRule check (emptyState Empty)+  where+    code = "DL3059"+    severity = DLInfoC+    message = "Multiple consecutive `RUN` instructions. Consider consolidation."++    check line st (Run (RunArgs _ flags))+      | state st == Acc flags = st |> addFail CheckFailure {..}+      | otherwise = st |> modify (remember flags)+    check _ st _ = st |> modify reset+{-# INLINEABLE rule #-}++remember :: RunFlags -> Acc -> Acc+remember flags _ = Acc flags++reset :: Acc -> Acc+reset _ = Empty
+ src/Hadolint/Rule/DL3060.hs view
@@ -0,0 +1,27 @@+module Hadolint.Rule.DL3060 (rule) where++import Hadolint.Rule+import qualified Hadolint.Shell as Shell+import Language.Docker.Syntax+++rule :: Rule Shell.ParsedShell+rule = simpleRule code severity message check+  where+    code = "DL3060"+    severity = DLInfoC+    message = "`yarn cache clean` missing after `yarn install` was run."++    check (Run (RunArgs args _)) =+      foldArguments (Shell.noCommands yarnInstall) args+        || ( foldArguments (Shell.anyCommands yarnInstall) args+              && foldArguments (Shell.anyCommands yarnCacheClean) args+           )+    check _ = True+{-# INLINEABLE rule #-}++yarnInstall :: Shell.Command -> Bool+yarnInstall = Shell.cmdHasArgs "yarn" ["install"]++yarnCacheClean :: Shell.Command -> Bool+yarnCacheClean = Shell.cmdHasArgs "yarn" ["cache", "clean"]
test/DL3005.hs view
@@ -8,9 +8,6 @@ tests = do   let ?rulesConfig = mempty   describe "DL3005" $ do-    it "apt-get upgrade" $ do-      ruleCatches "DL3005" "RUN apt-get update && apt-get upgrade"-      onBuildRuleCatches "DL3005" "RUN apt-get update && apt-get upgrade"     it "apt-get dist-upgrade" $ do       ruleCatches "DL3005" "RUN apt-get update && apt-get dist-upgrade"       onBuildRuleCatches "DL3005" "RUN apt-get update && apt-get dist-upgrade"
test/DL3042.hs view
@@ -1,5 +1,6 @@ module DL3042 (tests) where +import Data.Text as Text import Helpers import Test.Hspec @@ -32,3 +33,54 @@     it "don't match on pipenv" $ do       ruleCatchesNot "DL3042" "RUN pipenv install library"       onBuildRuleCatchesNot "DL3042" "RUN pipenv install library"++    -- ENV won't work with the onBuildRuleCatches[Not] wrapper:+    it "respect ENV PIP_NO_CACHE_DIR with truthy values" $ do+      ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=1\nRUN pip install MySQL_python"+      ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=on\nRUN pip install MySQL_python"+      ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=yes\nRUN pip install MySQL_python"+      ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=true\nRUN pip install MySQL_python"+    it "respect RUN PIP_NO_CACHE_DIR=... with truthy values" $ do+      ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=1 pip install MySQL_python"+      ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=on pip install MySQL_python"+      ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=yes pip install MySQL_python"+      ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=true pip install MySQL_python"+    it "respect RUN export PIP_NO_CACHE_DIR=... with truthy values" $ do+      ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=1 && pip install MySQL_python"+      ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=on && pip install MySQL_python"+      ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=yes && pip install MySQL_python"+      ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=true && pip install MySQL_python"+    it "respect ENV PIP_NO_CACHE_DIR with falsy values" $ do+      ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=0\nRUN pip install MySQL_python"+      ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=off\nRUN pip install MySQL_python"+      ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=no\nRUN pip install MySQL_python"+      ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=false\nRUN pip install MySQL_python"+    it "respect RUN PIP_NO_CACHE_DIR=... with falsy values" $ do+      ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=0 pip install MySQL_python"+      ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=off pip install MySQL_python"+      ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=no pip install MySQL_python"+      ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=false pip install MySQL_python"+    it "respect RUN export PIP_NO_CACHE_DIR=... with falsy values" $ do+      ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=0 && pip install MySQL_python"+      ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=off && pip install MySQL_python"+      ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=no && pip install MySQL_python"+      ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=false && pip install MySQL_python"++    it "don't trigger if PIP_NO_CACHE_DIR is inherited" $+      let dockerFile = Text.unlines+            [ "FROM debian:buster as base",+              "ENV PIP_NO_CACHE_DIR=1",+              "FROM base",+              "RUN pip install six"+            ]+       in do+        ruleCatchesNot "DL3042" dockerFile+    it "trigger if PIP_NO_CACHE_DIR is not inherited" $+      let dockerFile = Text.unlines+            [ "FROM debian:buster as base",+              "ENV PIP_NO_CACHE_DIR=1",+              "FROM debian:buster",+              "RUN pip install six"+            ]+       in do+        ruleCatches "DL3042" dockerFile
+ test/DL3059.hs view
@@ -0,0 +1,31 @@+module DL3059 (tests) where++import Data.Text as Text+import Helpers+import Test.Hspec+++tests :: SpecWith ()+tests = do+  let ?rulesConfig = mempty+  describe "DL3059 - Multiple consecutive `RUN` instructions" $ do+    it "ok with no `RUN` at all" $ do+      ruleCatchesNot "DL3059" "FROM debian:10"+    it "ok with one `RUN`" $ do+      ruleCatchesNot "DL3059" "RUN /foo.sh"+    it "ok with two not consecutive `RUN`" $ do+      ruleCatchesNot "DL3059" "RUN /foo.sh\nWORKDIR /\nRUN /bar.sh"+    it "not ok with two consecutive `RUN`s" $ do+      ruleCatches "DL3059" "RUN /foo.sh\nRUN /bar.sh"+    it "ok with two consecutive `RUN`s when flags are different 1" $ do+      ruleCatchesNot "DL3059" "RUN --mount=type=secret,id=foo /foo.sh\nRUN /bar.sh"+    it "ok with two consecutive `RUN`s when flags are different 2" $ do+      let dfile = [ "RUN --mount=type=secret,id=foo /foo.sh",+                    "RUN --mount=type=secret,id=bar /bar.sh"+                  ]+       in ruleCatchesNot "DL3059" $ Text.unlines dfile+    it "not ok with two consecutive `RUN`s when flags are equal" $ do+      let dfile = [ "RUN --mount=type=secret,id=foo /foo.sh",+                    "RUN --mount=type=secret,id=foo /bar.sh"+                  ]+       in ruleCatches "DL3059" $ Text.unlines dfile
+ test/DL3060.hs view
@@ -0,0 +1,19 @@+module DL3060 (tests) where++import Helpers+import Test.Hspec+++tests :: SpecWith ()+tests = do+  let ?rulesConfig = mempty+  describe "DL3060 - `yarn cache clean` missing after `yarn install`" $ do+    it "ok with non-yarn commands" $ do+      ruleCatchesNot "DL3060" "RUN foo"+      onBuildRuleCatchesNot "DL3060" "RUN foo"+    it "not ok with no cache clean" $ do+      ruleCatches "DL3060" "RUN yarn install foo"+      onBuildRuleCatches "DL3060" "RUN yarn install foo"+    it "ok with cache clean" $ do+      ruleCatchesNot "DL3060" "RUN yarn install bar && yarn cache clean"+      onBuildRuleCatchesNot "DL3060" "RUN yarn install bar && yarn cache clean"
test/ShellSpec.hs view
@@ -1,6 +1,5 @@ module ShellSpec where -import Data.Text as Text import Hadolint.Shell import Test.Hspec 
test/Spec.hs view
@@ -59,11 +59,13 @@ import qualified DL3056 import qualified DL3057 import qualified DL3058+import qualified DL3059 import qualified DL4001 import qualified DL4003 import qualified DL4004 import qualified DL4005 import qualified DL4006+import Hadolint.Formatter.Format (errorMessage) import Hadolint.Formatter.TTY (formatError) import Helpers import Language.Docker.Parser@@ -86,6 +88,13 @@         case ast of           Left err -> assertEqual "Unexpected error msg" expectedMsg (formatError err)           Right _ -> assertFailure "AST should fail parsing"+    describe "errorMessage" $+      it "display just the error message" $ do+        let ast = parseText "RUNNN"+            expectedMsg = "missing whitespace"+        case ast of+          Left err -> assertEqual "Unexpected error msg" expectedMsg (errorMessage err)+          Right _ -> assertFailure "AST should fail parsing"     --     describe "Rules can be ignored with inline comments" $ do       it "ignores single rule" $@@ -220,6 +229,7 @@     DL3056.tests     DL3057.tests     DL3058.tests+    DL3059.tests     DL4001.tests     DL4003.tests     DL4004.tests