packages feed

hadolint-1.18.2: src/Hadolint/Lint.hs

{-# LANGUAGE NamedFieldPuns #-}

module Hadolint.Lint where

import qualified Control.Concurrent.Async as Async
import Control.Parallel.Strategies (parListChunk, rseq, using)
import qualified Data.List.NonEmpty as NonEmpty
import Data.Text (Text)
import GHC.Conc (numCapabilities)
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
import qualified Language.Docker as Docker
import Language.Docker.Parser (DockerfileError, Error)
import Language.Docker.Syntax (Dockerfile)
import System.Exit (exitFailure, exitSuccess)

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 Text DockerfileError -> IO ()
printResultsAndExit format allResults = do
  printResult allResults
  if not . Format.isEmpty $ allResults
    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 Text DockerfileError)
lint LintOptions {ignoreRules = ignoreList, rulesConfig} dFiles = do
  parsedFiles <- Async.mapConcurrently parseFile (NonEmpty.toList dFiles)
  let results = lintAll parsedFiles `using` parListChunk (div numCapabilities 2) rseq
  return $ mconcat results
  where
    parseFile :: String -> IO (Either Error Dockerfile)
    parseFile "-" = Docker.parseStdin
    parseFile s = Docker.parseFile s

    lintAll = fmap (lintDockerfile ignoreList)

    lintDockerfile ignoreRules ast = processedFile ast
      where
        processedFile = Format.toResult . fmap processRules
        processRules fileLines = filter ignoredRules (analyzeAll rulesConfig fileLines)
        ignoredRules = ignoreFilter ignoreRules

        ignoreFilter :: [IgnoreRule] -> Rules.RuleCheck -> Bool
        ignoreFilter rules (Rules.RuleCheck (Rules.Metadata code _ _) _ _ _) = code `notElem` rules

-- | 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