packages feed

hadolint-2.8.0: src/Hadolint/Config/Configfile.hs

module Hadolint.Config.Configfile
  ( getConfigFromFile
  )
where

import Control.Monad (when, filterM)
import Data.Maybe (listToMaybe)
import Data.YAML as Yaml
import qualified Data.ByteString as Bytes
import Hadolint.Config.Configuration (PartialConfiguration (..))
import System.Directory
  ( XdgDirectory (..),
    doesFileExist,
    getCurrentDirectory,
    getAppUserDataDirectory,
    getUserDocumentsDirectory,
    getXdgDirectory,
  )
import System.FilePath ((</>))
import System.IO (hPrint, stderr)


getConfigFromFile ::
  Maybe FilePath -> Bool -> IO (Either String PartialConfiguration)
getConfigFromFile maybeExplicitPath verbose = do
  maybePath <- getConfig maybeExplicitPath
  when verbose $ hPrint stderr $ getFilePathDescription maybePath
  case maybePath of
    Nothing -> return $ Right mempty
    Just path -> readConfig path

readConfig :: FilePath -> IO (Either String PartialConfiguration)
readConfig path = do
  contents <- Bytes.readFile path
  return $ case Yaml.decode1Strict contents of
    Left (_, err) -> Left (formatError err path)
    Right config -> Right config

getFilePathDescription :: Maybe FilePath -> String
getFilePathDescription Nothing =
  "No configuration was specified. Using default configuration"
getFilePathDescription (Just filepath) = "Configuration file used: " ++ filepath

formatError :: String -> String -> String
formatError err config =
  Prelude.unlines
    [ "Error parsing your config file in  '" ++ config ++ "':",
      "It should contain one of the keys 'override', 'ignored'",
      "or 'trustedRegistries'. For example:\n",
      "ignored:",
      "\t- DL3000",
      "\t- SC1099\n\n",
      "The key 'override' should contain only lists with names 'error',",
      "'warning', 'info' or 'style', which each name rules to override the",
      "severity on. For example:\n",
      "override:",
      "\terror:",
      "\t\t- DL3008\n\n",
      "The key 'trustedRegistries' should contain the names of the allowed",
      "docker registries:\n",
      "trustedRegistries:",
      "\t- docker.io",
      "\t- my-company.com",
      "",
      err
    ]

-- | Gets the configuration file which Hadolint uses
getConfig :: Maybe FilePath -> IO (Maybe FilePath)
getConfig maybeConfig =
  case maybeConfig of
    Nothing -> findConfig
    _ -> return maybeConfig

-- | If no configuration file path was given on the command line, Hadolint
-- searches these locations or their equivalents on MacOS/Windows:
--  - $(pwd)/.hadolint.{yaml|yml}
--  - $HOME/.config/hadolint.{yaml|yml}
--  - $HOME/.hadolint/{hadolint|config}.{yaml|yml}
--  - $HOME/.hadolint.{yaml|yml}
-- The first file found is used, all other are ignored.
findConfig :: IO (Maybe FilePath)
findConfig = do
  filesInCWD <- traverse
                  (\filePath -> (</> filePath) <$> getCurrentDirectory)
                  hiddenConfigs
  filesInXdgConfig <- traverse (getXdgDirectory XdgConfig) visibleConfigs
  filesInAppData <- traverse
                      (\fp -> (</> fp) <$> getAppUserDataDirectory "hadolint")
                      (visibleConfigs <> moreConfigs)
  filesInHome <- traverse
                   (\fp -> (</> fp) <$> getUserDocumentsDirectory)
                   hiddenConfigs
  listToMaybe
    <$> filterM
          doesFileExist
          (filesInCWD <> filesInXdgConfig <> filesInAppData <> filesInHome)
  where
    hiddenConfigs = [".hadolint.yaml", ".hadolint.yml"]
    visibleConfigs = ["hadolint.yaml", "hadolint.yml"]
    moreConfigs = ["config.yaml", "config.yml"]