hadolint 1.17.0 → 1.17.1
raw patch · 5 files changed
+156/−31 lines, 5 filesdep +HsYAMLdep −yamlPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: HsYAML
Dependencies removed: yaml
API changes (from Hackage documentation)
- Hadolint.Config: instance Data.Aeson.Types.Class.FromJSON Hadolint.Config.ConfigFile
- Hadolint.Formatter.Codacy: instance Data.Aeson.Types.Class.ToJSON Hadolint.Formatter.Codacy.Issue
- Hadolint.Formatter.Codeclimate: instance Data.Aeson.Types.Class.ToJSON Hadolint.Formatter.Codeclimate.Issue
- Hadolint.Formatter.Codeclimate: instance Data.Aeson.Types.Class.ToJSON Hadolint.Formatter.Codeclimate.Location
- Hadolint.Formatter.Codeclimate: instance Data.Aeson.Types.Class.ToJSON Hadolint.Formatter.Codeclimate.Pos
- Hadolint.Formatter.Json: instance (Text.Megaparsec.Stream.Stream s, Text.Megaparsec.Error.ShowErrorComponent e) => Data.Aeson.Types.Class.ToJSON (Hadolint.Formatter.Json.JsonFormat s e)
+ Hadolint.Config: ConfigFile :: Maybe [IgnoreRule] -> Maybe [TrustedRegistry] -> ConfigFile
+ Hadolint.Config: [ignoredRules] :: ConfigFile -> Maybe [IgnoreRule]
+ Hadolint.Config: [trustedRegistries] :: ConfigFile -> Maybe [TrustedRegistry]
+ Hadolint.Config: data ConfigFile
+ Hadolint.Config: instance Data.YAML.FromYAML Hadolint.Config.ConfigFile
+ Hadolint.Formatter.Codacy: instance Data.Aeson.Types.ToJSON.ToJSON Hadolint.Formatter.Codacy.Issue
+ Hadolint.Formatter.Codeclimate: instance Data.Aeson.Types.ToJSON.ToJSON Hadolint.Formatter.Codeclimate.Issue
+ Hadolint.Formatter.Codeclimate: instance Data.Aeson.Types.ToJSON.ToJSON Hadolint.Formatter.Codeclimate.Location
+ Hadolint.Formatter.Codeclimate: instance Data.Aeson.Types.ToJSON.ToJSON Hadolint.Formatter.Codeclimate.Pos
+ Hadolint.Formatter.Json: instance (Text.Megaparsec.Stream.Stream s, Text.Megaparsec.Error.ShowErrorComponent e) => Data.Aeson.Types.ToJSON.ToJSON (Hadolint.Formatter.Json.JsonFormat s e)
Files
- hadolint.cabal +6/−4
- src/Hadolint/Config.hs +25/−24
- src/Hadolint/Rules.hs +20/−1
- test/ConfigSpec.hs +54/−0
- test/Spec.hs +51/−2
hadolint.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: ad3384cc7d2f156af93ef1a394947b12be4d8ca81081af3f44fe90c9c2e0bf94+-- hash: 099ae897b9dd6e8bd846e847751883f65ef076c560a6157945fbeb7be74a5859 name: hadolint-version: 1.17.0+version: 1.17.1 synopsis: Dockerfile Linter JavaScript API description: A smarter Dockerfile linter that helps you build best practice Docker images. category: Development@@ -44,7 +44,8 @@ src ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -optP-Wno-nonportable-include-path build-depends:- ShellCheck >=0.6.0+ HsYAML+ , ShellCheck >=0.6.0 , aeson , base >=4.8 && <5 , bytestring@@ -57,7 +58,6 @@ , split >=0.2 , text , void- , yaml default-language: Haskell2010 executable hadolint@@ -84,12 +84,14 @@ type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:+ ConfigSpec Paths_hadolint hs-source-dirs: test ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -optP-Wno-nonportable-include-path build-depends: HUnit >=1.2+ , HsYAML , ShellCheck >=0.6.0 , aeson , base >=4.8 && <5
src/Hadolint/Config.hs view
@@ -1,12 +1,15 @@ {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-} -module Hadolint.Config (applyConfig) where+module Hadolint.Config (applyConfig, ConfigFile(..)) where import Control.Monad (filterM) import Data.Coerce (coerce) import Data.Maybe (fromMaybe, listToMaybe)+import qualified Data.ByteString as Bytes import qualified Data.Set as Set-import qualified Data.Yaml as Yaml+import qualified Data.YAML as Yaml+import Data.YAML ((.:?)) import GHC.Generics import qualified Language.Docker as Docker import System.Directory@@ -18,11 +21,14 @@ import qualified Hadolint.Rules as Rules data ConfigFile = ConfigFile- { ignored :: Maybe [Lint.IgnoreRule]+ { ignoredRules :: Maybe [Lint.IgnoreRule] , trustedRegistries :: Maybe [Lint.TrustedRegistry] } deriving (Show, Eq, Generic) -instance Yaml.FromJSON ConfigFile+instance Yaml.FromYAML ConfigFile where+ parseYAML = Yaml.withMap "ConfigFile" $ \m -> ConfigFile+ <$> m .:? "ignored"+ <*> m .:? "trustedRegistries" -- | If both the ignoreRules and rulesConfig properties of Lint options are empty -- then this function will fill them with the default found in the passed config@@ -46,8 +52,8 @@ listToMaybe <$> filterM doesFileExist [localConfigFile, configFile] parseAndApply :: FilePath -> IO (Either String Lint.LintOptions) parseAndApply configFile = do- result <- Yaml.decodeFileEither configFile- case result of+ contents <- Bytes.readFile configFile+ case Yaml.decode1Strict contents of Left err -> return $ Left (formatError err configFile) Right (ConfigFile ignore trusted) -> return (Right (override ignore trusted)) -- | Applies the configuration found in the file to the passed Lint.LintOptions@@ -64,21 +70,16 @@ toRules (Just trusted) = Rules.RulesConfig (Set.fromList . coerce $ trusted) toRules _ = mempty formatError err config =- case err of- Yaml.AesonException e ->- unlines- [ "Error parsing your config file in '" ++ config ++ "':"- , "It should contain one of the keys 'ignored' or 'trustedRegistries'. For example:\n"- , "ignored:"- , "\t- DL3000"- , "\t- SC1099\n\n"- , "The key 'trustedRegistries' should contain the names of the allowed docker registries:\n"- , "allowedRegistries:"- , "\t- docker.io"- , "\t- my-company.com"- , ""- , e- ]- _ ->- "Error parsing your config file in '" ++- config ++ "': " ++ Yaml.prettyPrintParseException err+ unlines+ [ "Error parsing your config file in '" ++ config ++ "':"+ , "It should contain one of the keys 'ignored' or 'trustedRegistries'. For example:\n"+ , "ignored:"+ , "\t- DL3000"+ , "\t- SC1099\n\n"+ , "The key 'trustedRegistries' should contain the names of the allowed docker registries:\n"+ , "allowedRegistries:"+ , "\t- docker.io"+ , "\t- my-company.com"+ , ""+ , err+ ]
src/Hadolint/Rules.hs view
@@ -326,6 +326,7 @@ severity = WarningC message = "Either use Wget or Curl but not both" check state _ (Run args) = argumentsRule (detectDoubleUsage state) args+ check _ _ (From _) = withState Set.empty True -- Reset the state for each stage check state _ _ = withState state True detectDoubleUsage state args = let newArgs = extractCommands args@@ -546,7 +547,23 @@ , format <- archiveFormats ] check _ = True- archiveFormats = [".tar", ".gz", ".bz2", "xz"]+ archiveFormats =+ [ ".tar"+ , ".tar.bz2"+ , ".tb2"+ , ".tbz"+ , ".tbz2"+ , ".tar.gz"+ , ".tgz"+ , ".tpz"+ , ".tar.lz"+ , ".tar.lzma"+ , ".tlz"+ , ".tar.xz"+ , ".txz"+ , ".tar.Z"+ , ".tZ"+ ] invalidPort :: Rule invalidPort = instructionRule code severity message check@@ -830,7 +847,9 @@ [ arg | cmd <- Shell.findCommands args , Shell.cmdHasArgs "gem" ["install", "i"] cmd+ , not (Shell.cmdHasArgs "gem" ["-v"] cmd) , arg <- Shell.getArgsNoFlags cmd , arg /= "install" , arg /= "i"+ , not ("--" `isPrefixOf` arg) ]
+ test/ConfigSpec.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE OverloadedStrings #-}+module ConfigSpec where++import Test.HUnit+import Test.Hspec+import Control.Monad (unless)+import qualified Data.ByteString.Char8 as Bytes+import qualified Data.YAML as Yaml++import Hadolint.Config++tests :: SpecWith ()+tests =+ describe "Config" $ do+ it "Parses config with only ignores" $+ let configFile =+ [ "ignored:"+ , "- DL3000"+ , "- SC1010"+ ]+ expected = ConfigFile (Just ["DL3000", "SC1010"]) Nothing+ in assertConfig expected (Bytes.unlines configFile)++ it "Parses config with only trustedRegistries" $+ let configFile =+ [ "trustedRegistries:"+ , "- hub.docker.com"+ , "- my.shady.xyz"+ ]+ expected = ConfigFile Nothing (Just ["hub.docker.com", "my.shady.xyz"])+ in assertConfig expected (Bytes.unlines configFile)++ it "Parses full file" $+ let configFile =+ [ "trustedRegistries:"+ , "- hub.docker.com"+ , ""+ , "ignored:"+ , "- DL3000"+ ]+ expected = ConfigFile (Just ["DL3000"]) (Just ["hub.docker.com"])+ in assertConfig expected (Bytes.unlines configFile)++assertConfig :: HasCallStack => ConfigFile -> Bytes.ByteString -> Assertion+assertConfig config s =+ case Yaml.decode1Strict s of+ Left err ->+ assertFailure err+ Right result ->+ checkResult result+ where+ checkResult result =+ unless (result == config) $+ assertFailure ("Config \n\n" ++ show config ++ "\n\n is not \n\n" ++ show result)
test/Spec.hs view
@@ -12,6 +12,8 @@ import Data.Semigroup ((<>)) import qualified Data.Text as Text +import qualified ConfigSpec+ main :: IO () main = hspec $ do@@ -173,6 +175,12 @@ it "pinned" $ do ruleCatchesNot gemVersionPinned "RUN gem install bundler:1" onBuildRuleCatchesNot gemVersionPinned "RUN gem install bundler:1"+ it "does not warn on -v" $ do+ ruleCatchesNot gemVersionPinned "RUN gem install bundler -v '2.0.1'"+ onBuildRuleCatchesNot gemVersionPinned "RUN gem install bundler -v '2.0.1'"+ it "does not warn on extra flags" $ do+ 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 "apt-get rules" $ do it "apt" $@@ -1064,6 +1072,44 @@ ] in ruleCatchesNot (registryIsAllowed ["random.com"]) $ Text.unlines dockerFile --+ describe "Wget or Curl" $ do+ it "warns when using both wget and curl" $+ let dockerFile =+ [ "FROM node as foo"+ , "RUN wget my.xyz"+ , "RUN curl localhost"+ ]+ in ruleCatches wgetOrCurl $ Text.unlines dockerFile+ it "warns when using both wget and curl in same instruction" $+ let dockerFile =+ [ "FROM node as foo"+ , "RUN wget my.xyz && curl localhost"+ ]+ in ruleCatches wgetOrCurl $ Text.unlines dockerFile+ it "does not warn when using only wget" $+ let dockerFile =+ [ "FROM node as foo"+ , "RUN wget my.xyz"+ ]+ in ruleCatchesNot wgetOrCurl $ Text.unlines dockerFile+ it "does not warn when using both curl and wget in different stages" $+ let dockerFile =+ [ "FROM node as foo"+ , "RUN wget my.xyz"+ , "FROM scratch"+ , "RUN curl localhost"+ ]+ in ruleCatchesNot wgetOrCurl $ Text.unlines dockerFile+ it "does not warns when using both, on a single stage" $+ let dockerFile =+ [ "FROM node as foo"+ , "RUN wget my.xyz"+ , "RUN curl localhost"+ , "FROM scratch"+ , "RUN curl localhost"+ ]+ in ruleCatches wgetOrCurl $ Text.unlines dockerFile+ -- describe "Regression Tests" $ it "Comments with backslashes at the end are just comments" $ let dockerFile =@@ -1077,6 +1123,9 @@ ] in ruleCatches usePipefail $ Text.unlines dockerFile + -- Run tests for the Config module+ ConfigSpec.tests+ assertChecks :: HasCallStack => Rule -> Text.Text -> ([RuleCheck] -> IO a) -> IO a assertChecks rule s makeAssertions = case parseText (s <> "\n") of@@ -1099,8 +1148,8 @@ ruleCatches rule s = assertChecks rule s f where f checks = do- when (length checks /= 1) $- assertFailure $ "Too many errors found: \n" ++ (Text.unpack . Text.unlines . formatChecks $ checks)+ when (null checks) $+ assertFailure "I was expecting to catch at least one error" assertBool "Incorrect line number for result" $ null [c | c <- checks, linenumber c <= 0] onBuildRuleCatches :: HasCallStack => Rule -> Text.Text -> Assertion