hadolint 2.4.1 → 2.5.0
raw patch · 9 files changed
+180/−20 lines, 9 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Hadolint: SonarQube :: OutputFormat
+ Hadolint.Formatter.SonarQube: formatResult :: Result s e -> Seq (SonarQubeFormat s e)
+ Hadolint.Formatter.SonarQube: instance (Text.Megaparsec.Stream.VisualStream s, Text.Megaparsec.Stream.TraversableStream s, Text.Megaparsec.Error.ShowErrorComponent e) => Data.Aeson.Types.ToJSON.ToJSON (Hadolint.Formatter.SonarQube.SonarQubeFormat s e)
+ Hadolint.Formatter.SonarQube: printResults :: (VisualStream s, TraversableStream s, ShowErrorComponent e, Foldable f) => f (Result s e) -> IO ()
+ Hadolint.Shell: nonPosixShells :: [Text]
- Hadolint: printResults :: Foldable f => OutputFormat -> Bool -> f (Result Text DockerfileError) -> IO ()
+ Hadolint: printResults :: Foldable f => OutputFormat -> Bool -> Maybe FilePath -> f (Result Text DockerfileError) -> IO ()
- Hadolint.Formatter.Checkstyle: formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Builder
+ Hadolint.Formatter.Checkstyle: formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Maybe FilePath -> Builder
- Hadolint.Formatter.Checkstyle: printResults :: (Foldable f, VisualStream s, TraversableStream s, ShowErrorComponent e) => f (Result s e) -> IO ()
+ Hadolint.Formatter.Checkstyle: printResults :: (Foldable f, VisualStream s, TraversableStream s, ShowErrorComponent e) => f (Result s e) -> Maybe FilePath -> IO ()
Files
- app/Main.hs +16/−3
- hadolint.cabal +2/−3
- src/Hadolint.hs +6/−3
- src/Hadolint/Formatter/Checkstyle.hs +14/−6
- src/Hadolint/Formatter/SonarQube.hs +103/−0
- src/Hadolint/Rule/DL4006.hs +3/−2
- src/Hadolint/Shell.hs +5/−2
- test/DL4006.hs +21/−0
- test/Shellcheck.hs +10/−1
app/Main.hs view
@@ -59,11 +59,13 @@ isVerbose :: Bool, format :: Hadolint.OutputFormat, dockerfiles :: [String],- lintingOptions :: Hadolint.LintOptions+ lintingOptions :: Hadolint.LintOptions,+ filePathInReportOption :: Maybe FilePath } toOutputFormat :: String -> Maybe Hadolint.OutputFormat toOutputFormat "json" = Just Hadolint.Json+toOutputFormat "sonarqube" = Just Hadolint.SonarQube toOutputFormat "tty" = Just Hadolint.TTY toOutputFormat "codeclimate" = Just Hadolint.CodeclimateJson toOutputFormat "gitlab_codeclimate" = Just Hadolint.GitlabCodeclimateJson@@ -73,6 +75,7 @@ showFormat :: Hadolint.OutputFormat -> String showFormat Hadolint.Json = "json"+showFormat Hadolint.SonarQube = "sonarqube" showFormat Hadolint.TTY = "tty" showFormat Hadolint.CodeclimateJson = "codeclimate" showFormat Hadolint.GitlabCodeclimateJson = "gitlab_codeclimate"@@ -99,6 +102,7 @@ <*> outputFormat <*> files <*> lintOptions+ <*> filePathInReportOption where version = switch (long "version" <> short 'v' <> help "Show version") @@ -206,6 +210,14 @@ <*> parseRulesConfig <*> noFailCutoff + filePathInReportOption =+ optional+ ( strOption+ ( long "file-path-in-report" <> metavar "FILEPATHINREPORT"+ <> help "The file path referenced in the generated report. This only applies for the 'checkstyle' format and is useful when running Hadolint with Docker to set the correct file path."+ )+ )+ labels = Map.fromList <$> many@@ -247,7 +259,7 @@ noFailure :: Hadolint.Result s e -> Rule.DLSeverity -> Bool noFailure (Hadolint.Result _ Seq.Empty Seq.Empty) _ = True noFailure (Hadolint.Result _ Seq.Empty fails) cutoff =- Seq.null (Seq.filter (\f -> Rule.severity f < cutoff) fails)+ Seq.null (Seq.filter (\f -> Rule.severity f <= cutoff) fails) noFailure _ _ = False exitProgram ::@@ -267,7 +279,8 @@ res <- Hadolint.lintIO conf files noColorEnv <- lookupEnv "NO_COLOR" let noColor = nocolor cmd || isJust noColorEnv- Hadolint.printResults (format cmd) noColor res+ let filePathInReport = filePathInReportOption cmd+ Hadolint.printResults (format cmd) noColor filePathInReport res exitProgram cmd conf res main :: IO ()
hadolint.cabal view
@@ -3,11 +3,9 @@ -- This file has been generated from package.yaml by hpack version 0.34.4. -- -- see: https://github.com/sol/hpack------ hash: 81ce3bbf6ac259bb1aeda5e670d68ec5cbcb3bf9d955d085de125eb4e39f3fc8 name: hadolint-version: 2.4.1+version: 2.5.0 synopsis: Dockerfile Linter JavaScript API description: A smarter Dockerfile linter that helps you build best practice Docker images. category: Development@@ -39,6 +37,7 @@ Hadolint.Formatter.Codeclimate Hadolint.Formatter.Format Hadolint.Formatter.Json+ Hadolint.Formatter.SonarQube Hadolint.Formatter.TTY Hadolint.Ignore Hadolint.Lint
src/Hadolint.hs view
@@ -16,6 +16,7 @@ import qualified Hadolint.Formatter.Codeclimate import Hadolint.Formatter.Format (Result (..)) import qualified Hadolint.Formatter.Json+import qualified Hadolint.Formatter.SonarQube import qualified Hadolint.Formatter.TTY import Hadolint.Lint import Hadolint.Process@@ -23,6 +24,7 @@ data OutputFormat = Json+ | SonarQube | TTY | CodeclimateJson | GitlabCodeclimateJson@@ -33,12 +35,13 @@ shallSkipErrorStatus :: OutputFormat -> Bool shallSkipErrorStatus format = format `elem` [CodeclimateJson, Codacy] -printResults :: Foldable f => OutputFormat -> Bool -> f (Result Text DockerfileError) -> IO ()-printResults format nocolor allResults =+printResults :: Foldable f => OutputFormat -> Bool -> Maybe FilePath -> f (Result Text DockerfileError) -> IO ()+printResults format nocolor filePathInReport allResults = case format of TTY -> Hadolint.Formatter.TTY.printResults allResults nocolor+ SonarQube -> Hadolint.Formatter.SonarQube.printResults allResults Json -> Hadolint.Formatter.Json.printResults allResults- Checkstyle -> Hadolint.Formatter.Checkstyle.printResults allResults+ Checkstyle -> Hadolint.Formatter.Checkstyle.printResults allResults filePathInReport CodeclimateJson -> Hadolint.Formatter.Codeclimate.printResults allResults GitlabCodeclimateJson -> Hadolint.Formatter.Codeclimate.printGitlabResults allResults Codacy -> Hadolint.Formatter.Codacy.printResults allResults
src/Hadolint/Formatter/Checkstyle.hs view
@@ -81,8 +81,8 @@ else "&#" <> Text.pack (show (ord c)) <> ";" isOk x = any (\check -> check x) [isAsciiUpper, isAsciiLower, isDigit, (`elem` [' ', '.', '/'])] -formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Builder.Builder-formatResult (Result filename errors checks) = header <> xmlBody <> footer+formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Maybe FilePath -> Builder.Builder+formatResult (Result filename errors checks) filePathInReport = header <> xmlBody <> footer where xmlBody = Foldl.fold (Foldl.premap toXml Foldl.mconcat) issues @@ -91,21 +91,29 @@ checkstyleChecks = fmap ruleToCheckStyle checks isEmpty = null checks && null errors+ name = if null filePathInReport then filename else getFilePath filePathInReport header = if isEmpty then ""- else "<file " <> attr "name" (encode filename) <> ">"+ else "<file " <> attr "name" (encode name) <> ">" footer = if isEmpty then "" else "</file>" printResults :: (Foldable f, VisualStream s, TraversableStream s, ShowErrorComponent e) =>- f (Result s e) ->+ f (Result s e) -> Maybe FilePath -> IO ()-printResults results = do+printResults results filePathInReport = do B.putStr header mapM_ put results B.putStr footer where header = "<?xml version='1.0' encoding='UTF-8'?><checkstyle version='4.3'>" footer = "</checkstyle>"- put result = Builder.hPutBuilder stdout (formatResult result)+ put result = Builder.hPutBuilder stdout (formatResult result filePathInReport)++getFilePath :: Maybe FilePath -> Text.Text +getFilePath Nothing = ""+getFilePath (Just filePath) = toText [filePath]++toText :: [FilePath] -> Text.Text +toText = foldMap Text.pack
+ src/Hadolint/Formatter/SonarQube.hs view
@@ -0,0 +1,103 @@+module Hadolint.Formatter.SonarQube+ ( formatResult,+ printResults+ )+ where++import qualified Control.Foldl as Foldl+import Data.Aeson hiding (Result)+import qualified Data.ByteString.Lazy.Char8 as B+import Data.Sequence (Seq)+import qualified Data.Text as Text+import Hadolint.Formatter.Format+ ( Result (..),+ errorPosition,+ errorMessage+ )+import Hadolint.Rule+ ( CheckFailure (..),+ DLSeverity (..),+ unRuleCode+ )+import Text.Megaparsec (TraversableStream)+import Text.Megaparsec.Error+import Text.Megaparsec.Pos+ ( sourceColumn,+ sourceLine,+ sourceName,+ unPos+ )+import Text.Megaparsec.Stream (VisualStream)+++data SonarQubeFormat s e+ = SonarQubeCheck Text.Text CheckFailure+ | SonarQubeError (ParseErrorBundle s e)++instance (VisualStream s,+ TraversableStream s,+ ShowErrorComponent e) => ToJSON (SonarQubeFormat s e) where+ toJSON (SonarQubeCheck filename CheckFailure {..}) =+ object+ [ "engineId" .= Text.pack "Hadolint",+ "ruleId" .= unRuleCode code,+ "severity" .= toSeverity severity,+ "type" .= toType severity,+ "primaryLocation" .= object+ [ "message" .= message,+ "filePath" .= filename,+ "textRange" .= object+ [ "startLine" .= line,+ "endLine" .= line,+ "startColumn" .= (1 :: Int),+ "endColumn" .= (1 :: Int)+ ]+ ]+ ]+ toJSON (SonarQubeError err) =+ object+ [ "engineId" .= Text.pack "Hadolint",+ "ruleId" .= Text.pack "DL1000",+ "severity" .= Text.pack "BLOCKER",+ "type" .= Text.pack "BUG",+ "primaryLocation" .= object+ [ "message" .= errorMessage err,+ "filePath" .= Text.pack (sourceName pos),+ "textRange" .= object+ [ "startLine" .= linenumber,+ "endLine" .= linenumber,+ "startColumn" .= column,+ "endColumn" .= column+ ]+ ]+ ]+ where+ pos = errorPosition err+ linenumber = unPos $ sourceLine pos+ column = unPos $ sourceColumn pos+++formatResult :: Result s e -> Seq (SonarQubeFormat s e)+formatResult (Result filename errors checks) = allMessages+ where+ allMessages = errorMessages <> checkMessages+ errorMessages = fmap SonarQubeError errors+ checkMessages = fmap (SonarQubeCheck filename) checks++printResults :: (VisualStream s,+ TraversableStream s,+ ShowErrorComponent e,+ Foldable f) => f (Result s e) -> IO ()+printResults results = B.putStr . encode $ object [ "issues" .= flattened ]+ where+ flattened = Foldl.fold (Foldl.premap formatResult Foldl.mconcat) results++toType :: DLSeverity -> Text.Text+toType DLErrorC = "BUG"+toType _ = "CODE_SMELL"++toSeverity :: DLSeverity -> Text.Text+toSeverity DLErrorC = "CRITICAL"+toSeverity DLWarningC = "MAJOR"+toSeverity DLInfoC = "MINOR"+toSeverity _ = "INFO"
src/Hadolint/Rule/DL4006.hs view
@@ -18,14 +18,15 @@ check _ st From {} = st |> replaceWith False -- Reset the state each time we find a new FROM check _ st (Shell args)- | foldArguments isPowerShell args = st |> replaceWith True+ | foldArguments isNonPosixShell args = st |> replaceWith True | otherwise = st |> replaceWith (foldArguments hasPipefailOption args) check line st@(State _ False) (Run (RunArgs args _)) | foldArguments hasPipes args = st |> addFail CheckFailure {..} | otherwise = st check _ st _ = st - isPowerShell (Shell.ParsedShell orig _ _) = "pwsh" `Text.isPrefixOf` orig+ isNonPosixShell (Shell.ParsedShell orig _ _) =+ any (`Text.isPrefixOf` orig) Shell.nonPosixShells hasPipes script = Shell.hasPipes script hasPipefailOption script = not $
src/Hadolint/Shell.hs view
@@ -61,8 +61,8 @@ shellcheck :: ShellOpts -> ParsedShell -> [PositionedComment] shellcheck (ShellOpts sh env) (ParsedShell txt _ _) =- if "pwsh" `Text.isPrefixOf` sh- then [] -- Do no run for powershell+ if any (`Text.isPrefixOf` sh) nonPosixShells+ then [] -- Do no run for non-posix shells i.e. powershell, cmd.exe else runShellCheck where runShellCheck = crComments $ runIdentity $ checkScript si spec@@ -84,6 +84,9 @@ extractShell s = fromMaybe "" (listToMaybe . Text.words $ s) printVars = Text.unlines . Set.toList $ Set.map (\v -> "export " <> v <> "=1") env++nonPosixShells :: [Text.Text]+nonPosixShells = ["pwsh", "powershell", "cmd"] parseShell :: Text.Text -> ParsedShell parseShell txt = ParsedShell {original = txt, parsed = parsedResult, presentCommands = commands}
test/DL4006.hs view
@@ -81,3 +81,24 @@ "RUN wget -O - https://some.site | wc -l file > /number" ] in ruleCatches "DL4006" $ Text.unlines dockerFile+ it "ignore non posix shells: pwsh" $+ let dockerFile =+ [ "FROM mcr.microsoft.com/powershell:ubuntu-16.04",+ "SHELL [ \"pwsh\", \"-c\" ]",+ "RUN Get-Variable PSVersionTable | Select-Object -ExpandProperty Value"+ ]+ in ruleCatchesNot "DL4006" $ Text.unlines dockerFile+ it "ignore non posix shells: powershell" $+ let dockerFile =+ [ "FROM mcr.microsoft.com/powershell:ubuntu-16.04",+ "SHELL [ \"powershell.exe\" ]",+ "RUN Get-Variable PSVersionTable | Select-Object -ExpandProperty Value"+ ]+ in ruleCatchesNot "DL4006" $ Text.unlines dockerFile+ it "ignore non posix shells: cmd.exe" $+ let dockerFile =+ [ "FROM mcr.microsoft.com/powershell:ubuntu-16.04",+ "SHELL [ \"cmd.exe\", \"/c\" ]",+ "RUN Get-Variable PSVersionTable | Select-Object -ExpandProperty Value"+ ]+ in ruleCatchesNot "DL4006" $ Text.unlines dockerFile
test/Shellcheck.hs view
@@ -110,10 +110,19 @@ assertChecks dockerFile passesShellcheck assertOnBuildChecks dockerFile passesShellcheck - it "Does not complain on powershell" $+ it "Does not complain on non-posix shells: pwsh" $ let dockerFile = Text.unlines [ "SHELL [\"pwsh\", \"-c\"]",+ "RUN Get-Variable PSVersionTable | Select-Object -ExpandProperty Value"+ ]+ in do+ assertChecks dockerFile passesShellcheck+ assertOnBuildChecks dockerFile passesShellcheck+ it "Does not complain on non-posix shells: cmd.exe" $+ let dockerFile =+ Text.unlines+ [ "SHELL [\"cmd.exe\", \"/c\"]", "RUN Get-Variable PSVersionTable | Select-Object -ExpandProperty Value" ] in do