{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE FlexibleContexts #-} 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 Data.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 { ignoredRules :: Maybe [Lint.IgnoreRule] , trustedRegistries :: Maybe [Lint.TrustedRegistry] } deriving (Show, Eq, Generic) 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 -- 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 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 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 = 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 ]