packages feed

hadolint 1.12.0 → 1.13.0

raw patch · 5 files changed

+230/−175 lines, 5 files

Files

app/Main.hs view
@@ -5,91 +5,49 @@ module Main where  import Control.Applicative-import Control.Monad (filterM)-import Data.Coerce (coerce)-import Data.Maybe (fromMaybe, listToMaybe)+import qualified Data.List.NonEmpty as NonEmpty import Data.Semigroup ((<>)) import qualified Data.Set as Set import Data.String-import Data.Text (Text) import qualified Data.Version-import qualified Data.Yaml as Yaml import qualified Development.GitRev-import GHC.Generics-import qualified Language.Docker as Docker-import Language.Docker.Syntax (Dockerfile) import Options.Applicative hiding (ParseError) import qualified Paths_hadolint -- version from hadolint.cabal file-import System.Directory-       (XdgDirectory(..), doesFileExist, getCurrentDirectory,-        getXdgDirectory) import System.Exit (exitFailure, exitSuccess)-import System.FilePath ((</>)) -import qualified Hadolint.Formatter.Checkstyle as Checkstyle-import qualified Hadolint.Formatter.Codeclimate as Codeclimate-import qualified Hadolint.Formatter.Format as Format-import qualified Hadolint.Formatter.Json as Json-import qualified Hadolint.Formatter.TTY as TTY-import qualified Hadolint.Formatter.Codacy as Codacy-import qualified Hadolint.Rules as Rules--type IgnoreRule = Text--type TrustedRegistry = Text--data OutputFormat-    = Json-    | TTY-    | CodeclimateJson-    | Checkstyle-    | Codacy-    deriving (Show, Eq)+import qualified Hadolint -data LintOptions = LintOptions+data CommandOptions = CommandOptions     { showVersion :: Bool     , configFile :: Maybe FilePath-    , format :: OutputFormat-    , ignoreRules :: [IgnoreRule]+    , format :: Hadolint.OutputFormat     , dockerfiles :: [String]-    , rulesConfig :: Rules.RulesConfig-    } deriving (Show)--data ConfigFile = ConfigFile-    { ignored :: Maybe [IgnoreRule]-    , trustedRegistries :: Maybe [TrustedRegistry]-    } deriving (Show, Eq, Generic)--instance Yaml.FromJSON ConfigFile--ignoreFilter :: [IgnoreRule] -> Rules.RuleCheck -> Bool-ignoreFilter ignoredRules (Rules.RuleCheck (Rules.Metadata code _ _) _ _ _) =-    code `notElem` ignoredRules+    , lintingOptions :: Hadolint.LintOptions+    } -toOutputFormat :: String -> Maybe OutputFormat-toOutputFormat "json" = Just Json-toOutputFormat "tty" = Just TTY-toOutputFormat "codeclimate" = Just CodeclimateJson-toOutputFormat "checkstyle" = Just Checkstyle-toOutputFormat "codacy" = Just Codacy+toOutputFormat :: String -> Maybe Hadolint.OutputFormat+toOutputFormat "json" = Just Hadolint.Json+toOutputFormat "tty" = Just Hadolint.TTY+toOutputFormat "codeclimate" = Just Hadolint.CodeclimateJson+toOutputFormat "checkstyle" = Just Hadolint.Checkstyle+toOutputFormat "codacy" = Just Hadolint.Codacy toOutputFormat _ = Nothing -showFormat :: OutputFormat -> String-showFormat Json = "json"-showFormat TTY = "tty"-showFormat CodeclimateJson = "codeclimate"-showFormat Checkstyle = "checkstyle"-showFormat Codacy = "codacy"+showFormat :: Hadolint.OutputFormat -> String+showFormat Hadolint.Json = "json"+showFormat Hadolint.TTY = "tty"+showFormat Hadolint.CodeclimateJson = "codeclimate"+showFormat Hadolint.Checkstyle = "checkstyle"+showFormat Hadolint.Codacy = "codacy" -parseOptions :: Parser LintOptions+parseOptions :: Parser CommandOptions parseOptions =-    LintOptions <$> -- CLI options parser definition+    CommandOptions <$> -- CLI options parser definition     version <*>     configFile <*>     outputFormat <*>-    ignoreList <*>     files <*>-    parseRulesConfig+    lintOptions   where     version = switch (long "version" <> short 'v' <> help "Show version")     --@@ -106,8 +64,9 @@             (maybeReader toOutputFormat)             (long "format" <> -- options for the output format              short 'f' <>-             help "The output format for the results [tty | json | checkstyle | codeclimate | codacy]" <>-             value TTY <> -- The default value+             help+                 "The output format for the results [tty | json | checkstyle | codeclimate | codacy]" <>+             value Hadolint.TTY <> -- The default value              showDefaultWith showFormat <>              completeWith ["tty", "json", "checkstyle", "codeclimate", "codacy"])     --@@ -122,9 +81,12 @@     -- | Parse a list of dockerfile names     files = many (argument str (metavar "DOCKERFILE..." <> action "file"))     ---    -- | Parses all the optional rules configuration+    -- | Parse the rule ignore list and the rules configuration into a LintOptions+    lintOptions = Hadolint.LintOptions <$> ignoreList <*> parseRulesConfig+    --+    -- | Parse all the optional rules configuration     parseRulesConfig =-        Rules.RulesConfig . Set.fromList . fmap fromString <$>+        Hadolint.RulesConfig . Set.fromList . fmap fromString <$>         many             (strOption                  (long "trusted-registry" <>@@ -132,118 +94,29 @@                   metavar "REGISTRY (e.g. docker.io)"))  main :: IO ()-main = execParser opts >>= applyConfig >>= lint+main = do+    cmd <- execParser opts+    execute cmd   where+    execute CommandOptions {showVersion = True} = putStrLn getVersion >> exitSuccess+    execute CommandOptions {dockerfiles = []} =+        putStrLn "Please provide a Dockerfile" >> exitFailure+    execute cmd = do+        lintConfig <- Hadolint.applyConfig (configFile cmd) (lintingOptions cmd)+        let files = NonEmpty.fromList (dockerfiles cmd)+        case lintConfig of+            Left err -> error err+            Right conf -> do+                res <- Hadolint.lint conf files+                Hadolint.printResultsAndExit (format cmd) res     opts =         info             (helper <*> parseOptions)             (fullDesc <> progDesc "Lint Dockerfile for errors and best practices" <>              header "hadolint - Dockerfile Linter written in Haskell") -applyConfig :: LintOptions -> IO LintOptions-applyConfig o-    | not (null (ignoreRules o)) && rulesConfig o /= mempty = return o-    | otherwise = do-        theConfig <--            case configFile o of-                Nothing -> findConfig-                c -> return c-        case theConfig of-            Nothing -> return o-            Just config -> parseAndApply config-  where-    findConfig = do-        localConfigFile <- (</> ".hadolint.yaml") <$> getCurrentDirectory-        configFile <- getXdgDirectory XdgConfig "hadolint.yaml"-        listToMaybe <$> filterM doesFileExist [localConfigFile, configFile]-    parseAndApply config = do-        result <- Yaml.decodeFileEither config-        case result of-            Left err -> printError err config-            Right (ConfigFile ignore trusted) -> return (override ignore trusted)-    -- | Applies the configuration found in the file to the passed LintOptions-    override ignore trusted = applyTrusted trusted . applyIgnore ignore $ o-    applyIgnore ignore opts =-        case ignoreRules opts of-            [] -> opts {ignoreRules = fromMaybe [] ignore}-            _ -> opts-    applyTrusted trusted opts-        | null (Rules.allowedRegistries (rulesConfig opts)) =-            opts {rulesConfig = toRules trusted <> rulesConfig opts}-        | otherwise = opts-    -- | Converts a list of TrustedRegistry to a RulesConfig record-    toRules (Just trusted) = Rules.RulesConfig (Set.fromList . coerce $ trusted)-    toRules _ = mempty-    printError err config =-        case err of-            Yaml.AesonException e ->-                error $-                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 $-                "Error parsing your config file in  '" ++-                config ++ "': " ++ Yaml.prettyPrintParseException err---- | Support UNIX convention of passing "-" instead of "/dev/stdin"-parseFilename :: String -> String-parseFilename "-" = "/dev/stdin"-parseFilename s = s- getVersion :: String getVersion     | $(Development.GitRev.gitDescribe) == "UNKNOWN" =         "Haskell Dockerfile Linter " ++ Data.Version.showVersion Paths_hadolint.version ++ "-no-git"     | otherwise = "Haskell Dockerfile Linter " ++ $(Development.GitRev.gitDescribe)---- | Performs the process of parsing the dockerfile and analyzing it with all the applicable--- rules, depending on the list of ignored rules.--- Depending on the preferred printing format, it will output the results to stdout-lint :: LintOptions -> IO ()-lint LintOptions {showVersion = True} = putStrLn getVersion >> exitSuccess-lint LintOptions {dockerfiles = []} = putStrLn "Please provide a Dockerfile" >> exitFailure-lint LintOptions {ignoreRules = ignoreList, dockerfiles = dFiles, format, rulesConfig} = do-    processedFiles <- mapM (lintDockerfile ignoreList) dFiles-    let allResults = results processedFiles-    printResult allResults-    if allResults /= mempty-        then exitFailure-        else exitSuccess-  where-    results = foldMap Format.toResult -- Parse and check rules for each dockerfile,-                                      -- then convert them to a Result and combine with-                                      -- the result of the previous dockerfile results-    printResult res =-        case format of-            TTY -> TTY.printResult res-            Json -> Json.printResult res-            Checkstyle -> Checkstyle.printResult res-            CodeclimateJson -> Codeclimate.printResult res >> exitSuccess-            Codacy -> Codacy.printResult res >> exitSuccess-    lintDockerfile ignoreRules dockerFile = do-        ast <- Docker.parseFile (parseFilename dockerFile)-        return (processedFile ast)-      where-        processedFile = fmap processRules-        processRules fileLines = filter ignoredRules (analyzeAll rulesConfig fileLines)-        ignoredRules = ignoreFilter ignoreRules---- | Returns the result of applying all the rules to the given dockerfile-analyzeAll :: Rules.RulesConfig -> Dockerfile -> [Rules.RuleCheck]-analyzeAll config = Rules.analyze (Rules.rules ++ Rules.optionalRules config)---- | Helper to analyze AST quickly in GHCI-analyzeEither :: Rules.RulesConfig -> Either t Dockerfile -> [Rules.RuleCheck]-analyzeEither _ (Left _) = []-analyzeEither config (Right dockerFile) = analyzeAll config dockerFile
hadolint.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 4ff12972c5a652d9e4a6df3e276fdb1a565eaf408b15b14f6e7f9c22a20ade5d+-- hash: a69d376d8be88ae5432a97710688afbe46ad227c9262d12796a4357211b33238  name:           hadolint-version:        1.12.0+version:        1.13.0 synopsis:       Dockerfile Linter JavaScript API description:    A smarter Dockerfile linter that helps you build best practice Docker images. category:       Development@@ -34,19 +34,25 @@     , base >=4.8 && <5     , bytestring     , containers+    , directory >=1.3.0+    , filepath     , language-docker >=6.0.4 && <7     , megaparsec >=6.4     , mtl     , split >=0.2     , text     , void+    , yaml   exposed-modules:+      Hadolint+      Hadolint.Config       Hadolint.Formatter.Checkstyle       Hadolint.Formatter.Codacy       Hadolint.Formatter.Codeclimate       Hadolint.Formatter.Format       Hadolint.Formatter.Json       Hadolint.Formatter.TTY+      Hadolint.Lint       Hadolint.Rules       Hadolint.Shell   other-modules:@@ -61,15 +67,12 @@   build-depends:       base >=4.8 && <5     , containers-    , directory >=1.3.0-    , filepath     , gitrev >=1.3.1     , hadolint     , language-docker >=6.0.4 && <7     , megaparsec >=6.4     , optparse-applicative >=0.14.0     , text-    , yaml   if !(os(osx))     ld-options: -static -pthread   other-modules:
+ src/Hadolint.hs view
@@ -0,0 +1,9 @@+module Hadolint+    ( module Hadolint.Lint+    , module Hadolint.Rules+    , module Hadolint.Config+    ) where++import Hadolint.Config+import Hadolint.Lint+import Hadolint.Rules
+ src/Hadolint/Config.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE DeriveGeneric #-}++module Hadolint.Config (applyConfig) where++import Control.Monad (filterM)+import Data.Coerce (coerce)+import Data.Maybe (fromMaybe, listToMaybe)+import qualified Data.Set as Set+import qualified Data.Yaml as Yaml+import GHC.Generics+import qualified Language.Docker as Docker+import System.Directory+       (XdgDirectory(..), doesFileExist, getCurrentDirectory,+        getXdgDirectory)+import System.FilePath ((</>))++import qualified Hadolint.Lint as Lint+import qualified Hadolint.Rules as Rules++data ConfigFile = ConfigFile+    { ignored :: Maybe [Lint.IgnoreRule]+    , trustedRegistries :: Maybe [Lint.TrustedRegistry]+    } deriving (Show, Eq, Generic)++instance Yaml.FromJSON ConfigFile++-- | 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+-- file. If there is an error parsing the default config file, this function will+-- return the error string.+applyConfig :: Maybe FilePath -> Lint.LintOptions -> IO (Either String Lint.LintOptions)+applyConfig maybeConfig o+    | not (null (Lint.ignoreRules o)) && Lint.rulesConfig o /= mempty = return (Right o)+    | otherwise = do+        theConfig <-+            case maybeConfig of+                Nothing -> findConfig+                c -> return c+        case theConfig of+            Nothing -> return (Right o)+            Just config -> parseAndApply config+  where+    findConfig = do+        localConfigFile <- (</> ".hadolint.yaml") <$> getCurrentDirectory+        configFile <- getXdgDirectory XdgConfig "hadolint.yaml"+        listToMaybe <$> filterM doesFileExist [localConfigFile, configFile]+    parseAndApply :: FilePath -> IO (Either String Lint.LintOptions)+    parseAndApply configFile = do+        result <- Yaml.decodeFileEither configFile+        case result 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+    override ignore trusted = applyTrusted trusted . applyIgnore ignore $ o+    applyIgnore ignore opts =+        case Lint.ignoreRules opts of+            [] -> opts {Lint.ignoreRules = fromMaybe [] ignore}+            _ -> opts+    applyTrusted trusted opts+        | null (Rules.allowedRegistries (Lint.rulesConfig opts)) =+            opts {Lint.rulesConfig = toRules trusted <> Lint.rulesConfig opts}+        | otherwise = opts+    -- | Converts a list of TrustedRegistry to a RulesConfig record+    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
+ src/Hadolint/Lint.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE NamedFieldPuns #-}++module Hadolint.Lint where++import qualified Data.List.NonEmpty as NonEmpty+import Data.Text (Text)+import qualified Language.Docker as Docker+import Language.Docker.Parser (DockerfileError)+import Language.Docker.Syntax (Dockerfile)+import System.Exit (exitFailure, exitSuccess)++import qualified Hadolint.Formatter.Checkstyle as Checkstyle+import qualified Hadolint.Formatter.Codacy as Codacy+import qualified Hadolint.Formatter.Codeclimate as Codeclimate+import qualified Hadolint.Formatter.Format as Format+import qualified Hadolint.Formatter.Json as Json+import qualified Hadolint.Formatter.TTY as TTY+import qualified Hadolint.Rules as Rules++type IgnoreRule = Text++type TrustedRegistry = Text++data LintOptions = LintOptions+    { ignoreRules :: [IgnoreRule]+    , rulesConfig :: Rules.RulesConfig+    } deriving (Show)++data OutputFormat+    = Json+    | TTY+    | CodeclimateJson+    | Checkstyle+    | Codacy+    deriving (Show, Eq)++printResultsAndExit :: OutputFormat -> Format.Result Char DockerfileError -> IO ()+printResultsAndExit format allResults = do+    printResult allResults+    if allResults /= mempty+        then exitFailure+        else exitSuccess+  where+    printResult res =+        case format of+            TTY -> TTY.printResult res+            Json -> Json.printResult res+            Checkstyle -> Checkstyle.printResult res+            CodeclimateJson -> Codeclimate.printResult res >> exitSuccess+            Codacy -> Codacy.printResult res >> exitSuccess++-- | Performs the process of parsing the dockerfile and analyzing it with all the applicable+-- rules, depending on the list of ignored rules.+-- Depending on the preferred printing format, it will output the results to stdout+lint :: LintOptions -> NonEmpty.NonEmpty String -> IO (Format.Result Char DockerfileError)+lint LintOptions {ignoreRules = ignoreList, rulesConfig} dFiles = do+    processedFiles <- mapM (lintDockerfile ignoreList) (NonEmpty.toList dFiles)+    return (results processedFiles)+  where+    results = foldMap Format.toResult -- Parse and check rules for each dockerfile,+                                      -- then convert them to a Result and combine with+                                      -- the result of the previous dockerfile results+    lintDockerfile ignoreRules dockerFile = do+        ast <- Docker.parseFile (parseFilename dockerFile)+        return (processedFile ast)+      where+        processedFile = fmap processRules+        processRules fileLines = filter ignoredRules (analyzeAll rulesConfig fileLines)+        ignoredRules = ignoreFilter ignoreRules+        -- | Returns true if the rule should be ignored+        ignoreFilter :: [IgnoreRule] -> Rules.RuleCheck -> Bool+        ignoreFilter rules (Rules.RuleCheck (Rules.Metadata code _ _) _ _ _) =+            code `notElem` rules+        -- | Support UNIX convention of passing "-" instead of "/dev/stdin"+        parseFilename :: String -> String+        parseFilename "-" = "/dev/stdin"+        parseFilename s = s++-- | Returns the result of applying all the rules to the given dockerfile+analyzeAll :: Rules.RulesConfig -> Dockerfile -> [Rules.RuleCheck]+analyzeAll config = Rules.analyze (Rules.rules ++ Rules.optionalRules config)++-- | Helper to analyze AST quickly in GHCI+analyzeEither :: Rules.RulesConfig -> Either t Dockerfile -> [Rules.RuleCheck]+analyzeEither _ (Left _) = []+analyzeEither config (Right dockerFile) = analyzeAll config dockerFile