hadolint 2.7.0 → 2.8.0
raw patch · 174 files changed
+6089/−3909 lines, 174 filesdep +data-defaultdep +prettyprinterdep +silentlydep ~hspecdep ~language-dockerPVP ok
version bump matches the API change (PVP)
Dependencies added: data-default, prettyprinter, silently
Dependency ranges changed: hspec, language-docker
API changes (from Hackage documentation)
- Hadolint: Checkstyle :: OutputFormat
- Hadolint: Codacy :: OutputFormat
- Hadolint: CodeclimateJson :: OutputFormat
- Hadolint: GitlabCodeclimateJson :: OutputFormat
- Hadolint: Json :: OutputFormat
- Hadolint: Result :: Text -> Seq (ParseErrorBundle s e) -> Failures -> Result s e
- Hadolint: SonarQube :: OutputFormat
- Hadolint: TTY :: OutputFormat
- Hadolint: [checks] :: Result s e -> Failures
- Hadolint: [errors] :: Result s e -> Seq (ParseErrorBundle s e)
- Hadolint: [fileName] :: Result s e -> Text
- Hadolint: data OutputFormat
- Hadolint: data Result s e
- Hadolint: instance GHC.Classes.Eq Hadolint.OutputFormat
- Hadolint: instance GHC.Show.Show Hadolint.OutputFormat
- Hadolint: printResults :: Foldable f => OutputFormat -> Bool -> Maybe FilePath -> f (Result Text DockerfileError) -> IO ()
- Hadolint: shallSkipErrorStatus :: OutputFormat -> Bool
- Hadolint.Config: ConfigFile :: Maybe OverrideConfig -> Maybe [IgnoreRule] -> Maybe [TrustedRegistry] -> Maybe LabelSchema -> Maybe Bool -> Maybe DLSeverity -> ConfigFile
- Hadolint.Config: OverrideConfig :: Maybe [ErrorRule] -> Maybe [WarningRule] -> Maybe [InfoRule] -> Maybe [StyleRule] -> OverrideConfig
- Hadolint.Config: [failureThreshold] :: ConfigFile -> Maybe DLSeverity
- Hadolint.Config: [ignoredRules] :: ConfigFile -> Maybe [IgnoreRule]
- Hadolint.Config: [labelSchemaConfig] :: ConfigFile -> Maybe LabelSchema
- Hadolint.Config: [overrideErrorRules] :: OverrideConfig -> Maybe [ErrorRule]
- Hadolint.Config: [overrideInfoRules] :: OverrideConfig -> Maybe [InfoRule]
- Hadolint.Config: [overrideRules] :: ConfigFile -> Maybe OverrideConfig
- Hadolint.Config: [overrideStyleRules] :: OverrideConfig -> Maybe [StyleRule]
- Hadolint.Config: [overrideWarningRules] :: OverrideConfig -> Maybe [WarningRule]
- Hadolint.Config: [strictLabelSchema] :: ConfigFile -> Maybe Bool
- Hadolint.Config: [trustedRegistries] :: ConfigFile -> Maybe [TrustedRegistry]
- Hadolint.Config: applyConfig :: Maybe FilePath -> LintOptions -> IO (Either String LintOptions)
- Hadolint.Config: data ConfigFile
- Hadolint.Config: data OverrideConfig
- Hadolint.Config: getConfig :: Maybe FilePath -> IO (Maybe FilePath)
- Hadolint.Config: instance Data.YAML.FromYAML Hadolint.Config.ConfigFile
- Hadolint.Config: instance Data.YAML.FromYAML Hadolint.Config.OverrideConfig
- Hadolint.Config: instance GHC.Base.Monoid Hadolint.Config.OverrideConfig
- Hadolint.Config: instance GHC.Base.Semigroup Hadolint.Config.OverrideConfig
- Hadolint.Config: instance GHC.Classes.Eq Hadolint.Config.ConfigFile
- Hadolint.Config: instance GHC.Classes.Eq Hadolint.Config.OverrideConfig
- Hadolint.Config: instance GHC.Generics.Generic Hadolint.Config.ConfigFile
- Hadolint.Config: instance GHC.Generics.Generic Hadolint.Config.OverrideConfig
- Hadolint.Config: instance GHC.Show.Show Hadolint.Config.ConfigFile
- Hadolint.Config: instance GHC.Show.Show Hadolint.Config.OverrideConfig
- Hadolint.Ignore: ignored :: Fold (InstructionPos Text) (IntMap (Set RuleCode))
- Hadolint.Lint: LintOptions :: [ErrorRule] -> [WarningRule] -> [InfoRule] -> [StyleRule] -> [IgnoreRule] -> RulesConfig -> DLSeverity -> LintOptions
- Hadolint.Lint: [errorRules] :: LintOptions -> [ErrorRule]
- Hadolint.Lint: [failThreshold] :: LintOptions -> DLSeverity
- Hadolint.Lint: [ignoreRules] :: LintOptions -> [IgnoreRule]
- Hadolint.Lint: [infoRules] :: LintOptions -> [InfoRule]
- Hadolint.Lint: [rulesConfig] :: LintOptions -> RulesConfig
- Hadolint.Lint: [styleRules] :: LintOptions -> [StyleRule]
- Hadolint.Lint: [warningRules] :: LintOptions -> [WarningRule]
- Hadolint.Lint: data LintOptions
- Hadolint.Lint: instance GHC.Base.Monoid Hadolint.Lint.LintOptions
- Hadolint.Lint: instance GHC.Base.Semigroup Hadolint.Lint.LintOptions
- Hadolint.Lint: instance GHC.Show.Show Hadolint.Lint.LintOptions
- Hadolint.Lint: type ErrorRule = RuleCode
- Hadolint.Lint: type IgnoreRule = RuleCode
- Hadolint.Lint: type InfoRule = RuleCode
- Hadolint.Lint: type StyleRule = RuleCode
- Hadolint.Lint: type WarningRule = RuleCode
- Hadolint.Process: RulesConfig :: Set Registry -> LabelSchema -> Bool -> RulesConfig
- Hadolint.Process: [allowedRegistries] :: RulesConfig -> Set Registry
- Hadolint.Process: [labelSchema] :: RulesConfig -> LabelSchema
- Hadolint.Process: [strictLabels] :: RulesConfig -> Bool
- Hadolint.Process: data RulesConfig
- Hadolint.Process: instance GHC.Base.Monoid Hadolint.Process.RulesConfig
- Hadolint.Process: instance GHC.Base.Semigroup Hadolint.Process.RulesConfig
- Hadolint.Process: instance GHC.Classes.Eq Hadolint.Process.RulesConfig
- Hadolint.Process: instance GHC.Show.Show Hadolint.Process.RulesConfig
- Hadolint.Rule: instance GHC.Read.Read Hadolint.Rule.DLSeverity
- Hadolint.Rule: instance GHC.Read.Read Hadolint.Rule.LabelType
- Hadolint.Rule: read :: Text -> Either Text LabelType
- Hadolint.Rule: readSeverity :: Text -> Either Text DLSeverity
- Hadolint.Rule.DL3059: instance GHC.Show.Show Hadolint.Rule.DL3059.Acc
+ Hadolint: CheckFailure :: RuleCode -> DLSeverity -> Text -> Linenumber -> CheckFailure
+ Hadolint: DLErrorC :: DLSeverity
+ Hadolint: DLIgnoreC :: DLSeverity
+ Hadolint: DLInfoC :: DLSeverity
+ Hadolint: DLStyleC :: DLSeverity
+ Hadolint: DLWarningC :: DLSeverity
+ Hadolint: Email :: LabelType
+ Hadolint: GitHash :: LabelType
+ Hadolint: RawText :: LabelType
+ Hadolint: Rfc3339 :: LabelType
+ Hadolint: SemVer :: LabelType
+ Hadolint: Spdx :: LabelType
+ Hadolint: Url :: LabelType
+ Hadolint: [code] :: CheckFailure -> RuleCode
+ Hadolint: [line] :: CheckFailure -> Linenumber
+ Hadolint: [message] :: CheckFailure -> Text
+ Hadolint: [severity] :: CheckFailure -> DLSeverity
+ Hadolint: data CheckFailure
+ Hadolint: data DLSeverity
+ Hadolint: data LabelType
+ Hadolint: type LabelName = Text
+ Hadolint: type LabelSchema = Map LabelName LabelType
+ Hadolint.Config: CommandlineConfig :: Bool -> Maybe FilePath -> [String] -> Maybe FilePath -> PartialConfiguration -> CommandlineConfig
+ Hadolint.Config: [configFile] :: CommandlineConfig -> Maybe FilePath
+ Hadolint.Config: [configuration] :: CommandlineConfig -> PartialConfiguration
+ Hadolint.Config: [dockerfiles] :: CommandlineConfig -> [String]
+ Hadolint.Config: [filePathInReportOption] :: CommandlineConfig -> Maybe FilePath
+ Hadolint.Config: [showVersion] :: CommandlineConfig -> Bool
+ Hadolint.Config: data CommandlineConfig
+ Hadolint.Config: getConfigFromEnvironment :: IO PartialConfiguration
+ Hadolint.Config: getConfigFromFile :: Maybe FilePath -> Bool -> IO (Either String PartialConfiguration)
+ Hadolint.Config: parseCommandline :: Parser CommandlineConfig
+ Hadolint.Config.Commandline: CommandlineConfig :: Bool -> Maybe FilePath -> [String] -> Maybe FilePath -> PartialConfiguration -> CommandlineConfig
+ Hadolint.Config.Commandline: [configFile] :: CommandlineConfig -> Maybe FilePath
+ Hadolint.Config.Commandline: [configuration] :: CommandlineConfig -> PartialConfiguration
+ Hadolint.Config.Commandline: [dockerfiles] :: CommandlineConfig -> [String]
+ Hadolint.Config.Commandline: [filePathInReportOption] :: CommandlineConfig -> Maybe FilePath
+ Hadolint.Config.Commandline: [showVersion] :: CommandlineConfig -> Bool
+ Hadolint.Config.Commandline: data CommandlineConfig
+ Hadolint.Config.Commandline: instance GHC.Classes.Eq Hadolint.Config.Commandline.CommandlineConfig
+ Hadolint.Config.Commandline: instance GHC.Show.Show Hadolint.Config.Commandline.CommandlineConfig
+ Hadolint.Config.Commandline: parseCommandline :: Parser CommandlineConfig
+ Hadolint.Config.Configfile: getConfigFromFile :: Maybe FilePath -> Bool -> IO (Either String PartialConfiguration)
+ Hadolint.Config.Configuration: Configuration :: Bool -> Bool -> Bool -> OutputFormat -> [RuleCode] -> [RuleCode] -> [RuleCode] -> [RuleCode] -> [RuleCode] -> Set Registry -> LabelSchema -> Bool -> DLSeverity -> Configuration
+ Hadolint.Config.Configuration: PartialConfiguration :: Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe OutputFormat -> [RuleCode] -> [RuleCode] -> [RuleCode] -> [RuleCode] -> [RuleCode] -> Set Registry -> LabelSchema -> Maybe Bool -> Maybe DLSeverity -> PartialConfiguration
+ Hadolint.Config.Configuration: [allowedRegistries] :: Configuration -> Set Registry
+ Hadolint.Config.Configuration: [errorRules] :: Configuration -> [RuleCode]
+ Hadolint.Config.Configuration: [failureThreshold] :: Configuration -> DLSeverity
+ Hadolint.Config.Configuration: [format] :: Configuration -> OutputFormat
+ Hadolint.Config.Configuration: [ignoreRules] :: Configuration -> [RuleCode]
+ Hadolint.Config.Configuration: [infoRules] :: Configuration -> [RuleCode]
+ Hadolint.Config.Configuration: [labelSchema] :: Configuration -> LabelSchema
+ Hadolint.Config.Configuration: [noColor] :: Configuration -> Bool
+ Hadolint.Config.Configuration: [noFail] :: Configuration -> Bool
+ Hadolint.Config.Configuration: [partialAllowedRegistries] :: PartialConfiguration -> Set Registry
+ Hadolint.Config.Configuration: [partialErrorRules] :: PartialConfiguration -> [RuleCode]
+ Hadolint.Config.Configuration: [partialFailureThreshold] :: PartialConfiguration -> Maybe DLSeverity
+ Hadolint.Config.Configuration: [partialFormat] :: PartialConfiguration -> Maybe OutputFormat
+ Hadolint.Config.Configuration: [partialIgnoreRules] :: PartialConfiguration -> [RuleCode]
+ Hadolint.Config.Configuration: [partialInfoRules] :: PartialConfiguration -> [RuleCode]
+ Hadolint.Config.Configuration: [partialLabelSchema] :: PartialConfiguration -> LabelSchema
+ Hadolint.Config.Configuration: [partialNoColor] :: PartialConfiguration -> Maybe Bool
+ Hadolint.Config.Configuration: [partialNoFail] :: PartialConfiguration -> Maybe Bool
+ Hadolint.Config.Configuration: [partialStrictLabels] :: PartialConfiguration -> Maybe Bool
+ Hadolint.Config.Configuration: [partialStyleRules] :: PartialConfiguration -> [RuleCode]
+ Hadolint.Config.Configuration: [partialVerbose] :: PartialConfiguration -> Maybe Bool
+ Hadolint.Config.Configuration: [partialWarningRules] :: PartialConfiguration -> [RuleCode]
+ Hadolint.Config.Configuration: [strictLabels] :: Configuration -> Bool
+ Hadolint.Config.Configuration: [styleRules] :: Configuration -> [RuleCode]
+ Hadolint.Config.Configuration: [verbose] :: Configuration -> Bool
+ Hadolint.Config.Configuration: [warningRules] :: Configuration -> [RuleCode]
+ Hadolint.Config.Configuration: applyPartialConfiguration :: Configuration -> PartialConfiguration -> Configuration
+ Hadolint.Config.Configuration: data Configuration
+ Hadolint.Config.Configuration: data PartialConfiguration
+ Hadolint.Config.Configuration: instance Data.Default.Class.Default Hadolint.Config.Configuration.Configuration
+ Hadolint.Config.Configuration: instance Data.Default.Class.Default Hadolint.Config.Configuration.PartialConfiguration
+ Hadolint.Config.Configuration: instance Data.YAML.FromYAML Hadolint.Config.Configuration.OverrideConfig
+ Hadolint.Config.Configuration: instance Data.YAML.FromYAML Hadolint.Config.Configuration.PartialConfiguration
+ Hadolint.Config.Configuration: instance GHC.Base.Monoid Hadolint.Config.Configuration.OverrideConfig
+ Hadolint.Config.Configuration: instance GHC.Base.Monoid Hadolint.Config.Configuration.PartialConfiguration
+ Hadolint.Config.Configuration: instance GHC.Base.Semigroup Hadolint.Config.Configuration.OverrideConfig
+ Hadolint.Config.Configuration: instance GHC.Base.Semigroup Hadolint.Config.Configuration.PartialConfiguration
+ Hadolint.Config.Configuration: instance GHC.Classes.Eq Hadolint.Config.Configuration.Configuration
+ Hadolint.Config.Configuration: instance GHC.Classes.Eq Hadolint.Config.Configuration.OverrideConfig
+ Hadolint.Config.Configuration: instance GHC.Classes.Eq Hadolint.Config.Configuration.PartialConfiguration
+ Hadolint.Config.Configuration: instance GHC.Generics.Generic Hadolint.Config.Configuration.OverrideConfig
+ Hadolint.Config.Configuration: instance GHC.Show.Show Hadolint.Config.Configuration.Configuration
+ Hadolint.Config.Configuration: instance GHC.Show.Show Hadolint.Config.Configuration.OverrideConfig
+ Hadolint.Config.Configuration: instance GHC.Show.Show Hadolint.Config.Configuration.PartialConfiguration
+ Hadolint.Config.Configuration: instance Prettyprinter.Internal.Pretty Hadolint.Config.Configuration.Configuration
+ Hadolint.Config.Environment: getConfigFromEnvironment :: IO PartialConfiguration
+ Hadolint.Formatter: Checkstyle :: OutputFormat
+ Hadolint.Formatter: Codacy :: OutputFormat
+ Hadolint.Formatter: CodeclimateJson :: OutputFormat
+ Hadolint.Formatter: GitlabCodeclimateJson :: OutputFormat
+ Hadolint.Formatter: Json :: OutputFormat
+ Hadolint.Formatter: Result :: Text -> Seq (ParseErrorBundle s e) -> Failures -> Result s e
+ Hadolint.Formatter: Sarif :: OutputFormat
+ Hadolint.Formatter: SonarQube :: OutputFormat
+ Hadolint.Formatter: TTY :: OutputFormat
+ Hadolint.Formatter: [checks] :: Result s e -> Failures
+ Hadolint.Formatter: [errors] :: Result s e -> Seq (ParseErrorBundle s e)
+ Hadolint.Formatter: [fileName] :: Result s e -> Text
+ Hadolint.Formatter: data OutputFormat
+ Hadolint.Formatter: data Result s e
+ Hadolint.Formatter: printResults :: Foldable f => OutputFormat -> Bool -> Maybe FilePath -> f (Result Text DockerfileError) -> IO ()
+ Hadolint.Formatter: readMaybeOutputFormat :: Text -> Maybe OutputFormat
+ Hadolint.Formatter.Format: Checkstyle :: OutputFormat
+ Hadolint.Formatter.Format: Codacy :: OutputFormat
+ Hadolint.Formatter.Format: CodeclimateJson :: OutputFormat
+ Hadolint.Formatter.Format: GitlabCodeclimateJson :: OutputFormat
+ Hadolint.Formatter.Format: Json :: OutputFormat
+ Hadolint.Formatter.Format: Sarif :: OutputFormat
+ Hadolint.Formatter.Format: SonarQube :: OutputFormat
+ Hadolint.Formatter.Format: TTY :: OutputFormat
+ Hadolint.Formatter.Format: data OutputFormat
+ Hadolint.Formatter.Format: instance Data.Default.Class.Default Hadolint.Formatter.Format.OutputFormat
+ Hadolint.Formatter.Format: instance Data.YAML.FromYAML Hadolint.Formatter.Format.OutputFormat
+ Hadolint.Formatter.Format: instance GHC.Base.Monoid Hadolint.Formatter.Format.OutputFormat
+ Hadolint.Formatter.Format: instance GHC.Base.Semigroup Hadolint.Formatter.Format.OutputFormat
+ Hadolint.Formatter.Format: instance GHC.Classes.Eq Hadolint.Formatter.Format.OutputFormat
+ Hadolint.Formatter.Format: instance GHC.Show.Show Hadolint.Formatter.Format.OutputFormat
+ Hadolint.Formatter.Format: instance Prettyprinter.Internal.Pretty Hadolint.Formatter.Format.OutputFormat
+ Hadolint.Formatter.Format: readMaybeOutputFormat :: Text -> Maybe OutputFormat
+ Hadolint.Formatter.Sarif: formatResult :: Result s e -> Seq (SarifFormat s e)
+ Hadolint.Formatter.Sarif: instance (Text.Megaparsec.Stream.VisualStream s, Text.Megaparsec.Stream.TraversableStream s, Text.Megaparsec.Error.ShowErrorComponent e) => Data.Aeson.Types.ToJSON.ToJSON (Hadolint.Formatter.Sarif.SarifFormat s e)
+ Hadolint.Formatter.Sarif: printResults :: (VisualStream s, TraversableStream s, ShowErrorComponent e, Foldable f) => f (Result s e) -> IO ()
+ Hadolint.Meta: getShortVersion :: String
+ Hadolint.Meta: getVersion :: String
+ Hadolint.Pragma: ignored :: Fold (InstructionPos Text) (IntMap (Set RuleCode))
+ Hadolint.Pragma: parseShell :: Text -> Maybe Text
+ Hadolint.Rule: dropQuotes :: Text -> Text
+ Hadolint.Rule: instance Data.Default.Class.Default Hadolint.Rule.DLSeverity
+ Hadolint.Rule: instance Prettyprinter.Internal.Pretty Hadolint.Rule.DLSeverity
+ Hadolint.Rule: instance Prettyprinter.Internal.Pretty Hadolint.Rule.LabelType
+ Hadolint.Rule: instance Prettyprinter.Internal.Pretty Hadolint.Rule.RuleCode
+ Hadolint.Rule: readEitherLabelType :: Text -> Either Text LabelType
+ Hadolint.Rule: readEitherSeverity :: Text -> Either String DLSeverity
+ Hadolint.Rule: readMaybeSeverity :: Text -> Maybe DLSeverity
+ Hadolint.Rule.DL3009: instance GHC.Classes.Eq Hadolint.Rule.DL3009.Acc
- Hadolint.Lint: analyze :: LintOptions -> Dockerfile -> Seq CheckFailure
+ Hadolint.Lint: analyze :: Configuration -> Dockerfile -> Seq CheckFailure
- Hadolint.Lint: lint :: LintOptions -> [(Text, Either Error Dockerfile)] -> [Result Text DockerfileError]
+ Hadolint.Lint: lint :: Configuration -> [(Text, Either Error Dockerfile)] -> [Result Text DockerfileError]
- Hadolint.Lint: lintIO :: LintOptions -> NonEmpty FilePath -> IO (NonEmpty (Result Text DockerfileError))
+ Hadolint.Lint: lintIO :: Configuration -> NonEmpty FilePath -> IO (NonEmpty (Result Text DockerfileError))
- Hadolint.Process: run :: RulesConfig -> [InstructionPos Text] -> Failures
+ Hadolint.Process: run :: Configuration -> [InstructionPos Text] -> Failures
- Hadolint.Rule.DL3010: rule :: Rule args
+ Hadolint.Rule.DL3010: rule :: Rule ParsedShell
- Hadolint.Rule.DL3059: rule :: Rule args
+ Hadolint.Rule.DL3059: rule :: Rule ParsedShell
Files
- README.md +124/−67
- app/Main.hs +55/−281
- hadolint.cabal +119/−87
- src/Hadolint.hs +20/−39
- src/Hadolint/Config.hs +10/−166
- src/Hadolint/Config/Commandline.hs +263/−0
- src/Hadolint/Config/Configfile.hs +101/−0
- src/Hadolint/Config/Configuration.hs +229/−0
- src/Hadolint/Config/Environment.hs +103/−0
- src/Hadolint/Formatter.hs +37/−0
- src/Hadolint/Formatter/Format.hs +69/−8
- src/Hadolint/Formatter/Sarif.hs +165/−0
- src/Hadolint/Ignore.hs +0/−40
- src/Hadolint/Lint.hs +36/−66
- src/Hadolint/Meta.hs +21/−0
- src/Hadolint/Pragma.hs +76/−0
- src/Hadolint/Process.hs +13/−30
- src/Hadolint/Rule.hs +85/−33
- src/Hadolint/Rule/DL3000.hs +3/−9
- src/Hadolint/Rule/DL3009.hs +51/−18
- src/Hadolint/Rule/DL3010.hs +127/−10
- src/Hadolint/Rule/DL3019.hs +13/−7
- src/Hadolint/Rule/DL3020.hs +5/−2
- src/Hadolint/Rule/DL3033.hs +31/−6
- src/Hadolint/Rule/DL3034.hs +1/−1
- src/Hadolint/Rule/DL3041.hs +31/−6
- src/Hadolint/Rule/DL3059.hs +23/−8
- src/Hadolint/Rule/Shellcheck.hs +121/−20
- src/Hadolint/Shell.hs +1/−1
- test/ConfigSpec.hs +0/−136
- test/DL3000.hs +0/−26
- test/DL3001.hs +0/−16
- test/DL3002.hs +0/−86
- test/DL3003.hs +0/−12
- test/DL3004.hs +0/−19
- test/DL3005.hs +0/−13
- test/DL3006.hs +0/−42
- test/DL3007.hs +0/−20
- test/DL3008.hs +0/−61
- test/DL3009.hs +0/−81
- test/DL3010.hs +0/−13
- test/DL3011.hs +0/−12
- test/DL3012.hs +0/−18
- test/DL3013.hs +0/−139
- test/DL3014.hs +0/−31
- test/DL3015.hs +0/−26
- test/DL3016.hs +0/−123
- test/DL3018.hs +0/−90
- test/DL3019.hs +0/−25
- test/DL3020.hs +0/−18
- test/DL3021.hs +0/−13
- test/DL3022.hs +0/−29
- test/DL3023.hs +0/−26
- test/DL3024.hs +0/−27
- test/DL3025.hs +0/−36
- test/DL3026.hs +0/−56
- test/DL3027.hs +0/−19
- test/DL3028.hs +0/−42
- test/DL3029.hs +0/−12
- test/DL3030.hs +0/−18
- test/DL3032.hs +0/−18
- test/DL3033.hs +0/−18
- test/DL3034.hs +0/−18
- test/DL3035.hs +0/−15
- test/DL3036.hs +0/−18
- test/DL3037.hs +0/−28
- test/DL3038.hs +0/−18
- test/DL3040.hs +0/−18
- test/DL3041.hs +0/−18
- test/DL3042.hs +0/−86
- test/DL3043.hs +0/−43
- test/DL3044.hs +0/−42
- test/DL3045.hs +0/−156
- test/DL3046.hs +0/−15
- test/DL3047.hs +0/−71
- test/DL3048.hs +0/−37
- test/DL3049.hs +0/−80
- test/DL3050.hs +0/−25
- test/DL3051.hs +0/−22
- test/DL3052.hs +0/−22
- test/DL3053.hs +0/−22
- test/DL3054.hs +0/−22
- test/DL3055.hs +0/−25
- test/DL3056.hs +0/−22
- test/DL3057.hs +0/−18
- test/DL3058.hs +0/−22
- test/DL3059.hs +0/−31
- test/DL3060.hs +0/−68
- test/DL4000.hs +0/−15
- test/DL4001.hs +0/−69
- test/DL4003.hs +0/−42
- test/DL4004.hs +0/−45
- test/DL4005.hs +0/−19
- test/DL4006.hs +0/−104
- test/Hadolint/Config/CommandlineSpec.hs +346/−0
- test/Hadolint/Config/ConfigfileSpec.hs +169/−0
- test/Hadolint/Config/ConfigurationSpec.hs +219/−0
- test/Hadolint/Config/EnvironmentSpec.hs +186/−0
- test/Hadolint/Config/SpecHook.hs +8/−0
- test/Hadolint/Formatter/ParseErrorSpec.hs +31/−0
- test/Hadolint/Formatter/SarifSpec.hs +32/−0
- test/Hadolint/Formatter/TTYSpec.hs +87/−0
- test/Hadolint/PragmaSpec.hs +57/−0
- test/Hadolint/Rule/DL3000Spec.hs +28/−0
- test/Hadolint/Rule/DL3001Spec.hs +18/−0
- test/Hadolint/Rule/DL3002Spec.hs +88/−0
- test/Hadolint/Rule/DL3003Spec.hs +14/−0
- test/Hadolint/Rule/DL3004Spec.hs +21/−0
- test/Hadolint/Rule/DL3005Spec.hs +15/−0
- test/Hadolint/Rule/DL3006Spec.hs +44/−0
- test/Hadolint/Rule/DL3007Spec.hs +24/−0
- test/Hadolint/Rule/DL3008Spec.hs +63/−0
- test/Hadolint/Rule/DL3009Spec.hs +133/−0
- test/Hadolint/Rule/DL3010Spec.hs +61/−0
- test/Hadolint/Rule/DL3011Spec.hs +14/−0
- test/Hadolint/Rule/DL3012Spec.hs +20/−0
- test/Hadolint/Rule/DL3013Spec.hs +141/−0
- test/Hadolint/Rule/DL3014Spec.hs +33/−0
- test/Hadolint/Rule/DL3015Spec.hs +28/−0
- test/Hadolint/Rule/DL3016Spec.hs +124/−0
- test/Hadolint/Rule/DL3018Spec.hs +92/−0
- test/Hadolint/Rule/DL3019Spec.hs +27/−0
- test/Hadolint/Rule/DL3020Spec.hs +23/−0
- test/Hadolint/Rule/DL3021Spec.hs +15/−0
- test/Hadolint/Rule/DL3022Spec.hs +31/−0
- test/Hadolint/Rule/DL3023Spec.hs +28/−0
- test/Hadolint/Rule/DL3024Spec.hs +29/−0
- test/Hadolint/Rule/DL3025Spec.hs +38/−0
- test/Hadolint/Rule/DL3026Spec.hs +62/−0
- test/Hadolint/Rule/DL3027Spec.hs +21/−0
- test/Hadolint/Rule/DL3028Spec.hs +44/−0
- test/Hadolint/Rule/DL3029Spec.hs +14/−0
- test/Hadolint/Rule/DL3030Spec.hs +19/−0
- test/Hadolint/Rule/DL3032Spec.hs +20/−0
- test/Hadolint/Rule/DL3033Spec.hs +28/−0
- test/Hadolint/Rule/DL3034Spec.hs +24/−0
- test/Hadolint/Rule/DL3035Spec.hs +17/−0
- test/Hadolint/Rule/DL3036Spec.hs +20/−0
- test/Hadolint/Rule/DL3037Spec.hs +30/−0
- test/Hadolint/Rule/DL3038Spec.hs +20/−0
- test/Hadolint/Rule/DL3040Spec.hs +20/−0
- test/Hadolint/Rule/DL3041Spec.hs +28/−0
- test/Hadolint/Rule/DL3042Spec.hs +88/−0
- test/Hadolint/Rule/DL3043Spec.hs +45/−0
- test/Hadolint/Rule/DL3044Spec.hs +44/−0
- test/Hadolint/Rule/DL3045Spec.hs +158/−0
- test/Hadolint/Rule/DL3046Spec.hs +17/−0
- test/Hadolint/Rule/DL3047Spec.hs +73/−0
- test/Hadolint/Rule/DL3048Spec.hs +39/−0
- test/Hadolint/Rule/DL3049Spec.hs +82/−0
- test/Hadolint/Rule/DL3050Spec.hs +31/−0
- test/Hadolint/Rule/DL3051Spec.hs +24/−0
- test/Hadolint/Rule/DL3052Spec.hs +23/−0
- test/Hadolint/Rule/DL3053Spec.hs +24/−0
- test/Hadolint/Rule/DL3054Spec.hs +24/−0
- test/Hadolint/Rule/DL3055Spec.hs +27/−0
- test/Hadolint/Rule/DL3056Spec.hs +24/−0
- test/Hadolint/Rule/DL3057Spec.hs +20/−0
- test/Hadolint/Rule/DL3058Spec.hs +24/−0
- test/Hadolint/Rule/DL3059Spec.hs +73/−0
- test/Hadolint/Rule/DL3060Spec.hs +70/−0
- test/Hadolint/Rule/DL4000Spec.hs +16/−0
- test/Hadolint/Rule/DL4001Spec.hs +71/−0
- test/Hadolint/Rule/DL4003Spec.hs +44/−0
- test/Hadolint/Rule/DL4004Spec.hs +47/−0
- test/Hadolint/Rule/DL4005Spec.hs +21/−0
- test/Hadolint/Rule/DL4006Spec.hs +106/−0
- test/Hadolint/Rule/ShellcheckSpec.hs +171/−0
- test/Hadolint/ShellSpec.hs +53/−0
- test/Helpers.hs +30/−12
- test/RegressionSpec.hs +32/−0
- test/ShellSpec.hs +0/−52
- test/Shellcheck.hs +0/−130
- test/Spec.hs +1/−233
README.md view
@@ -8,12 +8,12 @@ src="https://hadolint.github.io/hadolint/img/cat_container.png" /> A smarter Dockerfile linter that helps you build [best practice][] Docker-images. The linter is parsing the Dockerfile into an AST and performs rules on-top of the AST. It is standing on the shoulders of [ShellCheck][] to lint+images. The linter parses the Dockerfile into an AST and performs rules on+top of the AST. It stands on the shoulders of [ShellCheck][] to lint the Bash code inside `RUN` instructions. [:globe_with_meridians: **Check the online version on- hadolint.github.io/hadolint**](https://hadolint.github.io/hadolint)+hadolint.github.io/hadolint**](https://hadolint.github.io/hadolint) [](https://hadolint.github.io/hadolint) ## How to use@@ -26,55 +26,67 @@ hadolint --trusted-registry my-company.com:500 <Dockerfile> # Warn when using untrusted FROM images ``` -Docker comes to the rescue to provide an easy way how to run `hadolint` on most+Docker comes to the rescue, providing an easy way how to run `hadolint` on most platforms. Just pipe your `Dockerfile` to `docker run`: ```bash-$ docker run --rm -i hadolint/hadolint < Dockerfile+docker run --rm -i hadolint/hadolint < Dockerfile # OR-$ docker run --rm -i ghcr.io/hadolint/hadolint < Dockerfile+docker run --rm -i ghcr.io/hadolint/hadolint < Dockerfile ``` -or if you are using Windows PowerShell:+or using [Podman](https://podman.io/): +```bash+podman run --rm -i docker.io/hadolint/hadolint < Dockerfile+# OR+podman run --rm -i ghcr.io/hadolint/hadolint < Dockerfile+```++or using Windows PowerShell:+ ```powershell-> cat .\Dockerfile | docker run --rm -i hadolint/hadolint+cat .\Dockerfile | docker run --rm -i hadolint/hadolint ``` ## Install You can download prebuilt binaries for OSX, Windows and Linux from the latest-[release page][]. However, if it doesn't work for you, please fall back to-Docker, `brew` or source installation.+[release page][]. However, if this does not work for you, please fall back to+container (Docker), `brew` or source installation. -If you are on OSX you can use [brew](https://brew.sh/) to install `hadolint`.+On OSX, you can use [brew](https://brew.sh/) to install `hadolint`. ```bash brew install hadolint ``` -On Windows you can use [scoop](https://github.com/lukesampson/scoop) to+On Windows, you can use [scoop](https://github.com/lukesampson/scoop) to install `hadolint`. ```batch scoop install hadolint ``` -As shown before, `hadolint` is available as a Docker container:+On distributions that have `nix` installed, you can use the `hadolint`+package to run ad-hoc shells or permanently install `hadolint` into+your environment. +As mentioned earlier, `hadolint` is available as a container image:+ ```bash docker pull hadolint/hadolint # OR docker pull ghcr.io/hadolint/hadolint ``` -If you need a Docker container with shell access, use the Debian or Alpine-variants of the Docker image:+If you need a container with shell access, use the Debian or Alpine+variants: ```bash docker pull hadolint/hadolint:latest-debian-# OR +# OR docker pull hadolint/hadolint:latest-alpine # OR docker pull ghcr.io/hadolint/hadolint:latest-debian@@ -91,6 +103,18 @@ && stack install ``` +If you want the+[VS Code Hadolint](https://github.com/michaellzc/vscode-hadolint)+extension to use Hadolint in a container, you can use the following+[wrapper script](https://github.com/hadolint/hadolint/issues/691#issuecomment-932116329):++```bash+#!/bin/bash+dockerfile="$1"+shift+docker run --rm -i hadolint/hadolint hadolint "$@" - < "$dockerfile"+```+ ## CLI ```bash@@ -100,13 +124,13 @@ ```text hadolint - Dockerfile Linter written in Haskell -Usage: hadolint [-v|--version] [--no-fail] [--no-color] [-c|--config FILENAME] - [-V|--verbose] [-f|--format ARG] [DOCKERFILE...] - [--error RULECODE] [--warning RULECODE] [--info RULECODE] - [--style RULECODE] [--ignore RULECODE] - [--trusted-registry REGISTRY (e.g. docker.io)] - [--require-label LABELSCHEMA (e.g. maintainer:text)] - [--strict-labels] [-t|--failure-theshold THRESHOLD]+Usage: hadolint [-v|--version] [--no-fail] [--no-color] [-c|--config FILENAME]+ [-V|--verbose] [-f|--format ARG] [DOCKERFILE...]+ [--error RULECODE] [--warning RULECODE] [--info RULECODE]+ [--style RULECODE] [--ignore RULECODE]+ [--trusted-registry REGISTRY (e.g. docker.io)]+ [--require-label LABELSCHEMA (e.g. maintainer:text)]+ [--strict-labels] [-t|--failure-threshold THRESHOLD] Lint Dockerfile for errors and best practices Available options:@@ -136,7 +160,7 @@ format requirement `format` --strict-labels Do not permit labels other than specified in `label-schema`- -t,--failure-theshold THRESHOLD+ -t,--failure-threshold THRESHOLD Exit with failure code only when rules with a severity above THRESHOLD are violated. Accepted values: [error | warning | info | style | ignore |@@ -145,14 +169,22 @@ ## Configure -Configuration files can be used globally or per project. By default,-`hadolint` will look for a configuration file in the current directory-with the name `.hadolint.yaml` or `.hadolint.yml`+Configuration files can be used globally or per project.+Hadolint looks for configuration files in the following locations or their+platform specific equivalents in this order and uses the first one exclusively:+- `$PWD/.hadolint.yaml`+- `$XDG_CONFIG_HOME/hadolint.yaml`+- `$HOME/.config/hadolint.yaml`+- `$HOME/.hadolint/hadolint.yaml or $HOME/hadolint/config.yaml`+- `$HOME/.hadolint.yaml` +In windows, the `%LOCALAPPDATA%` environment variable is used instead of +`XDG_CONFIG_HOME`. Config files can have either `yaml` or `yml` extensions.+ `hadolint` full `yaml` config file schema ```yaml-failure-threshold: string # name of threshold level (error | warning | info | style | ignore | none) +failure-threshold: string # name of threshold level (error | warning | info | style | ignore | none) format: string # Output format (tty | json | checkstyle | codeclimate | gitlab_codeclimate | codacy) ignored: [string] # list of rules label-schema: # See Linting Labels below for specific label-schema details@@ -186,7 +218,7 @@ Additionally, `hadolint` can warn you when images from untrusted repositories are being used in Dockerfiles, you can append the-`trustedRegistries` keys to the configuration file as shown below:+`trustedRegistries` keys to the configuration file, as shown below: ```yaml ignored:@@ -226,18 +258,6 @@ - DL3032 ``` -The global configuration file should be placed in the folder-specified by `XDG_CONFIG_HOME`,-with the name `hadolint.yaml` or `hadolint.yml`. In summary, the-following locations are valid for the configuration file, in order-or preference:--- `$PWD/.hadolint.yaml`-- `$XDG_CONFIG_HOME/hadolint.yaml`-- `~/.config/hadolint.yaml`--In windows, the `%LOCALAPPDATA%` environment variable is used instead of `XDG_CONFIG_HOME`- Additionally, you can pass a custom configuration file in the command line with the `--config` option @@ -245,7 +265,7 @@ hadolint --config /path/to/config.yaml Dockerfile ``` -To pass a custom configuration file (using relative or absolute path) to +To pass a custom configuration file (using relative or absolute path) to a container, use the following command: ```bash@@ -254,11 +274,40 @@ docker run --rm -i -v /your/path/to/hadolint.yaml:/.config/hadolint.yaml ghcr.io/hadolint/hadolint < Dockerfile ``` +In addition to config files, Hadolint can be configured with environment+variables.+```bash+NO_COLOR=1 # Truthy value e.g. 1, true or yes+HADOLINT_NOFAIL=1 # Truthy value e.g. 1, true or yes+HADOLINT_VERBOSE=1 # Truthy value e.g. 1, true or yes+HADOLINT_FORMAT=json # Output format (tty | json | checkstyle | codeclimate | gitlab_codeclimate | codacy | sarif )+HADOLINT_FAILURE_THRESHOLD=info # threshold level (error | warning | info | style | ignore | none)+HADOLINT_OVERRIDE_ERROR=DL3010,DL3020 # comma separated list of rule codes+HADOLINT_OVERRIDE_WARNING=DL3010,DL3020 # comma separated list of rule codes+HADOLINT_OVERRIDE_INFO=DL3010,DL3020 # comma separated list of rule codes+HADOLINT_OVERRIDE_STYLE=DL3010,DL3020 # comma separated list of rule codes+HADOLINT_IGNORE=DL3010,DL3020 # comma separated list of rule codes+HADOLINT_STRICT_LABELS=1 # Truthy value e.g. 1, true or yes+HADOLINT_TRUSTED_REGISTRIES # comma separated list of registry urls+```++## Non-Posix Shells++When using base images with non-posix shells as default (e.g. Windows based+images) a special pragma `hadolint shell` can specify which shell the base image+uses, so that Hadolint can automatically ignore all shell-specific rules.++```Dockerfile+FROM mcr.microsoft.com/windows/servercore:ltsc2022+# hadolint shell=powershell+RUN Get-Process notepad | Stop-Process+```+ ## Inline ignores -It is also possible to ignore rules by using a special comment directly-above the Dockerfile instruction you want to make an exception for.-Ignore rule comments look like+It is also possible to ignore rules by adding a special comment directly+above the Dockerfile statement for which you want to make an exception for.+Such comments look like `# hadolint ignore=DL3001,SC1081`. For example: ```dockerfile@@ -269,19 +318,19 @@ RUN cd /tmp && echo "hello!" ``` -Inline ignores will only work if place directly above the instruction.+The comment "inline ignores" applies only to the statement following it. ## Linting Labels -Hadolint has the ability to check that specific labels be present and conform+Hadolint is able to check if specific labels are present and conform to a predefined label schema.-First a label schema must be defined either via commandline:+First, a label schema must be defined either via the command line: ```bash hadolint --require-label author:text --require-label version:semver Dockerfile ``` -or via config file:+or via the config file: ```yaml label-schema:@@ -306,20 +355,20 @@ | spdx | An [SPDX license identifier][spdxid] | | email | An email address conforming to [RFC 5322][rfc5322] | -By default, Hadolint ignores any label not specified in the label schema. To-warn on such additional labels, turn on strict labels:+By default, Hadolint ignores any label that is not specified in the label schema. To+warn against such additional labels, turn on strict labels, using the command line: ```bash hadolint --strict-labels --require-label version:semver Dockerfile ``` -or in the config file:+or the config file: ```yaml strict-labels: true ``` -When strict labels is enabled, but no label schema has been specified, Hadolint+When strict labels is enabled, but no label schema is specified, `hadolint` will warn if any label is present. ### Note on dealing with variables in labels@@ -342,8 +391,8 @@ ## Integrations -To get most of `hadolint` it is useful to integrate it as a check to your CI-or to your editor, or as a pre-commit hook, to lint your `Dockerfile` as you+To get most of `hadolint`, it is useful to integrate it as a check in your CI+or into your editor, or as a pre-commit hook, to lint your `Dockerfile` as you write it. See our [Integration][] docs. - [Code Review Platform Integrations][]@@ -356,16 +405,18 @@ An incomplete list of implemented rules. Click on the error code to get more detailed information. -- Rules with the prefix `DL` originate from `hadolint`. Take a look at-`Rules.hs` to find the implementation of the rules.+- Rules with the prefix `DL` are from `hadolint`. Have a look at+ `Rules.hs` to find the implementation of the rules. -- Rules with the `SC` prefix originate from **ShellCheck** (Only the most-common rules are listed, there are dozens more)+- Rules with the `SC` prefix are from **ShellCheck** (only the most+ common rules are listed, there are dozens more). Please [create an issue][] if you have an idea for a good rule.+ <!--lint disable maximum-line-length-->+ | Rule | Default Severity | Description |-|:-------------------------------------------------------------|:-----------------|:----------------------------------------------------------------------------------------------------------------------------------------------------|+| :----------------------------------------------------------- | :--------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------- | | [DL3000](https://github.com/hadolint/hadolint/wiki/DL3000) | Error | Use absolute WORKDIR. | | [DL3001](https://github.com/hadolint/hadolint/wiki/DL3001) | Info | For some bash commands it makes no sense running them in a Docker container like ssh, vim, shutdown, service, ps, free, top, kill, mount, ifconfig. | | [DL3002](https://github.com/hadolint/hadolint/wiki/DL3002) | Warning | Last user should not be root. |@@ -399,7 +450,7 @@ | [DL3032](https://github.com/hadolint/hadolint/wiki/DL3032) | Warning | `yum clean all` missing after yum command. | | [DL3033](https://github.com/hadolint/hadolint/wiki/DL3033) | Warning | Specify version with `yum install -y <package>-<version>` | | [DL3034](https://github.com/hadolint/hadolint/wiki/DL3034) | Warning | Non-interactive switch missing from `zypper` command: `zypper install -y` |-| [DL3035](https://github.com/hadolint/hadolint/wiki/DL3035) | Warning | Do not use `zypper dist-upgrade`. |+| [DL3035](https://github.com/hadolint/hadolint/wiki/DL3035) | Warning | Do not use `zypper dist-upgrade`. | | [DL3036](https://github.com/hadolint/hadolint/wiki/DL3036) | Warning | `zypper clean` missing after zypper use. | | [DL3037](https://github.com/hadolint/hadolint/wiki/DL3037) | Warning | Specify version with `zypper install -y <package>[=]<version>`. | | [DL3038](https://github.com/hadolint/hadolint/wiki/DL3038) | Warning | Use the `-y` switch to avoid manual input `dnf install -y <package>` |@@ -431,7 +482,7 @@ | [DL4005](https://github.com/hadolint/hadolint/wiki/DL4005) | Warning | Use `SHELL` to change the default shell. | | [DL4006](https://github.com/hadolint/hadolint/wiki/DL4006) | Warning | Set the `SHELL` option -o pipefail before `RUN` with a pipe in it | | [SC1000](https://github.com/koalaman/shellcheck/wiki/SC1000) | | `$` is not used specially and should therefore be escaped. |-| [SC1001](https://github.com/koalaman/shellcheck/wiki/SC1001) | | This `\c` will be a regular `'c'` in this context. |+| [SC1001](https://github.com/koalaman/shellcheck/wiki/SC1001) | | This `\c` will be a regular `'c'` in this context. | | [SC1007](https://github.com/koalaman/shellcheck/wiki/SC1007) | | Remove space after `=` if trying to assign a value (or for empty string, use `var='' ...`). | | [SC1010](https://github.com/koalaman/shellcheck/wiki/SC1010) | | Use semicolon or linefeed before `done` (or quote to make it literal). | | [SC1018](https://github.com/koalaman/shellcheck/wiki/SC1018) | | This is a unicode non-breaking space. Delete it and retype as space. |@@ -463,11 +514,12 @@ | [SC2154](https://github.com/koalaman/shellcheck/wiki/SC2154) | | var is referenced but not assigned. | | [SC2155](https://github.com/koalaman/shellcheck/wiki/SC2155) | | Declare and assign separately to avoid masking return values. | | [SC2164](https://github.com/koalaman/shellcheck/wiki/SC2164) | | Use <code>cd ... || exit</code> in case `cd` fails. |+ <!--lint enable maximum-line-length--> ## Develop -If you are an experienced Haskeller we would be really thankful if you would+If you are an experienced Haskeller, we would be very grateful if you would tear our code apart in a review. ### Setup@@ -501,13 +553,13 @@ ### Tests -Run unit tests.+Run unit tests: ```bash stack test ``` -Run integration tests.+Run integration tests: ```bash ./integration_test.sh@@ -521,10 +573,15 @@ ## Alternatives -- RedCoolBeans/[dockerlint](https://github.com/RedCoolBeans/dockerlint/)-- projectatomic/[dockerfile_lint](https://github.com/projectatomic/dockerfile_lint/)+- replicatedhq/[dockerfilelint](https://github.com/replicatedhq/dockerfilelint),+ the other linter used by the [super-linter](https://github.com/github/super-linter/blob/main/README.md#supported-linters) +- RedCoolBeans/[dockerlint](https://github.com/RedCoolBeans/dockerlint/)++- projectatomic/[dockerfile_lint](https://github.com/projectatomic/dockerfile_lint/)+ <!-- References -->+ [github-actions-img]: https://github.com/hadolint/hadolint/workflows/Haskell%20Tests/badge.svg?branch=master [github-actions]: https://travis-ci.org/hadolint/hadolint/actions [license-img]: https://img.shields.io/badge/license-GPL--3-blue.svg
app/Main.hs view
@@ -1,262 +1,28 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE TemplateHaskell #-}- module Main where -import Control.Applicative import Control.Monad (when)-import qualified Data.Bifunctor as Bifunctor+import Data.Default+import Hadolint (OutputFormat (..), printResults, DLSeverity (..))+import Hadolint.Config+import Prettyprinter import qualified Data.List.NonEmpty as NonEmpty-import qualified Data.Map as Map-import Data.Maybe import qualified Data.Sequence as Seq-import qualified Data.Set as Set-import Data.String (IsString (fromString))-import qualified Data.Text as Text-import qualified Data.Version-import qualified Development.GitRev import qualified Hadolint-import qualified Hadolint.Formatter.Format as Format import qualified Hadolint.Rule as Rule import Options.Applicative- ( Parser,- ReadM,- action,- argument,- completeWith,- eitherReader,- execParser,+ ( execParser, fullDesc, header,- help, helper, info,- long,- maybeReader,- metavar,- option,- progDesc,- short,- showDefaultWith,- str,- strOption,- switch,- value,+ progDesc ) -- version from hadolint.cabal file-import qualified Paths_hadolint as Meta-import System.Environment import System.Exit (exitFailure, exitSuccess)-import System.IO (hPutStrLn, stderr)+import System.IO (hPrint, stderr) -data CommandOptions = CommandOptions- { showVersion :: Bool,- noFail :: Bool,- nocolor :: Bool,- configFile :: Maybe FilePath,- isVerbose :: Bool,- format :: Hadolint.OutputFormat,- dockerfiles :: [String],- 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-toOutputFormat "checkstyle" = Just Hadolint.Checkstyle-toOutputFormat "codacy" = Just Hadolint.Codacy-toOutputFormat _ = Nothing--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"-showFormat Hadolint.Checkstyle = "checkstyle"-showFormat Hadolint.Codacy = "codacy"--toNofailSeverity :: String -> Maybe Rule.DLSeverity-toNofailSeverity "error" = Just Rule.DLErrorC-toNofailSeverity "warning" = Just Rule.DLWarningC-toNofailSeverity "info" = Just Rule.DLInfoC-toNofailSeverity "style" = Just Rule.DLStyleC-toNofailSeverity "ignore" = Just Rule.DLIgnoreC-toNofailSeverity "none" = Just Rule.DLIgnoreC-toNofailSeverity _ = Nothing--parseOptions :: Parser CommandOptions-parseOptions =- CommandOptions- <$> version -- CLI options parser definition- <*> noFail- <*> nocolor- <*> configFile- <*> isVerbose- <*> outputFormat- <*> files- <*> lintOptions- <*> filePathInReportOption- where- version = switch (long "version" <> short 'v' <> help "Show version")-- noFail = switch (long "no-fail" <> help "Don't exit with a failure status code when any rule is violated")-- noFailCutoff =- option- (maybeReader toNofailSeverity)- ( short 't'- <> long "failure-threshold"- <> help- "Exit with failure code only when rules with a severity \- \equal to or above THRESHOLD are violated. Accepted values: \- \[error | warning | info | style | ignore | none]"- <> value Rule.DLInfoC- <> metavar "THRESHOLD"- <> showDefaultWith (Text.unpack . Format.severityText)- <> completeWith ["error", "warning", "info", "style", "ignore", "none"]- )-- nocolor = switch (long "no-color" <> help "Don't colorize output")-- strictlabels =- switch- ( long "strict-labels"- <> help "Do not permit labels other than specified in `label-schema`"- )-- configFile =- optional- ( strOption- ( long "config" <> short 'c' <> metavar "FILENAME"- <> help "Path to the configuration file"- )- )-- isVerbose = switch (long "verbose" <> short 'V' <> help "Enables verbose logging of hadolint's output to stderr")-- outputFormat =- option- (maybeReader toOutputFormat)- ( long "format"- <> short 'f' -- options for the output format- <> help- "The output format for the results [tty | json | checkstyle | codeclimate | gitlab_codeclimate | codacy | sonarqube]"- <> value Hadolint.TTY- <> showDefaultWith showFormat -- The default value- <> completeWith ["tty", "json", "checkstyle", "codeclimate", "gitlab_codeclimate", "codacy", "sonarqube"]- )-- errorList =- many- ( strOption- ( long "error"- <> help "Make the rule `RULECODE` have the level `error`"- <> metavar "RULECODE"- )- )-- warningList =- many- ( strOption- ( long "warning"- <> help "Make the rule `RULECODE` have the level `warning`"- <> metavar "RULECODE"- )- )-- infoList =- many- ( strOption- ( long "info"- <> help "Make the rule `RULECODE` have the level `info`"- <> metavar "RULECODE"- )- )-- styleList =- many- ( strOption- ( long "style"- <> help "Make the rule `RULECODE` have the level `style`"- <> metavar "RULECODE"- )- )-- ignoreList =- many- ( strOption- ( long "ignore"- <> help "A rule to ignore. If present, the ignore list in the config file is ignored"- <> metavar "RULECODE"- )- )-- files = many (argument str (metavar "DOCKERFILE..." <> action "file"))-- lintOptions =- Hadolint.LintOptions- <$> errorList- <*> warningList- <*> infoList- <*> styleList- <*> ignoreList- <*> 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- ( option- readSingleLabelSchema- ( long "require-label"- <> help "The option --require-label=label:format makes Hadolint check that the label `label` conforms to format requirement `format`"- <> metavar "LABELSCHEMA (e.g. maintainer:text)"- )- )-- parseRulesConfig =- Hadolint.RulesConfig- <$> parseAllowedRegistries- <*> labels- <*> strictlabels-- parseAllowedRegistries =- Set.fromList . fmap fromString- <$> many- ( strOption- ( long "trusted-registry"- <> help "A docker registry to allow to appear in FROM instructions"- <> metavar "REGISTRY (e.g. docker.io)"- )- )--type SingleLabelSchema = (Rule.LabelName, Rule.LabelType)--readSingleLabelSchema :: ReadM SingleLabelSchema-readSingleLabelSchema = eitherReader $ \s -> labelParser (Text.pack s)--labelParser :: Text.Text -> Either String (Rule.LabelName, Rule.LabelType)-labelParser l =- case Bifunctor.second (Rule.read . Text.drop 1) $ Text.breakOn ":" l of- (ln, Right lt) -> Right (ln, lt)- (_, Left e) -> Left $ Text.unpack e--noFailure :: Hadolint.Result s e -> Rule.DLSeverity -> Bool+noFailure :: Hadolint.Result s e -> 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)@@ -264,56 +30,64 @@ exitProgram :: Foldable f =>- CommandOptions ->- Hadolint.LintOptions ->+ Configuration -> f (Hadolint.Result s e) -> IO ()-exitProgram cmd conf res- | noFail cmd = exitSuccess- | Hadolint.shallSkipErrorStatus (format cmd) = exitSuccess- | all (`noFailure` Hadolint.failThreshold conf) res = exitSuccess+exitProgram conf res+ | noFail conf = exitSuccess+ | CodeclimateJson == format conf = exitSuccess+ | Codacy == format conf = exitSuccess+ | all (`noFailure` failureThreshold conf) res = exitSuccess | otherwise = exitFailure -runLint :: CommandOptions -> Hadolint.LintOptions -> NonEmpty.NonEmpty String -> IO ()-runLint cmd conf files = do+runLint ::+ CommandlineConfig ->+ Configuration ->+ IO ()+runLint cmd conf = do+ let files = NonEmpty.fromList $ dockerfiles cmd+ filePathInReport = filePathInReportOption cmd res <- Hadolint.lintIO conf files- noColorEnv <- lookupEnv "NO_COLOR"- let noColor = nocolor cmd || isJust noColorEnv- let filePathInReport = filePathInReportOption cmd- Hadolint.printResults (format cmd) noColor filePathInReport res- exitProgram cmd conf res+ printResults (format conf) (noColor conf) filePathInReport res+ exitProgram conf res +execute :: CommandlineConfig -> Configuration -> IO ()+execute CommandlineConfig {showVersion = True} _ =+ putStrLn Hadolint.getVersion >> exitSuccess+execute CommandlineConfig {dockerfiles = []} _ =+ putStrLn "Please provide a Dockerfile" >> exitFailure+execute cmd config = runLint cmd config++ main :: IO () main = do- cmd <- execParser opts- execute cmd+ invokedWith <- execParser opts+ fromEnvironment <- getConfigFromEnvironment+ let fromCommandline = configuration invokedWith+ eitherFromConfigfile <- getConfigFromFile+ (configFile invokedWith) (Just True == partialVerbose fromCommandline)+ fromConfigfile <- getConfigFromEither eitherFromConfigfile++ let runningConfig =+ applyPartialConfiguration+ def+ ( fromEnvironment <> fromConfigfile <> fromCommandline )++ when (verbose runningConfig) (hPrint stderr (pretty runningConfig))+ execute invokedWith runningConfig where- execute CommandOptions {showVersion = True} = putStrLn getVersion >> exitSuccess- execute CommandOptions {dockerfiles = []} =- putStrLn "Please provide a Dockerfile" >> exitFailure- execute cmd = do- maybeConfig <- Hadolint.getConfig (configFile cmd)- when (isVerbose cmd) (hPutStrLn stderr $ getFilePathDescription maybeConfig)- lintConfig <- Hadolint.applyConfig maybeConfig (lintingOptions cmd)- let files = NonEmpty.fromList (dockerfiles cmd)- case lintConfig of- Left err -> error err- Right conf -> runLint cmd conf files opts = info- (helper <*> parseOptions)- ( fullDesc <> progDesc "Lint Dockerfile for errors and best practices"+ ( helper <*> parseCommandline )+ ( fullDesc <> header "hadolint - Dockerfile Linter written in Haskell"+ <> progDesc "Lint Dockerfile for errors and best practices" ) -getVersion :: String-getVersion- | version == "UNKNOWN" =- "Haskell Dockerfile Linter " ++ Data.Version.showVersion Meta.version ++ "-no-git"- | otherwise = "Haskell Dockerfile Linter " ++ version- where- version = $(Development.GitRev.gitDescribe)--getFilePathDescription :: Maybe FilePath -> String-getFilePathDescription Nothing = "No configuration was specified. Using default configuration"-getFilePathDescription (Just filepath) = "Configuration file used: " ++ filepath+ -- Either return the config or print the error message+ getConfigFromEither ei =+ case ei of+ Left err -> do+ hPrint stderr err+ return mempty+ Right conf -> return conf
hadolint.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: hadolint-version: 2.7.0+version: 2.8.0 synopsis: Dockerfile Linter JavaScript API description: A smarter Dockerfile linter that helps you build best practice Docker images. category: Development@@ -32,15 +32,22 @@ exposed-modules: Hadolint Hadolint.Config+ Hadolint.Config.Commandline+ Hadolint.Config.Configfile+ Hadolint.Config.Configuration+ Hadolint.Config.Environment+ Hadolint.Formatter Hadolint.Formatter.Checkstyle Hadolint.Formatter.Codacy Hadolint.Formatter.Codeclimate Hadolint.Formatter.Format Hadolint.Formatter.Json+ Hadolint.Formatter.Sarif Hadolint.Formatter.SonarQube Hadolint.Formatter.TTY- Hadolint.Ignore Hadolint.Lint+ Hadolint.Meta+ Hadolint.Pragma Hadolint.Process Hadolint.Rule Hadolint.Rule.DL3000@@ -116,13 +123,16 @@ hs-source-dirs: src default-extensions:- OverloadedStrings- NamedFieldPuns- DeriveGeneric DeriveAnyClass+ DeriveGeneric+ NamedFieldPuns+ OverloadedStrings+ PatternSynonyms RecordWildCards- StrictData ScopedTypeVariables+ StrictData+ TemplateHaskell+ TupleSections PatternSynonyms ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -optP-Wno-nonportable-include-path -flate-dmd-anal build-depends:@@ -135,18 +145,22 @@ , colourista , containers , cryptonite+ , data-default , deepseq ==1.4.4.* , directory >=1.3.0 , email-validate , filepath , foldl+ , gitrev >=1.3.1 , ilist- , language-docker >=10.1.0 && <11+ , language-docker >=10.3.0 && <11 , megaparsec >=9.0.0 , mtl , network-uri+ , optparse-applicative >=0.14.0 , parallel , parsec >=3.1.14+ , prettyprinter >=1.7.0 , semver , spdx , split >=0.2@@ -163,24 +177,27 @@ hs-source-dirs: app default-extensions:- OverloadedStrings- NamedFieldPuns- DeriveGeneric DeriveAnyClass+ DeriveGeneric+ NamedFieldPuns+ OverloadedStrings+ PatternSynonyms RecordWildCards- StrictData ScopedTypeVariables- PatternSynonyms+ StrictData TemplateHaskell+ TupleSections+ PatternSynonyms ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -optP-Wno-nonportable-include-path -flate-dmd-anal -O2 -threaded -rtsopts "-with-rtsopts=-N5 -A4m" build-depends: base >=4.8 && <5 , containers- , gitrev >=1.3.1+ , data-default , hadolint- , language-docker >=10.1.0 && <11+ , language-docker >=10.3.0 && <11 , megaparsec >=9.0.0 , optparse-applicative >=0.14.0+ , prettyprinter >=1.7.0 , text if flag(static) && !(os(osx)) ld-options: -static -pthread@@ -190,85 +207,97 @@ type: exitcode-stdio-1.0 main-is: Spec.hs other-modules:- ConfigSpec- DL3000- DL3001- DL3002- DL3003- DL3004- DL3005- DL3006- DL3007- DL3008- DL3009- DL3010- DL3011- DL3012- DL3013- DL3014- DL3015- DL3016- DL3018- DL3019- DL3020- DL3021- DL3022- DL3023- DL3024- DL3025- DL3026- DL3027- DL3028- DL3029- DL3030- DL3032- DL3033- DL3034- DL3035- DL3036- DL3037- DL3038- DL3040- DL3041- DL3042- DL3043- DL3044- DL3045- DL3046- DL3047- DL3048- DL3049- DL3050- DL3051- DL3052- DL3053- DL3054- DL3055- DL3056- DL3057- DL3058- DL3059- DL3060- DL4000- DL4001- DL4003- DL4004- DL4005- DL4006+ Hadolint.Config.CommandlineSpec+ Hadolint.Config.ConfigfileSpec+ Hadolint.Config.ConfigurationSpec+ Hadolint.Config.EnvironmentSpec+ Hadolint.Config.SpecHook+ Hadolint.Formatter.ParseErrorSpec+ Hadolint.Formatter.SarifSpec+ Hadolint.Formatter.TTYSpec+ Hadolint.PragmaSpec+ Hadolint.Rule.DL3000Spec+ Hadolint.Rule.DL3001Spec+ Hadolint.Rule.DL3002Spec+ Hadolint.Rule.DL3003Spec+ Hadolint.Rule.DL3004Spec+ Hadolint.Rule.DL3005Spec+ Hadolint.Rule.DL3006Spec+ Hadolint.Rule.DL3007Spec+ Hadolint.Rule.DL3008Spec+ Hadolint.Rule.DL3009Spec+ Hadolint.Rule.DL3010Spec+ Hadolint.Rule.DL3011Spec+ Hadolint.Rule.DL3012Spec+ Hadolint.Rule.DL3013Spec+ Hadolint.Rule.DL3014Spec+ Hadolint.Rule.DL3015Spec+ Hadolint.Rule.DL3016Spec+ Hadolint.Rule.DL3018Spec+ Hadolint.Rule.DL3019Spec+ Hadolint.Rule.DL3020Spec+ Hadolint.Rule.DL3021Spec+ Hadolint.Rule.DL3022Spec+ Hadolint.Rule.DL3023Spec+ Hadolint.Rule.DL3024Spec+ Hadolint.Rule.DL3025Spec+ Hadolint.Rule.DL3026Spec+ Hadolint.Rule.DL3027Spec+ Hadolint.Rule.DL3028Spec+ Hadolint.Rule.DL3029Spec+ Hadolint.Rule.DL3030Spec+ Hadolint.Rule.DL3032Spec+ Hadolint.Rule.DL3033Spec+ Hadolint.Rule.DL3034Spec+ Hadolint.Rule.DL3035Spec+ Hadolint.Rule.DL3036Spec+ Hadolint.Rule.DL3037Spec+ Hadolint.Rule.DL3038Spec+ Hadolint.Rule.DL3040Spec+ Hadolint.Rule.DL3041Spec+ Hadolint.Rule.DL3042Spec+ Hadolint.Rule.DL3043Spec+ Hadolint.Rule.DL3044Spec+ Hadolint.Rule.DL3045Spec+ Hadolint.Rule.DL3046Spec+ Hadolint.Rule.DL3047Spec+ Hadolint.Rule.DL3048Spec+ Hadolint.Rule.DL3049Spec+ Hadolint.Rule.DL3050Spec+ Hadolint.Rule.DL3051Spec+ Hadolint.Rule.DL3052Spec+ Hadolint.Rule.DL3053Spec+ Hadolint.Rule.DL3054Spec+ Hadolint.Rule.DL3055Spec+ Hadolint.Rule.DL3056Spec+ Hadolint.Rule.DL3057Spec+ Hadolint.Rule.DL3058Spec+ Hadolint.Rule.DL3059Spec+ Hadolint.Rule.DL3060Spec+ Hadolint.Rule.DL4000Spec+ Hadolint.Rule.DL4001Spec+ Hadolint.Rule.DL4003Spec+ Hadolint.Rule.DL4004Spec+ Hadolint.Rule.DL4005Spec+ Hadolint.Rule.DL4006Spec+ Hadolint.Rule.ShellcheckSpec+ Hadolint.ShellSpec Helpers- Shellcheck- ShellSpec+ RegressionSpec Paths_hadolint hs-source-dirs: test default-extensions:- OverloadedStrings- NamedFieldPuns- DeriveGeneric DeriveAnyClass+ DeriveGeneric+ NamedFieldPuns+ OverloadedStrings+ PatternSynonyms RecordWildCards- StrictData ScopedTypeVariables+ StrictData+ TemplateHaskell+ TupleSections PatternSynonyms ImplicitParams OverloadedLists@@ -281,11 +310,14 @@ , base >=4.8 && <5 , bytestring >=0.10 , containers+ , data-default , foldl , hadolint- , hspec- , language-docker >=10.1.0 && <11+ , hspec >=2.8.3+ , language-docker >=10.3.0 && <11 , megaparsec >=9.0.0+ , optparse-applicative >=0.14.0+ , silently , split >=0.2 , text default-language: Haskell2010
src/Hadolint.hs view
@@ -1,47 +1,28 @@ module Hadolint- ( module Hadolint.Lint,- module Hadolint.Process,+ ( module Hadolint.Config,- Result (..),- OutputFormat (..),- shallSkipErrorStatus,- printResults,+ module Hadolint.Formatter,+ module Hadolint.Lint,+ module Hadolint.Meta,+ module Hadolint.Process,++ CheckFailure (..),+ DLSeverity (..),+ LabelSchema,+ LabelName,+ LabelType(..) ) where -import Data.Text (Text) import Hadolint.Config-import qualified Hadolint.Formatter.Checkstyle-import qualified Hadolint.Formatter.Codacy-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.Formatter import Hadolint.Lint+import Hadolint.Meta import Hadolint.Process-import Language.Docker.Parser (DockerfileError)--data OutputFormat- = Json- | SonarQube- | TTY- | CodeclimateJson- | GitlabCodeclimateJson- | Checkstyle- | Codacy- deriving (Show, Eq)--shallSkipErrorStatus :: OutputFormat -> Bool-shallSkipErrorStatus format = format `elem` [CodeclimateJson, Codacy]--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 filePathInReport- CodeclimateJson -> Hadolint.Formatter.Codeclimate.printResults allResults- GitlabCodeclimateJson -> Hadolint.Formatter.Codeclimate.printGitlabResults allResults- Codacy -> Hadolint.Formatter.Codacy.printResults allResults+import Hadolint.Rule+ ( CheckFailure (..),+ DLSeverity (..),+ LabelName,+ LabelSchema,+ LabelType (..),+ )
src/Hadolint/Config.hs view
@@ -1,170 +1,14 @@ module Hadolint.Config- ( applyConfig,- getConfig,- ConfigFile (..),- OverrideConfig (..),- )-where+ ( module Hadolint.Config.Configuration, -import Control.Applicative ((<|>))-import qualified Control.Foldl.Text as Text-import Control.Monad (filterM)-import qualified Data.ByteString as Bytes-import Data.Coerce (coerce)-import Data.Maybe (fromMaybe, listToMaybe)-import qualified Data.Set as Set-import Data.YAML ((.:?))-import qualified Data.YAML as Yaml-import GHC.Generics (Generic)-import qualified Hadolint.Lint as Lint-import qualified Hadolint.Process as Process-import qualified Hadolint.Rule as Rule-import qualified Language.Docker as Docker-import System.Directory- ( XdgDirectory (..),- doesFileExist,- getCurrentDirectory,- getXdgDirectory,+ CommandlineConfig (..),+ getConfigFromEnvironment,+ getConfigFromFile,+ parseCommandline )-import System.FilePath ((</>))--data OverrideConfig = OverrideConfig- { overrideErrorRules :: Maybe [Lint.ErrorRule],- overrideWarningRules :: Maybe [Lint.WarningRule],- overrideInfoRules :: Maybe [Lint.InfoRule],- overrideStyleRules :: Maybe [Lint.StyleRule]- }- deriving (Show, Eq, Generic)--instance Semigroup OverrideConfig where- OverrideConfig a1 a2 a3 a4 <> OverrideConfig b1 b2 b3 b4 =- OverrideConfig (a1 <> b1) (a2 <> b2) (a3 <> b3) (a4 <> b4)--instance Monoid OverrideConfig where- mempty = OverrideConfig Nothing Nothing Nothing Nothing--data ConfigFile = ConfigFile- { overrideRules :: Maybe OverrideConfig,- ignoredRules :: Maybe [Lint.IgnoreRule],- trustedRegistries :: Maybe [Lint.TrustedRegistry],- labelSchemaConfig :: Maybe Rule.LabelSchema,- strictLabelSchema :: Maybe Bool,- failureThreshold :: Maybe Rule.DLSeverity- }- deriving (Show, Eq, Generic)--instance Yaml.FromYAML OverrideConfig where- parseYAML = Yaml.withMap "OverrideConfig" $ \m -> do- err <- m .:? "error"- wrn <- m .:? "warning"- inf <- m .:? "info"- sty <- m .:? "style"- let overrideErrorRules = coerce (err :: Maybe [Text.Text])- overrideWarningRules = coerce (wrn :: Maybe [Text.Text])- overrideInfoRules = coerce (inf :: Maybe [Text.Text])- overrideStyleRules = coerce (sty:: Maybe [Text.Text])- return OverrideConfig {..}--instance Yaml.FromYAML ConfigFile where- parseYAML = Yaml.withMap "ConfigFile" $ \m -> do- overrideRules <- m .:? "override"- ignored <- m .:? "ignored"- let ignoredRules = coerce (ignored :: Maybe [Text.Text])- trustedRegistries <- m .:? "trustedRegistries"- labelSchemaConfig <- m .:? "label-schema"- strictLabelSchema <- m .:? "strict-labels"- failureThreshold <- m .:? "failure-threshold"- return 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 (Prelude.null (Lint.ignoreRules o)) && Lint.rulesConfig o /= mempty = return (Right o)- | otherwise = do- case maybeConfig of- Nothing -> return (Right o)- Just config -> parseAndApply config- where- parseAndApply :: FilePath -> IO (Either String Lint.LintOptions)- parseAndApply configFile = do- contents <- Bytes.readFile configFile- return $ case Yaml.decode1Strict contents of- Left (_, err) -> Left (formatError err configFile)- Right config -> Right $ fromMaybe o (applyOverride config)-- applyOverride ConfigFile {..} =- -- Maybe.do- do- OverrideConfig {..} <- overrideRules <|> Just mempty- overrideError <- overrideErrorRules <|> Just mempty- overrideWarning <- overrideWarningRules <|> Just mempty- overrideInfo <- overrideInfoRules <|> Just mempty- overrideStyle <- overrideStyleRules <|> Just mempty- overrideIgnored <- ignoredRules <|> Just mempty- overrideThreshold <- failureThreshold <|> Just mempty-- trusted <- Set.fromList . coerce <$> (trustedRegistries <|> Just mempty)- schema <- labelSchemaConfig <|> Just mempty- strictLabels <- strictLabelSchema <|> Just False-- let rulesConfig = Lint.rulesConfig o-- return $- Lint.LintOptions- { Lint.errorRules = Lint.errorRules o <|> overrideError,- Lint.warningRules = Lint.warningRules o <|> overrideWarning,- Lint.infoRules = Lint.infoRules o <|> overrideInfo,- Lint.styleRules = Lint.styleRules o <|> overrideStyle,- Lint.ignoreRules = Lint.ignoreRules o <|> overrideIgnored,- Lint.rulesConfig =- Process.RulesConfig- { Process.allowedRegistries = Process.allowedRegistries rulesConfig `ifNull` trusted,- Process.labelSchema = Process.labelSchema rulesConfig `ifNull` schema,- Process.strictLabels = Process.strictLabels rulesConfig || strictLabels- },- Lint.failThreshold = Lint.failThreshold o <> overrideThreshold- }-- ifNull value override = if null value then override else value-- 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- ]+where --- | Gets the configuration file which Hadolint uses-getConfig :: Maybe FilePath -> IO (Maybe FilePath)-getConfig maybeConfig =- case maybeConfig of- Nothing -> findConfig- _ -> return maybeConfig- where- findConfig :: IO (Maybe FilePath)- findConfig = do- localConfigFiles <- traverse- (\filePath -> (</> filePath) <$> getCurrentDirectory)- (fmap ("."++) acceptedConfigs)- configFiles <- traverse (getXdgDirectory XdgConfig) acceptedConfigs- listToMaybe <$> filterM doesFileExist (localConfigFiles ++ configFiles)- where- acceptedConfigs = ["hadolint.yaml", "hadolint.yml"]+import Hadolint.Config.Commandline+import Hadolint.Config.Configuration+import Hadolint.Config.Environment+import Hadolint.Config.Configfile
+ src/Hadolint/Config/Commandline.hs view
@@ -0,0 +1,263 @@+module Hadolint.Config.Commandline+ ( CommandlineConfig (..),+ parseCommandline+ )+where++import Control.Applicative+import Data.Bifunctor (second)+import qualified Data.Map as Map+import qualified Data.Set as Set+import Data.String (IsString (fromString))+import Data.Text (Text, pack, unpack, breakOn, drop)+import Hadolint.Formatter.Format (readMaybeOutputFormat)+import Hadolint.Config.Configuration+import Hadolint.Rule+ ( LabelName,+ LabelType,+ readEitherLabelType,+ readEitherSeverity+ )+import Options.Applicative+ ( Parser,+ ReadM,+ action,+ argument,+ completeWith,+ eitherReader,+ flag',+ help,+ long,+ maybeReader,+ metavar,+ option,+ short,+ str,+ strOption,+ switch,+ )+++data CommandlineConfig =+ CommandlineConfig+ { showVersion :: Bool,+ configFile :: Maybe FilePath,+ dockerfiles :: [String],+ filePathInReportOption :: Maybe FilePath,+ configuration :: PartialConfiguration+ }+ deriving (Eq, Show)++parseCommandline :: Parser CommandlineConfig+parseCommandline =+ CommandlineConfig+ <$> parseVersion+ <*> parseConfigFile+ <*> parseFiles+ <*> parseFilePathInReportOption+ <*> parseConfiguration+ where+ parseVersion = switch (long "version" <> short 'v' <> help "Show version")++ parseConfigFile =+ optional+ ( strOption+ ( long "config" <> short 'c' <> metavar "FILENAME"+ <> help "Path to the configuration file"+ )+ )++ parseFiles = many (argument str (metavar "DOCKERFILE..." <> action "file"))++ parseFilePathInReportOption =+ 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."+ )+ )++ parseConfiguration =+ PartialConfiguration+ <$> parseNoFail+ <*> parseNoColor+ <*> parseVerbose+ <*> parseOutputFormat+ <*> parseErrorList+ <*> parseWarningList+ <*> parseInfoList+ <*> parseStyleList+ <*> parseIgnoreList+ <*> parseAllowedRegistries+ <*> parseLabelSchema+ <*> parseStrictlabels+ <*> parseFailureThreshold++ -- All optional flags with boolean value must not have a default value. The+ -- optional parser then converts it to Nothing.+ -- This is to ensure if they are not set, this is correctly mirrored in the+ -- parsed Configuration and then their behaviour with respect to+ -- configurations from environment variables or config files is correct.+ parseNoFail =+ optional+ ( flag' True+ ( long "no-fail"+ <> help "Don't exit with a failure status code when any rule is\+ \ violated"+ )+ )++ parseNoColor =+ optional+ ( flag' True+ ( long "no-color"+ <> help "Don't colorize output"+ )+ )++ parseVerbose =+ optional+ ( flag' True+ ( long "verbose"+ <> short 'V'+ <> help "Enables verbose logging of hadolint's output to stderr"+ )+ )++ parseOutputFormat =+ optional $+ option+ ( maybeReader (readMaybeOutputFormat . pack) )+ ( long "format"+ <> short 'f' -- options for the output format+ <> help+ "The output format for the results [tty | json | checkstyle |\+ \ codeclimate | gitlab_codeclimate | codacy | sonarqube |\+ \ sarif] (default: tty)"+ <> completeWith+ [ "tty",+ "json",+ "checkstyle",+ "codeclimate",+ "gitlab_codeclimate",+ "codacy",+ "sonarqube",+ "sarif"+ ]+ )++ parseErrorList =+ many+ ( strOption+ ( long "error"+ <> help "Make the rule `RULECODE` have the level `error`"+ <> metavar "RULECODE"+ )+ )++ parseWarningList =+ many+ ( strOption+ ( long "warning"+ <> help "Make the rule `RULECODE` have the level `warning`"+ <> metavar "RULECODE"+ )+ )++ parseInfoList =+ many+ ( strOption+ ( long "info"+ <> help "Make the rule `RULECODE` have the level `info`"+ <> metavar "RULECODE"+ )+ )++ parseStyleList =+ many+ ( strOption+ ( long "style"+ <> help "Make the rule `RULECODE` have the level `style`"+ <> metavar "RULECODE"+ )+ )++ parseIgnoreList =+ many+ ( strOption+ ( long "ignore"+ <> metavar "RULECODE"+ <> help "A rule to ignore. If present, the ignore list in the\+ \ config file is ignored"+ )+ )++ parseAllowedRegistries =+ Set.fromList . fmap fromString+ <$> many+ ( strOption+ ( long "trusted-registry"+ <> metavar "REGISTRY (e.g. docker.io)"+ <> help "A docker registry to allow to appear in FROM \+ \instructions"+ )+ )++ parseLabelSchema =+ Map.fromList+ <$> many+ ( option+ readSingleLabelSchema+ ( long "require-label"+ <> metavar "LABELSCHEMA (e.g. maintainer:text)"+ <> help "The option --require-label=label:format makes\+ \ Hadolint check that the label `label` conforms to\+ \ format requirement `format`"+ )+ )++ parseStrictlabels =+ optional+ ( flag' True+ ( long "strict-labels"+ <> help "Do not permit labels other than specified in\+ \ `label-schema`"+ )+ )++ parseFailureThreshold =+ optional $+ option+ ( eitherReader (readEitherSeverity . pack) )+ ( short 't'+ <> long "failure-threshold"+ <> help+ "Exit with failure code only when rules with a severity equal\+ \ to or above THRESHOLD are violated. Accepted values: [error\+ \ | warning | info | style | ignore | none] (default: info)"+ <> metavar "THRESHOLD"+ <> completeWith+ [ "error",+ "warning",+ "info",+ "style",+ "ignore",+ "none"+ ]+ )+++type SingleLabelSchema = (LabelName, LabelType)++readSingleLabelSchema :: ReadM SingleLabelSchema+readSingleLabelSchema = eitherReader $ \s -> labelParser (pack s)++labelParser :: Text -> Either String (LabelName, LabelType)+labelParser l =+ case second (readEitherLabelType . Data.Text.drop 1) $ breakOn ":" l of+ (ln, Right lt) -> Right (ln, lt)+ (_, Left e) -> Left (unpack e)
+ src/Hadolint/Config/Configfile.hs view
@@ -0,0 +1,101 @@+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"]
+ src/Hadolint/Config/Configuration.hs view
@@ -0,0 +1,229 @@+module Hadolint.Config.Configuration+ ( Configuration (..),+ PartialConfiguration (..),++ applyPartialConfiguration,+ )+where++import Data.Maybe (fromMaybe)+import Control.Applicative+import Data.Coerce (coerce)+import Data.Default+import Data.Text (Text)+import Data.YAML ((.:?), (.!=))+import GHC.Generics (Generic)+import Hadolint.Formatter.Format (OutputFormat (..))+import Hadolint.Rule (RuleCode (..), DLSeverity (..), LabelSchema)+import Language.Docker+import Prettyprinter+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.YAML as Yaml+++data Configuration =+ Configuration+ { noFail :: Bool,+ noColor :: Bool,+ verbose :: Bool,+ format :: OutputFormat,+ errorRules :: [RuleCode],+ warningRules :: [RuleCode],+ infoRules :: [RuleCode],+ styleRules :: [RuleCode],+ ignoreRules :: [RuleCode],+ allowedRegistries :: Set.Set Registry,+ labelSchema :: LabelSchema,+ strictLabels :: Bool,+ failureThreshold :: DLSeverity+ }+ deriving (Eq, Show)++instance Default Configuration where+ def =+ Configuration+ False+ False+ False+ def+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ False+ def++applyPartialConfiguration ::+ Configuration -> PartialConfiguration -> Configuration+applyPartialConfiguration config partial =+ Configuration+ (fromMaybe (noFail config) (partialNoFail partial))+ (fromMaybe (noColor config) (partialNoColor partial))+ (fromMaybe (verbose config) (partialVerbose partial))+ (fromMaybe (format config) (partialFormat partial))+ (errorRules config <> partialErrorRules partial)+ (warningRules config <> partialWarningRules partial)+ (infoRules config <> partialInfoRules partial)+ (styleRules config <> partialStyleRules partial)+ (ignoreRules config <> partialIgnoreRules partial)+ (allowedRegistries config <> partialAllowedRegistries partial)+ (labelSchema config <> partialLabelSchema partial)+ (fromMaybe (strictLabels config) (partialStrictLabels partial))+ (fromMaybe (failureThreshold config) (partialFailureThreshold partial))++instance Pretty Configuration where+ pretty c =+ nest 2+ ( vsep+ [ "Configuration:",+ "no fail:" <+> pretty (noFail c),+ "no color:" <+> pretty (noColor c),+ "output format:" <+> pretty (format c),+ "failure threshold:" <+> pretty (failureThreshold c),+ prettyPrintRulelist "error" (errorRules c),+ prettyPrintRulelist "warning" (warningRules c),+ prettyPrintRulelist "info" (infoRules c),+ prettyPrintRulelist "style" (styleRules c),+ prettyPrintRulelist "ignore" (ignoreRules c),+ "strict labels:" <+> pretty (strictLabels c),+ prettyPrintLabelSchema (labelSchema c),+ prettyPrintRegistries (allowedRegistries c)+ ]+ )++prettyPrintRulelist :: String -> [RuleCode] -> Doc ann+prettyPrintRulelist name lst =+ nest 2 (("override" <+> pretty name <> ":\n") <> prettyPrintList pretty lst)++-- | This function needs to convert the set to a list because Doc ann is not+-- ordered.+prettyPrintRegistries :: Set.Set Registry -> Doc ann+prettyPrintRegistries regs =+ nest 2 ( "allowed registries:\n"+ <> prettyPrintList+ (\r -> "-" <+> pretty (unRegistry r))+ (Set.toList regs)+ )++prettyPrintLabelSchema :: LabelSchema -> Doc ann+prettyPrintLabelSchema ls =+ nest 2 ( "label schema:\n"+ <> prettyPrintList+ (\(n, t) -> pretty n <> ":" <+> pretty t)+ (Map.toList ls)+ )++-- | pretty print a list with a custom pretty printing function for each element+prettyPrintList :: (a -> Doc ann) -> [a] -> Doc ann+prettyPrintList _ [] = "none"+prettyPrintList prnt lst = vsep (fmap (\i -> "-" <+> prnt i) lst)+++data PartialConfiguration =+ PartialConfiguration+ { partialNoFail :: Maybe Bool,+ partialNoColor :: Maybe Bool,+ partialVerbose :: Maybe Bool,+ partialFormat :: Maybe OutputFormat,+ partialErrorRules :: [RuleCode],+ partialWarningRules :: [RuleCode],+ partialInfoRules :: [RuleCode],+ partialStyleRules :: [RuleCode],+ partialIgnoreRules :: [RuleCode],+ partialAllowedRegistries :: Set.Set Registry,+ partialLabelSchema :: LabelSchema,+ partialStrictLabels :: Maybe Bool,+ partialFailureThreshold :: Maybe DLSeverity+ }+ deriving (Eq, Show)+++instance Semigroup PartialConfiguration where+ PartialConfiguration a1 a2 a3 a4 a5 a6 a7 a8 a9 a10 a11 a12 a13+ <> PartialConfiguration b1 b2 b3 b4 b5 b6 b7 b8 b9 b10 b11 b12 b13 =+ PartialConfiguration+ (b1 <|> a1)+ (b2 <|> a2)+ (b3 <|> a3)+ (a4 <> b4)+ (a5 <> b5)+ (a6 <> b6)+ (a7 <> b7)+ (a8 <> b8)+ (a9 <> b9)+ (a10 <> b10)+ (a11 <> b11)+ (b12 <|> a12)+ (a13 <> b13)++instance Monoid PartialConfiguration where+ mempty =+ PartialConfiguration+ Nothing+ Nothing+ Nothing+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ Nothing+ mempty++instance Default PartialConfiguration where+ def = mempty++instance Yaml.FromYAML PartialConfiguration where+ parseYAML = Yaml.withMap "Configuration" $ \m -> do+ partialNoFail <- m .:? "no-fail" .!= Nothing+ partialNoColor <- m .:? "no-color" .!= Nothing+ partialVerbose <- m .:? "verbose" .!= Nothing+ partialFormat <- m .:? "output-format"+ override <- m .:? "override" .!= mempty+ ignored <- m .:? "ignored" .!= mempty+ trusted <- m .:? "trusted-registries" .!= mempty+ partialLabelSchema <- m .:? "label-schema" .!= mempty+ partialStrictLabels <- m .:? "strict-labels" .!= Nothing+ let partialIgnoreRules = coerce (ignored :: [Text])+ partialErrorRules = overrideErrorRules override+ partialWarningRules = overrideWarningRules override+ partialInfoRules = overrideInfoRules override+ partialStyleRules = overrideStyleRules override+ partialAllowedRegistries = Set.fromList (coerce (trusted :: [Text]))+ partialFailureThreshold <- m .:? "failure-threshold"+ return PartialConfiguration {..}+++data OverrideConfig = OverrideConfig+ { overrideErrorRules :: [RuleCode],+ overrideWarningRules :: [RuleCode],+ overrideInfoRules :: [RuleCode],+ overrideStyleRules :: [RuleCode]+ }+ deriving (Show, Eq, Generic)++instance Semigroup OverrideConfig where+ OverrideConfig a1 a2 a3 a4 <> OverrideConfig b1 b2 b3 b4 =+ OverrideConfig (a1 <> b1) (a2 <> b2) (a3 <> b3) (a4 <> b4)++instance Monoid OverrideConfig where+ mempty = OverrideConfig mempty mempty mempty mempty++instance Yaml.FromYAML OverrideConfig where+ parseYAML = Yaml.withMap "OverrideConfig" $ \m -> do+ err <- m .:? "error" .!= mempty+ wrn <- m .:? "warning" .!= mempty+ inf <- m .:? "info" .!= mempty+ sty <- m .:? "style" .!= mempty+ let overrideErrorRules = coerce (err :: [Text])+ overrideWarningRules = coerce (wrn :: [Text])+ overrideInfoRules = coerce (inf :: [Text])+ overrideStyleRules = coerce (sty:: [Text])+ return OverrideConfig {..}
+ src/Hadolint/Config/Environment.hs view
@@ -0,0 +1,103 @@+module Hadolint.Config.Environment+ ( getConfigFromEnvironment+ )+where++import Data.Char (toLower)+import Data.Coerce (coerce)+import Data.Map (empty, fromList)+import Data.Set (Set, empty, fromList)+import Data.Text (Text, pack, unpack, drop, splitOn, breakOn)+import Hadolint.Formatter.Format (OutputFormat (..), readMaybeOutputFormat)+import Hadolint.Config.Configuration+import Hadolint.Rule+import Language.Docker.Syntax+import System.Environment+++getConfigFromEnvironment :: IO PartialConfiguration+getConfigFromEnvironment =+ PartialConfiguration+ <$> maybeTruthy "HADOLINT_NOFAIL"+ <*> maybeTruthy "NO_COLOR"+ <*> maybeTruthy "HADOLINT_VERBOSE"+ <*> getFormat+ <*> getOverrideList "HADOLINT_OVERRIDE_ERROR"+ <*> getOverrideList "HADOLINT_OVERRIDE_WARNING"+ <*> getOverrideList "HADOLINT_OVERRIDE_INFO"+ <*> getOverrideList "HADOLINT_OVERRIDE_STYLE"+ <*> getOverrideList "HADOLINT_IGNORE"+ <*> getAllowedSet "HADOLINT_ALLOWED_REGISTRIES"+ <*> getLabelSchema "HADOLINT_REQUIRE_LABELS"+ <*> maybeTruthy "HADOLINT_STRICT_LABELS"+ <*> getFailureThreshold+++maybeTruthy :: String -> IO (Maybe Bool)+maybeTruthy name = do+ e <- lookupEnv name+ case e of+ Just v ->+ if truthy v+ then return $ Just True+ else return $ Just False+ Nothing -> return Nothing++truthy :: String -> Bool+truthy s = map toLower s `elem` ["1", "y", "on", "true", "yes"]++getFormat :: IO (Maybe OutputFormat)+getFormat = do+ fmt <- lookupEnv "HADOLINT_FORMAT"+ return $ (readMaybeOutputFormat . pack) =<< fmt++getOverrideList :: String -> IO [RuleCode]+getOverrideList env = do+ maybeString <- lookupEnv env+ case maybeString of+ Just s -> return $ getRulecodes (pack s)+ Nothing -> return []++getRulecodes :: Text -> [RuleCode]+getRulecodes s = do+ list <- splitOn "," s+ let rules = coerce (list :: Text)+ return rules++getAllowedSet :: String -> IO (Set Registry)+getAllowedSet env = do+ maybeString <- lookupEnv env+ case maybeString of+ Just s -> return $ Data.Set.fromList (getAllowed (pack s))+ Nothing -> return Data.Set.empty++getAllowed :: Text -> [Registry]+getAllowed s = do+ list <- splitOn "," s+ let regs = coerce (list :: Text)+ return regs++getLabelSchema :: String -> IO LabelSchema+getLabelSchema env = do+ maybeString <- lookupEnv env+ case maybeString of+ Just s -> return $ Data.Map.fromList (labelSchemaFromText (pack s))+ Nothing -> return Data.Map.empty++labelSchemaFromText :: Text -> [(LabelName, LabelType)]+labelSchemaFromText txt =+ [ (ln, lt) | Right (ln, lt) <- map convertToLabelSchema (convertToPairs txt) ]++convertToPairs :: Text -> [(Text, Text)]+convertToPairs txt = map (breakOn ":") (splitOn "," txt)++convertToLabelSchema :: (Text, Text) -> Either String (LabelName, LabelType)+convertToLabelSchema (tln, tlt) =+ case (readEitherLabelType . Data.Text.drop 1) tlt of+ Right lt -> Right (coerce tln :: Text, lt)+ Left e -> Left (unpack e)++getFailureThreshold :: IO (Maybe DLSeverity)+getFailureThreshold = do+ ft <- lookupEnv "HADOLINT_FAILURE_THRESHOLD"+ return $ (readMaybeSeverity . pack) =<< ft
+ src/Hadolint/Formatter.hs view
@@ -0,0 +1,37 @@+module Hadolint.Formatter+ ( OutputFormat (..),+ Result (..),+ printResults,+ readMaybeOutputFormat,+ )+where++import Data.Text (Text)+import Hadolint.Formatter.Format+import Language.Docker.Parser (DockerfileError)+import qualified Hadolint.Formatter.Checkstyle as FormatCheckstyle+import qualified Hadolint.Formatter.Codacy as FormatCodacy+import qualified Hadolint.Formatter.Codeclimate as FormatCodeclimate+import qualified Hadolint.Formatter.Json as FormatJson+import qualified Hadolint.Formatter.Sarif as FormatSarif+import qualified Hadolint.Formatter.SonarQube as FormatSonarQube+import qualified Hadolint.Formatter.TTY as FormatTTY+++printResults ::+ Foldable f =>+ OutputFormat ->+ Bool ->+ Maybe FilePath ->+ f (Result Text DockerfileError) ->+ IO ()+printResults format nocolor filePathInReport allResults =+ case format of+ Checkstyle -> FormatCheckstyle.printResults allResults filePathInReport+ Codacy -> FormatCodacy.printResults allResults+ CodeclimateJson -> FormatCodeclimate.printResults allResults+ GitlabCodeclimateJson -> FormatCodeclimate.printGitlabResults allResults+ Json -> FormatJson.printResults allResults+ Sarif -> FormatSarif.printResults allResults+ SonarQube -> FormatSonarQube.printResults allResults+ TTY -> FormatTTY.printResults allResults nocolor
src/Hadolint/Formatter/Format.hs view
@@ -1,25 +1,86 @@ module Hadolint.Formatter.Format- ( severityText,- stripNewlines,+ ( OutputFormat (..),+ Result (..),+ Text.Megaparsec.Error.errorBundlePretty, errorMessage, errorMessageLine, errorPosition, errorPositionPretty,- Text.Megaparsec.Error.errorBundlePretty,- Result (..),+ severityText,+ stripNewlines,+ readMaybeOutputFormat, toResult, ) where -import qualified Data.List.NonEmpty as NE+import Data.Default import Data.Sequence (Seq)-import qualified Data.Sequence as Seq-import qualified Data.Text as Text-import qualified Hadolint.Rule+import Data.Text (Text)+import Data.Text.Prettyprint.Doc (Pretty, pretty)+import Data.YAML import Text.Megaparsec (TraversableStream (..), pstateSourcePos) import Text.Megaparsec.Error import Text.Megaparsec.Pos (SourcePos, sourcePosPretty) import Text.Megaparsec.Stream (VisualStream)+import qualified Data.List.NonEmpty as NE+import qualified Data.Sequence as Seq+import qualified Data.Text as Text+import qualified Hadolint.Rule++data OutputFormat+ = Json+ | SonarQube+ | TTY+ | CodeclimateJson+ | GitlabCodeclimateJson+ | Checkstyle+ | Codacy+ | Sarif+ deriving (Eq, Show)++instance Pretty OutputFormat where+ pretty Json = "json"+ pretty SonarQube = "sonarqube"+ pretty TTY = "tty"+ pretty CodeclimateJson = "codeclimate"+ pretty GitlabCodeclimateJson = "gitlab_codeclimate"+ pretty Checkstyle = "checkstyle"+ pretty Codacy = "codacy"+ pretty Sarif = "sarif"++instance Semigroup OutputFormat where+ _ <> f = f++instance Monoid OutputFormat where+ mempty = TTY++instance FromYAML OutputFormat where+ parseYAML = withOutputFormat pure++withOutputFormat ::+ (OutputFormat -> Parser a) ->+ Node Pos ->+ Parser a+withOutputFormat f v@(Scalar _ (SStr b)) =+ case readMaybeOutputFormat b of+ Just out -> f out+ Nothing -> typeMismatch "output format" v+withOutputFormat _ v = typeMismatch "output format" v++instance Default OutputFormat where+ def = TTY++readMaybeOutputFormat :: Text -> Maybe OutputFormat+readMaybeOutputFormat "json" = Just Json+readMaybeOutputFormat "sonarqube" = Just SonarQube+readMaybeOutputFormat "tty" = Just TTY+readMaybeOutputFormat "codeclimate" = Just CodeclimateJson+readMaybeOutputFormat "gitlab_codeclimate" = Just GitlabCodeclimateJson+readMaybeOutputFormat "checkstyle" = Just Checkstyle+readMaybeOutputFormat "codacy" = Just Codacy+readMaybeOutputFormat "sarif" = Just Sarif+readMaybeOutputFormat _ = Nothing+ data Result s e = Result { fileName :: Text.Text,
+ src/Hadolint/Formatter/Sarif.hs view
@@ -0,0 +1,165 @@+module Hadolint.Formatter.Sarif+ ( printResults,+ formatResult,+ )+where++import qualified Control.Foldl as Foldl+import Data.Aeson hiding (Result)+import qualified Data.ByteString.Lazy.Char8 as B+import Data.Sequence as Seq+import qualified Data.Text as Text+import Hadolint.Formatter.Format+ ( Result (..),+ errorMessage,+ errorPosition,+ )+import Hadolint.Meta+ ( getShortVersion,+ )+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 SarifFormat s e+ = SarifCheck Text.Text CheckFailure+ | SarifError (ParseErrorBundle s e)++instance+ ( VisualStream s,+ TraversableStream s,+ ShowErrorComponent e+ ) =>+ ToJSON (SarifFormat s e)+ where+ toJSON (SarifCheck filename CheckFailure {..}) =+ object+ [ "ruleId" .= unRuleCode code,+ "level" .= toSeverity severity,+ "message"+ .= object+ [ "text" .= message+ ],+ "locations"+ .= [ object+ [ "physicalLocation"+ .= object+ [ "artifactLocation"+ .= object+ [ "uri" .= filename+ ],+ "region"+ .= object+ [ "startLine" .= line,+ "endLine" .= line,+ "startColumn" .= (1 :: Int),+ "endColumn" .= (1 :: Int),+ "sourceLanguage" .= Text.pack language+ ]+ ]+ ]+ ]+ ]+ where+ language = if "DL" `Text.isPrefixOf` unRuleCode code+ then "dockerfile"+ else "sh"+ toJSON (SarifError err) =+ object+ [ "ruleId" .= Text.pack "DL1000",+ "level" .= Text.pack "error",+ "message"+ .= object+ [ "text" .= errorMessage err+ ],+ "locations"+ .= [ object+ [ "physicalLocation"+ .= object+ [ "artifactLocation"+ .= object+ [ "uri" .= Text.pack (sourceName pos)+ ],+ "region"+ .= object+ [ "startLine" .= linenumber,+ "endLine" .= linenumber,+ "startColumn" .= column,+ "endColumn" .= column,+ "sourceLanguage" .= Text.pack "dockerfile"+ ]+ ]+ ]+ ]+ ]+ where+ pos = errorPosition err+ linenumber = unPos $ sourceLine pos+ column = unPos $ sourceColumn pos++formatResult :: Result s e -> Seq (SarifFormat s e)+formatResult (Result filename errors checks) = allMessages+ where+ allMessages = errorMessages <> checkMessages+ checkMessages = fmap (SarifCheck filename) checks+ errorMessages = fmap SarifError errors++printResults ::+ ( VisualStream s,+ TraversableStream s,+ ShowErrorComponent e,+ Foldable f+ ) =>+ f (Result s e) ->+ IO ()+printResults results =+ B.putStr . encode $+ object+ [ ("version", "2.1.0"),+ "$schema"+ .= Text.pack "http://json.schemastore.org/sarif-2.1.0",+ "runs"+ .= [ object+ [ "tool"+ .= object+ [ "driver"+ .= object+ [ ("name", "Hadolint"),+ ("fullName", "Haskell Dockerfile Linter"),+ ("downloadUri",+ "https://github.com/hadolint/hadolint"),+ "version"+ .= Text.pack Hadolint.Meta.getShortVersion,+ "shortDescription"+ .= object+ [ ("text",+ "Dockerfile linter, validate inline bash, written in Haskell")+ ]+ ]+ ],+ "results" .= flattened,+ "defaultSourceLanguage" .= Text.pack "dockerfile"+ ]+ ]+ ]+ where+ flattened = Foldl.fold (Foldl.premap formatResult Foldl.mconcat) results++-- | SARIF only specifies three severities "error", "warning" and "note"+-- We pack our "info" and "style" severities together into the "note" severity+-- here.+toSeverity :: DLSeverity -> Text.Text+toSeverity DLErrorC = "error"+toSeverity DLWarningC = "warning"+toSeverity _ = "note"
− src/Hadolint/Ignore.hs
@@ -1,40 +0,0 @@-module Hadolint.Ignore (ignored) where--import qualified Control.Foldl as Foldl-import qualified Data.IntMap.Strict as Map-import qualified Data.Set as Set-import qualified Data.Text as Text-import Data.Void (Void)-import Hadolint.Rule (RuleCode (RuleCode))-import Language.Docker.Syntax-import qualified Text.Megaparsec as Megaparsec-import qualified Text.Megaparsec.Char as Megaparsec--ignored :: Foldl.Fold (InstructionPos Text.Text) (Map.IntMap (Set.Set RuleCode))-ignored = Foldl.Fold parse mempty id- where- parse acc InstructionPos {instruction = Comment comment, lineNumber = line} =- case parseComment comment of- Just ignores@(_ : _) -> Map.insert (line + 1) (Set.fromList . fmap RuleCode $ ignores) acc- _ -> acc- parse acc _ = acc-- parseComment :: Text.Text -> Maybe [Text.Text]- parseComment =- Megaparsec.parseMaybe commentParser-- commentParser :: Megaparsec.Parsec Void Text.Text [Text.Text]- commentParser =- do- spaces- >> string "hadolint"- >> spaces1- >> string "ignore="- >> spaces- >> Megaparsec.sepBy1 ruleName (spaces >> string "," >> spaces)-- ruleName = Megaparsec.takeWhile1P Nothing (\c -> c `elem` Set.fromList "DLSC0123456789")- string = Megaparsec.string- spaces = Megaparsec.takeWhileP Nothing space- spaces1 = Megaparsec.takeWhile1P Nothing space- space c = c == ' ' || c == '\t'
src/Hadolint/Lint.hs view
@@ -2,71 +2,37 @@ ( lintIO, lint, analyze,- LintOptions (..),- ErrorRule,- WarningRule,- InfoRule,- StyleRule,- IgnoreRule, TrustedRegistry, ) where +import Data.Sequence (Seq)+import Data.Text (Text)+import Hadolint.Config.Configuration (Configuration (..))+import Hadolint.Rule (RuleCode, DLSeverity (..), CheckFailure (..))+import Language.Docker.Parser (DockerfileError, Error)+import Language.Docker.Syntax (Dockerfile) import qualified Control.Parallel.Strategies as Parallel import qualified Data.List.NonEmpty as NonEmpty import qualified Data.Sequence as Seq-import Data.Text (Text) import qualified Data.Text as Text import qualified Hadolint.Formatter.Format as Format import qualified Hadolint.Process-import qualified Hadolint.Rule import qualified Language.Docker as Docker-import Language.Docker.Parser (DockerfileError, Error)-import Language.Docker.Syntax (Dockerfile) -type ErrorRule = Hadolint.Rule.RuleCode -type WarningRule = Hadolint.Rule.RuleCode--type InfoRule = Hadolint.Rule.RuleCode--type StyleRule = Hadolint.Rule.RuleCode--type IgnoreRule = Hadolint.Rule.RuleCode- type TrustedRegistry = Text -data LintOptions = LintOptions- { errorRules :: [ErrorRule],- warningRules :: [WarningRule],- infoRules :: [InfoRule],- styleRules :: [StyleRule],- ignoreRules :: [IgnoreRule],- rulesConfig :: Hadolint.Process.RulesConfig,- failThreshold :: Hadolint.Rule.DLSeverity- }- deriving (Show) -instance Semigroup LintOptions where- LintOptions a1 a2 a3 a4 a5 a6 a7 <> LintOptions b1 b2 b3 b4 b5 b6 b7 =- LintOptions- (a1 <> b1)- (a2 <> b2)- (a3 <> b3)- (a4 <> b4)- (a5 <> b5)- (a6 <> b6)- (a7 <> b7)--instance Monoid LintOptions where- mempty = LintOptions mempty mempty mempty mempty mempty mempty mempty---- | Performs the process of parsing the dockerfile and analyzing it with all the applicable--- rules, depending on the list of ignored rules.-lintIO :: LintOptions -> NonEmpty.NonEmpty FilePath -> IO (NonEmpty.NonEmpty (Format.Result Text DockerfileError))-lintIO options dFiles = do+-- | Performs the process of parsing the dockerfile and analyzing it with all+-- the applicable rules, depending on the list of ignored rules.+lintIO ::+ Configuration ->+ NonEmpty.NonEmpty FilePath ->+ IO (NonEmpty.NonEmpty (Format.Result Text DockerfileError))+lintIO config dFiles = do parsedFiles <- mapM parseFile (NonEmpty.toList dFiles)- return $ NonEmpty.fromList (lint options parsedFiles)+ return $ NonEmpty.fromList (lint config parsedFiles) where parseFile :: String -> IO (Text, Either Error Dockerfile) parseFile "-" = do@@ -77,42 +43,46 @@ return (Text.pack s, res) lint ::- LintOptions ->+ Configuration -> [(Text, Either Error Dockerfile)] -> [Format.Result Text DockerfileError]-lint options parsedFiles = gather results `Parallel.using` parallelRun+lint config parsedFiles = gather results `Parallel.using` parallelRun where gather = fmap (uncurry Format.toResult) results = [ ( name,- fmap (analyze options) parseResult+ fmap (analyze config) parseResult ) | (name, parseResult) <- parsedFiles ] parallelRun = Parallel.parList Parallel.rseq -analyze :: LintOptions -> Dockerfile -> Seq.Seq Hadolint.Rule.CheckFailure-analyze options dockerfile = fixer process+analyze :: Configuration -> Dockerfile -> Seq Hadolint.Rule.CheckFailure+analyze config dockerfile = fixer process where- fixer = fixSeverity options- process = Hadolint.Process.run (rulesConfig options) dockerfile+ fixer = fixSeverity config+ process = Hadolint.Process.run config dockerfile -fixSeverity :: LintOptions -> Seq.Seq Hadolint.Rule.CheckFailure -> Seq.Seq Hadolint.Rule.CheckFailure-fixSeverity LintOptions {..} = Seq.filter ignoredRules . Seq.mapWithIndex (const correctSeverity)+fixSeverity ::+ Configuration ->+ Seq CheckFailure ->+ Seq CheckFailure+fixSeverity Configuration {..} =+ Seq.filter ignoredRules . Seq.mapWithIndex (const correctSeverity) where correctSeverity =- makeSeverity Hadolint.Rule.DLErrorC errorRules- . makeSeverity Hadolint.Rule.DLWarningC warningRules- . makeSeverity Hadolint.Rule.DLInfoC infoRules- . makeSeverity Hadolint.Rule.DLStyleC styleRules+ makeSeverity DLErrorC errorRules+ . makeSeverity DLWarningC warningRules+ . makeSeverity DLInfoC infoRules+ . makeSeverity DLStyleC styleRules ignoredRules = ignoreFilter ignoreRules - makeSeverity s rules rule@Hadolint.Rule.CheckFailure {code} =+ makeSeverity s rules rule@CheckFailure {code} = if code `elem` rules- then rule {Hadolint.Rule.severity = s}+ then rule {severity = s} else rule - ignoreFilter :: [IgnoreRule] -> Hadolint.Rule.CheckFailure -> Bool- ignoreFilter ignored Hadolint.Rule.CheckFailure {code, severity} =- code `notElem` ignored && severity /= Hadolint.Rule.DLIgnoreC+ ignoreFilter :: [RuleCode] -> CheckFailure -> Bool+ ignoreFilter ignored CheckFailure {code, severity} =+ code `notElem` ignored && severity /= DLIgnoreC
+ src/Hadolint/Meta.hs view
@@ -0,0 +1,21 @@+module Hadolint.Meta+ ( getVersion,+ getShortVersion,+ )+where++import qualified Development.GitRev+++getVersion :: String+getVersion = "Haskell Dockerfile Linter " ++ getShortVersion++getShortVersion :: String+getShortVersion = v ++ d+ where+ version = $(Development.GitRev.gitDescribe)+ dirty = $(Development.GitRev.gitDirty)+ v = case version of+ "UNKONWN" -> "-no-git"+ _ -> version+ d = if dirty then "-dirty" else ""
+ src/Hadolint/Pragma.hs view
@@ -0,0 +1,76 @@+module Hadolint.Pragma+ ( ignored,+ parseShell+ )+ where++import Data.Functor.Identity (Identity)+import Data.Text (Text)+import Data.Void (Void)+import Hadolint.Rule (RuleCode (RuleCode))+import Language.Docker.Syntax+import qualified Control.Foldl as Foldl+import qualified Data.IntMap.Strict as Map+import qualified Data.Set as Set+import qualified Text.Megaparsec as Megaparsec+import qualified Text.Megaparsec.Char as Megaparsec+++ignored :: Foldl.Fold (InstructionPos Text) (Map.IntMap (Set.Set RuleCode))+ignored = Foldl.Fold parse mempty id+ where+ parse acc InstructionPos {instruction = Comment comment, lineNumber = line} =+ case parseComment comment of+ Just ignores@(_ : _) -> Map.insert (line + 1) (Set.fromList . fmap RuleCode $ ignores) acc+ _ -> acc+ parse acc _ = acc++parseComment :: Text -> Maybe [Text]+parseComment =+ Megaparsec.parseMaybe commentParser++commentParser :: Megaparsec.Parsec Void Text [Text]+commentParser =+ do+ spaces+ >> string "hadolint"+ >> spaces1+ >> string "ignore="+ >> spaces+ >> Megaparsec.sepBy1 ruleName (spaces >> string "," >> spaces)++ruleName :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)+ruleName = Megaparsec.takeWhile1P Nothing (\c -> c `elem` Set.fromList "DLSC0123456789")+++parseShell :: Text -> Maybe Text+parseShell = Megaparsec.parseMaybe shellParser++shellParser :: Megaparsec.Parsec Void Text Text+shellParser =+ do+ spaces+ >> string "hadolint"+ >> spaces1+ >> string "shell"+ >> spaces+ >> string "="+ >> spaces+ >> shellName++shellName :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)+shellName = Megaparsec.takeWhile1P Nothing (/= '\n')+++string :: Megaparsec.Tokens Text+ -> Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)+string = Megaparsec.string++spaces :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)+spaces = Megaparsec.takeWhileP Nothing space++spaces1 :: Megaparsec.ParsecT Void Text Identity (Megaparsec.Tokens Text)+spaces1 = Megaparsec.takeWhile1P Nothing space++space :: Char -> Bool+space c = c == ' ' || c == '\t'
src/Hadolint/Process.hs view
@@ -1,13 +1,14 @@-module Hadolint.Process (run, RulesConfig (..)) where+module Hadolint.Process (run) where +import Hadolint.Config.Configuration (Configuration (..))+import Hadolint.Rule (CheckFailure (..), Failures, Rule, RuleCode)+import Language.Docker.Syntax import qualified Control.Foldl as Foldl import qualified Data.IntMap.Strict as SMap import qualified Data.Sequence as Seq import qualified Data.Set as Set import qualified Data.Text as Text-import qualified Hadolint.Ignore-import Hadolint.Rule (CheckFailure (..), Failures, Rule, RuleCode)-import qualified Hadolint.Rule as Rule+import qualified Hadolint.Pragma import qualified Hadolint.Rule.DL3000 import qualified Hadolint.Rule.DL3001 import qualified Hadolint.Rule.DL3002@@ -74,28 +75,8 @@ import qualified Hadolint.Rule.DL4006 import qualified Hadolint.Rule.Shellcheck import qualified Hadolint.Shell as Shell-import Language.Docker.Syntax --- | Contains the required parameters for optional rules-data RulesConfig = RulesConfig- { -- | The docker registries that are allowed in FROM- allowedRegistries :: Set.Set Registry,- labelSchema :: Rule.LabelSchema,- strictLabels :: Bool- }- deriving (Show, Eq)--instance Semigroup RulesConfig where- RulesConfig a1 a2 a3 <> RulesConfig b1 b2 b3 =- RulesConfig- (a1 <> b1)- (a2 <> b2)- (a3 || b3)--instance Monoid RulesConfig where- mempty = RulesConfig mempty mempty False- data AnalisisResult = AnalisisResult { -- | The set of ignored rules per line ignored :: SMap.IntMap (Set.Set RuleCode),@@ -103,7 +84,7 @@ failed :: Failures } -run :: RulesConfig -> [InstructionPos Text.Text] -> Failures+run :: Configuration -> [InstructionPos Text.Text] -> Failures run config dockerfile = Seq.filter shouldKeep failed where AnalisisResult {..} = Foldl.fold (analyze config) dockerfile@@ -113,16 +94,18 @@ ignoreList <- SMap.lookup line ignored return $ code `Set.member` ignoreList -analyze :: RulesConfig -> Foldl.Fold (InstructionPos Text.Text) AnalisisResult+analyze ::+ Configuration ->+ Foldl.Fold (InstructionPos Text.Text) AnalisisResult analyze config = AnalisisResult- <$> Hadolint.Ignore.ignored+ <$> Hadolint.Pragma.ignored <*> Foldl.premap parseShell (failures config <> onBuildFailures config) parseShell :: InstructionPos Text.Text -> InstructionPos Shell.ParsedShell parseShell = fmap Shell.parseShell -onBuildFailures :: RulesConfig -> Rule Shell.ParsedShell+onBuildFailures :: Configuration -> Rule Shell.ParsedShell onBuildFailures config = Foldl.prefilter isOnBuild@@ -134,8 +117,8 @@ unwrapOnbuild inst@InstructionPos {instruction = OnBuild i} = inst {instruction = i} unwrapOnbuild inst = inst -failures :: RulesConfig -> Rule Shell.ParsedShell-failures RulesConfig {allowedRegistries, labelSchema, strictLabels} =+failures :: Configuration -> Rule Shell.ParsedShell+failures Configuration {allowedRegistries, labelSchema, strictLabels} = Hadolint.Rule.DL3000.rule <> Hadolint.Rule.DL3001.rule <> Hadolint.Rule.DL3002.rule
src/Hadolint/Rule.hs view
@@ -1,14 +1,17 @@ module Hadolint.Rule where import Control.DeepSeq (NFData)+import Data.Default+import Data.String (IsString (..))+import Data.Text (Text, unpack)+import GHC.Generics (Generic)+import Language.Docker.Syntax+import Prettyprinter (Pretty, pretty) import qualified Control.Foldl as Foldl import qualified Data.Map as Map import qualified Data.Sequence as Seq-import Data.String (IsString (..)) import qualified Data.Text as Text import qualified Data.YAML as Yaml-import GHC.Generics (Generic)-import Language.Docker.Syntax infixl 0 |> @@ -21,37 +24,67 @@ | DLInfoC | DLStyleC | DLIgnoreC- deriving (Show, Read, Eq, Ord, Generic, NFData)+ deriving (Eq, Ord, Show, Generic, NFData) instance Yaml.FromYAML DLSeverity where parseYAML = withSeverity pure -withSeverity :: (DLSeverity -> Yaml.Parser a) -> Yaml.Node Yaml.Pos -> Yaml.Parser a+withSeverity ::+ (DLSeverity -> Yaml.Parser a) ->+ Yaml.Node Yaml.Pos ->+ Yaml.Parser a withSeverity f v@(Yaml.Scalar _ (Yaml.SStr b)) =- case Hadolint.Rule.readSeverity b of+ case readEitherSeverity b of Right s -> f s Left _ -> Yaml.typeMismatch "severity" v withSeverity _ v = Yaml.typeMismatch "severity" v -readSeverity :: Text.Text -> Either Text.Text DLSeverity-readSeverity "error" = Right DLErrorC-readSeverity "warning" = Right DLWarningC-readSeverity "info" = Right DLInfoC-readSeverity "style" = Right DLStyleC-readSeverity "ignore" = Right DLIgnoreC-readSeverity "none" = Right DLIgnoreC-readSeverity t = Left ("Invalid severity: " <> t)+readEitherSeverity :: Text -> Either String DLSeverity+readEitherSeverity "error" = Right DLErrorC+readEitherSeverity "warning" = Right DLWarningC+readEitherSeverity "info" = Right DLInfoC+readEitherSeverity "style" = Right DLStyleC+readEitherSeverity "ignore" = Right DLIgnoreC+readEitherSeverity "none" = Right DLIgnoreC+readEitherSeverity t = Left ("Invalid severity: " ++ unpack t) -instance Semigroup DLSeverity where s1 <> s2 = min s1 s2+readMaybeSeverity :: Text -> Maybe DLSeverity+readMaybeSeverity "error" = Just DLErrorC+readMaybeSeverity "warning" = Just DLWarningC+readMaybeSeverity "info" = Just DLInfoC+readMaybeSeverity "style" = Just DLStyleC+readMaybeSeverity "ignore" = Just DLIgnoreC+readMaybeSeverity "none" = Just DLIgnoreC+readMaybeSeverity _ = Nothing +instance Semigroup DLSeverity where _ <> s2 = s2+ instance Monoid DLSeverity where mempty = DLIgnoreC -newtype RuleCode = RuleCode {unRuleCode :: Text.Text}- deriving (Show, Eq, Ord)+instance Default DLSeverity where+ def = DLInfoC +instance Pretty DLSeverity where+ pretty DLErrorC = "error"+ pretty DLWarningC = "warning"+ pretty DLInfoC = "info"+ pretty DLStyleC = "style"+ pretty DLIgnoreC = "ignore"+++newtype RuleCode = RuleCode {unRuleCode :: Text}+ deriving (Eq, Ord)++instance Show RuleCode where+ show rc = show (unRuleCode rc)+ instance IsString RuleCode where fromString = RuleCode . Text.pack +instance Pretty RuleCode where+ pretty rc = pretty $ show rc++ data CheckFailure = CheckFailure { code :: RuleCode, severity :: DLSeverity,@@ -63,47 +96,59 @@ instance Ord CheckFailure where a `compare` b = line a `compare` line b + type Failures = Seq.Seq CheckFailure + data State a = State { failures :: Failures, state :: a } deriving (Show) + type LabelName = Text.Text data LabelType- = RawText- | Url- | Spdx+ = Email | GitHash+ | RawText | Rfc3339 | SemVer- | Email- deriving (Eq, Read, Show)+ | Spdx+ | Url+ deriving (Eq, Show) -read :: Text.Text -> Either Text.Text LabelType-read "url" = Right Url-read "spdx" = Right Spdx-read "hash" = Right GitHash-read "rfc3339" = Right Rfc3339-read "semver" = Right SemVer-read "email" = Right Email-read "text" = Right RawText-read "" = Right RawText-read t = Left ("Invalid label type: " <> t)+readEitherLabelType :: Text -> Either Text LabelType+readEitherLabelType "email" = Right Email+readEitherLabelType "hash" = Right GitHash+readEitherLabelType "text" = Right RawText+readEitherLabelType "rfc3339" = Right Rfc3339+readEitherLabelType "semver" = Right SemVer+readEitherLabelType "spdx" = Right Spdx+readEitherLabelType "url" = Right Url+readEitherLabelType "" = Right RawText+readEitherLabelType t = Left ("invalid label type: " <> t) instance Yaml.FromYAML LabelType where parseYAML = withLabelType pure withLabelType :: (LabelType -> Yaml.Parser a) -> Yaml.Node Yaml.Pos -> Yaml.Parser a withLabelType f v@(Yaml.Scalar _ (Yaml.SStr b)) =- case Hadolint.Rule.read b of+ case readEitherLabelType b of Right lt -> f lt Left _ -> Yaml.typeMismatch "labeltype" v withLabelType _ v = Yaml.typeMismatch "labeltype" v +instance Pretty LabelType where+ pretty RawText = "text"+ pretty Url = "url"+ pretty Spdx = "spdx"+ pretty GitHash = "hash"+ pretty Rfc3339 = "rfc3339"+ pretty SemVer = "semver"+ pretty Email = "email"+ type LabelSchema = Map.Map LabelName LabelType @@ -214,3 +259,10 @@ ".txz", ".xz" ]++dropQuotes :: Text -> Text+dropQuotes = Text.dropAround quotes+ where+ quotes '\"' = True+ quotes '\'' = True+ quotes _ = False
src/Hadolint/Rule/DL3000.hs view
@@ -12,18 +12,12 @@ severity = DLErrorC message = "Use absolute WORKDIR" check (Workdir loc)- | "$" `Text.isPrefixOf` Text.dropAround dropQuotes loc = True- | "/" `Text.isPrefixOf` Text.dropAround dropQuotes loc = True- | isWindowsAbsolute (Text.dropAround dropQuotes loc) = True+ | "$" `Text.isPrefixOf` dropQuotes loc = True+ | "/" `Text.isPrefixOf` dropQuotes loc = True+ | isWindowsAbsolute (dropQuotes loc) = True | otherwise = False check _ = True {-# INLINEABLE rule #-}--dropQuotes :: Char -> Bool-dropQuotes chr- | chr == '\"' = True- | chr == '\'' = True- | otherwise = False isWindowsAbsolute :: Text.Text -> Bool isWindowsAbsolute path
src/Hadolint/Rule/DL3009.hs view
@@ -1,20 +1,24 @@ module Hadolint.Rule.DL3009 (rule) where +import Hadolint.Rule+import Language.Docker.Syntax import qualified Data.Map.Strict as Map+import qualified Data.Set as Set import qualified Data.Text as Text-import Hadolint.Rule import qualified Hadolint.Shell as Shell-import Language.Docker.Syntax + data Acc = Acc { lastFrom :: BaseImage,+ dockerClean :: Bool, stages :: Map.Map Text.Text Linenumber, forgets :: Map.Map Linenumber BaseImage } | Empty- deriving (Show)+ deriving (Eq, Show) + rule :: Rule Shell.ParsedShell rule = veryCustomRule check (emptyState Empty) markFailures where@@ -24,9 +28,14 @@ check line st (From from) = st |> modify (rememberStage line from) check line st (Run (RunArgs args flags))- | hasNoCacheMount flags && foldArguments forgotToCleanup args =- st |> modify (rememberLine line)- | otherwise = st+ | not (foldArguments forgotToCleanup args) =+ if foldArguments disabledDockerClean args+ then st |> modify rememberDockerClean+ else st+ | hasCacheDirectory "/var/lib/apt/lists" flags = st+ | hasCacheDirectory "/var/lib/apt" flags+ && hasCacheDirectory "/var/cache/apt" flags = st+ | otherwise = st |> modify (rememberLine line) check _ st _ = st -- Convert the final state into failures@@ -43,29 +52,53 @@ {-# INLINEABLE rule #-} rememberStage :: Linenumber -> BaseImage -> Acc -> Acc-rememberStage line from@BaseImage {image = Image _ als} (Acc _ stages o) = Acc from (Map.insert als line stages) o-rememberStage line from@BaseImage {image = Image _ als} Empty = Acc from (Map.singleton als line) Map.empty+rememberStage line from@BaseImage {image = Image _ als} (Acc _ _ stages o) = Acc from True (Map.insert als line stages) o+rememberStage line from@BaseImage {image = Image _ als} Empty = Acc from True (Map.singleton als line) Map.empty rememberLine :: Linenumber -> Acc -> Acc-rememberLine line (Acc from stages o) = Acc from stages (Map.insert line from o)-rememberLine line Empty = Acc emptyImage mempty (Map.singleton line emptyImage)+rememberLine line (Acc from clean stages o) = Acc from clean stages (Map.insert line from o)+rememberLine line Empty = Acc emptyImage True mempty (Map.singleton line emptyImage) +rememberDockerClean :: Acc -> Acc+rememberDockerClean Empty = Acc emptyImage False mempty mempty+rememberDockerClean (Acc from _ stages forget) = Acc from False stages forget+ forgotToCleanup :: Shell.ParsedShell -> Bool forgotToCleanup args- | hasUpdate && not hasCleanup = True+ | hasUpdate args && not hasCleanup = True | otherwise = False where hasCleanup = any (Shell.cmdHasArgs "rm" ["-rf", "/var/lib/apt/lists/*"]) (Shell.presentCommands args) - hasUpdate = any (Shell.cmdHasArgs "apt-get" ["update"]) (Shell.presentCommands args)+hasUpdate :: Shell.ParsedShell -> Bool+hasUpdate args =+ any (Shell.cmdHasArgs "apt-get" ["update"]) (Shell.presentCommands args) -hasNoCacheMount :: RunFlags -> Bool-hasNoCacheMount RunFlags- { mount =- Just (CacheMount CacheOpts {cTarget = TargetPath {unTargetPath = p}})- } = Text.dropWhileEnd (=='/') p /= "/var/lib/apt/lists"-hasNoCacheMount RunFlags {} = True+disabledDockerClean :: Shell.ParsedShell -> Bool+disabledDockerClean args+ | removesScript || keepsPackages = True+ | otherwise = False+ where+ removesScript =+ any+ (Shell.cmdHasArgs "rm" ["/etc/apt/apt.conf.d/docker-clean"])+ (Shell.presentCommands args)+ keepsPackages =+ any+ ( Shell.cmdHasArgs+ "echo"+ ["\'Binary::apt::APT::Keep-Downloaded-Packages \"true\";\'"]+ )+ (Shell.presentCommands args)++hasCacheDirectory :: Text.Text -> RunFlags -> Bool+hasCacheDirectory dir RunFlags { mount } =+ not ( null $ Set.filter (isCacheMount dir) mount)++isCacheMount :: Text.Text -> RunMount -> Bool+isCacheMount dir (CacheMount CacheOpts {cTarget = TargetPath {unTargetPath = t}}) = dir `Text.isPrefixOf` t+isCacheMount _ _ = False -- | Even though dockerfiles without a FROM are not valid, we still want to provide some feedback for this rule -- so we pretend there is a base image at the start of the file if there is none
src/Hadolint/Rule/DL3010.hs view
@@ -1,21 +1,138 @@ module Hadolint.Rule.DL3010 (rule) where import Data.Foldable (toList)+import Data.Function ((&))+import Data.List.NonEmpty (NonEmpty)+import qualified Data.Set as Set+import qualified Data.Sequence as Seq import qualified Data.Text as Text import Hadolint.Rule+import qualified Hadolint.Shell as Shell import Language.Docker.Syntax -rule :: Rule args-rule = simpleRule code severity message check++data Acc+ = Acc+ { archives :: Set.Set (Linenumber, Text.Text),+ extracted :: Set.Set (Linenumber, Text.Text)+ }+ | Empty++rule :: Rule Shell.ParsedShell+rule = veryCustomRule check (emptyState Empty) markFailures where code = "DL3010" severity = DLInfoC- message = "Use ADD for extracting archives into an image"- check (Copy (CopyArgs srcs _ _ _ NoSource)) =- and- [ not (format `Text.isSuffixOf` src)- | SourcePath src <- toList srcs,- format <- archiveFileFormatExtensions- ]- check _ = True+ message = "Use `ADD` for extracting archives into an image"++ check _ _ (From _) = emptyState Empty+ check line st (Copy (CopyArgs srcs tgt _ _ NoSource)) =+ st |> modify (rememberArchives line srcs tgt)+ check _ st (Run (RunArgs args _))+ | Acc archives _ <- state st,+ ex <- foldArguments (getExtractedArchives archives) args =+ st |> modify (markExtracted ex)+ | otherwise = st+ check _ st _ = st++ markFailures :: State Acc -> Failures+ markFailures (State fails (Acc _ e)) =+ Set.foldl' (Seq.|>) fails (Set.map makeFail e)+ markFailures st = failures st++ makeFail :: (Linenumber, Text.Text) -> CheckFailure+ makeFail (line, _) = CheckFailure {..} {-# INLINEABLE rule #-}+++extractsThisArchive :: (Linenumber, Text.Text) -> Shell.Command -> Bool+extractsThisArchive (_, archive) cmd =+ (isTarExtractCommand cmd || isUnzipCommand cmd) && archive `elem` arguments+ where+ arguments = map basename $ Shell.getArgsNoFlags cmd++getExtractedArchives ::+ Set.Set (Linenumber, Text.Text) ->+ Shell.ParsedShell ->+ Set.Set (Linenumber, Text.Text)+getExtractedArchives archives shell =+ Set.filter+ (\a -> any (extractsThisArchive a) cmds)+ archives+ where+ cmds = Shell.presentCommands shell++isTarExtractCommand :: Shell.Command -> Bool+isTarExtractCommand cmd@(Shell.Command name _ _) =+ name == "tar" && (any longExtractFlags args || any shortExtractFlags args)+ where+ longExtractFlags f = f `elem` ["--extract", "--get"]+ shortExtractFlags f = "-" `Text.isPrefixOf` f && "x" `Text.isInfixOf` f+ args = Shell.getArgs cmd++isUnzipCommand :: Shell.Command -> Bool+isUnzipCommand (Shell.Command name _ _) =+ name `elem`+ [ "unzip",+ "gunzip",+ "bunzip2",+ "unlzma",+ "unxz",+ "zgz",+ "uncompress",+ "zcat",+ "gzcat"+ ]++markExtracted :: Set.Set (Linenumber, Text.Text) -> Acc -> Acc+markExtracted _ Empty = Empty+markExtracted exarcv Acc {archives, extracted} =+ Acc { archives, extracted = Set.union exarcv extracted }++rememberArchives ::+ Linenumber ->+ NonEmpty SourcePath ->+ TargetPath ->+ Acc ->+ Acc+rememberArchives line paths target Empty =+ if isArchive $ unTargetPath target+ then Acc+ { archives = Set.singleton (line, basename $ unTargetPath target),+ extracted = Set.empty+ }+ else Acc+ { archives =+ paths+ & toList+ & map (basename . unSourcePath)+ & Set.fromList+ & Set.filter isArchive+ & Set.map (line,),+ extracted = Set.empty+ }+rememberArchives line paths target Acc {archives, extracted} =+ if isArchive $ unTargetPath target+ then Acc+ { archives =+ Set.insert (line, basename $ unTargetPath target) archives,+ extracted+ }+ else Acc+ { archives =+ paths+ & toList+ & map (basename . unSourcePath)+ & Set.fromList+ & Set.filter isArchive+ & Set.map (line,)+ & Set.union archives,+ extracted+ }++basename :: Text.Text -> Text.Text+basename = Text.takeWhileEnd (\c -> c /= '/' && c /= '\\') . dropQuotes++isArchive :: Text.Text -> Bool+isArchive src =+ any (`Text.isSuffixOf` dropQuotes src) archiveFileFormatExtensions
src/Hadolint/Rule/DL3019.hs view
@@ -1,10 +1,11 @@ module Hadolint.Rule.DL3019 (rule) where -import qualified Data.Text as Text import Hadolint.Rule import Hadolint.Shell (ParsedShell)-import qualified Hadolint.Shell as Shell import Language.Docker.Syntax+import qualified Data.Set as Set+import qualified Data.Text as Text+import qualified Hadolint.Shell as Shell rule :: Rule ParsedShell rule = simpleRule code severity message check@@ -20,11 +21,16 @@ {-# INLINEABLE rule #-} hasCacheMount :: RunFlags -> Bool-hasCacheMount RunFlags- { mount =- Just (CacheMount CacheOpts {cTarget = TargetPath {unTargetPath = p}})- } = Text.dropWhileEnd (=='/') p == "/var/cache/apk"-hasCacheMount RunFlags {} = False+hasCacheMount RunFlags { mount } =+ not (null $ Set.filter isCacheMount mount)++isCacheMount :: RunMount -> Bool+isCacheMount (CacheMount CacheOpts { cTarget = t })= isVarCacheApk t+isCacheMount _ = False++isVarCacheApk :: TargetPath -> Bool+isVarCacheApk TargetPath { unTargetPath = p }+ = Text.dropWhileEnd (=='/') p == "/var/cache/apk" forgotCacheOption :: Shell.Command -> Bool forgotCacheOption cmd = Shell.cmdHasArgs "apk" ["add"] cmd
src/Hadolint/Rule/DL3020.hs view
@@ -20,10 +20,13 @@ isArchive :: Text.Text -> Bool isArchive path = or- ( [ ftype `Text.isSuffixOf` path+ ( [ ftype `Text.isSuffixOf` dropQuotes path | ftype <- archiveFileFormatExtensions ] ) isUrl :: Text.Text -> Bool-isUrl path = or ([proto `Text.isPrefixOf` path | proto <- ["https://", "http://"]])+isUrl path = or+ [ proto `Text.isPrefixOf` dropQuotes path+ | proto <- ["https://", "http://"]+ ]
src/Hadolint/Rule/DL3033.hs view
@@ -12,15 +12,40 @@ severity = DLWarningC message = "Specify version with `yum install -y <package>-<version>`." - check (Run (RunArgs args _)) = foldArguments (all versionFixed . yumPackages) args+ check (Run (RunArgs args _)) =+ foldArguments (all packageVersionFixed . yumPackages) args+ && foldArguments (all moduleVersionFixed . yumModules) args check _ = True-- versionFixed package =- "-" `Text.isInfixOf` package- || ".rpm" `Text.isSuffixOf` package {-# INLINEABLE rule #-} yumPackages :: Shell.ParsedShell -> [Text.Text] yumPackages args =- [ arg | cmd <- Shell.presentCommands args, Shell.cmdHasArgs "yum" ["install"] cmd, arg <- Shell.getArgsNoFlags cmd, arg /= "install"+ [ arg+ | cmd <- Shell.presentCommands args,+ not (Shell.cmdHasArgs "yum" ["module"] cmd),+ arg <- installFilter cmd+ ]++packageVersionFixed :: Text.Text -> Bool+packageVersionFixed package =+ "-" `Text.isInfixOf` package || ".rpm" `Text.isSuffixOf` package++yumModules :: Shell.ParsedShell -> [Text.Text]+yumModules args =+ [ arg+ | cmd <- Shell.presentCommands args,+ Shell.cmdHasArgs "yum" ["module"] cmd,+ arg <- installFilter cmd+ ]++moduleVersionFixed :: Text.Text -> Bool+moduleVersionFixed = Text.isInfixOf ":"++installFilter :: Shell.Command -> [Text.Text]+installFilter cmd =+ [ arg+ | Shell.cmdHasArgs "yum" ["install"] cmd,+ arg <- Shell.getArgsNoFlags cmd,+ arg /= "install",+ arg /= "module" ]
src/Hadolint/Rule/DL3034.hs view
@@ -26,5 +26,5 @@ "si", "patch" ]- hasYesOption = Shell.hasAnyFlag ["no-confirm", "y"]+ hasYesOption = Shell.hasAnyFlag ["non-interactive", "n", "no-confirm", "y"] {-# INLINEABLE rule #-}
src/Hadolint/Rule/DL3041.hs view
@@ -12,15 +12,40 @@ severity = DLWarningC message = "Specify version with `dnf install -y <package>-<version>`." - check (Run (RunArgs args _)) = foldArguments (all versionFixed . dnfPackages) args+ check (Run (RunArgs args _)) =+ foldArguments (all packageVersionFixed . dnfPackages) args+ && foldArguments (all moduleVersionFixed . dnfModules) args check _ = True-- versionFixed package =- "-" `Text.isInfixOf` package- || ".rpm" `Text.isSuffixOf` package {-# INLINEABLE rule #-} dnfPackages :: Shell.ParsedShell -> [Text.Text] dnfPackages args =- [ arg | cmd <- Shell.presentCommands args, Shell.cmdHasArgs "dnf" ["install"] cmd, arg <- Shell.getArgsNoFlags cmd, arg /= "install"+ [ arg+ | cmd <- Shell.presentCommands args,+ not (Shell.cmdHasArgs "dnf" ["module"] cmd),+ arg <- installFilter cmd+ ]++packageVersionFixed :: Text.Text -> Bool+packageVersionFixed package =+ "-" `Text.isInfixOf` package || ".rpm" `Text.isSuffixOf` package++dnfModules :: Shell.ParsedShell -> [Text.Text]+dnfModules args =+ [ arg+ | cmd <- Shell.presentCommands args,+ Shell.cmdHasArgs "dnf" ["module"] cmd,+ arg <- installFilter cmd+ ]++moduleVersionFixed :: Text.Text -> Bool+moduleVersionFixed = Text.isInfixOf ":"++installFilter :: Shell.Command -> [Text.Text]+installFilter cmd =+ [ arg+ | Shell.cmdHasArgs "dnf" ["install"] cmd,+ arg <- Shell.getArgsNoFlags cmd,+ arg /= "install",+ arg /= "module" ]
src/Hadolint/Rule/DL3059.hs view
@@ -1,29 +1,44 @@ module Hadolint.Rule.DL3059 (rule) where import Hadolint.Rule+import qualified Hadolint.Shell as Shell import Language.Docker.Syntax data Acc- = Acc RunFlags+ = Acc { flags :: RunFlags, count :: Int } | Empty- deriving (Eq, Show)+ deriving (Eq) -rule :: Rule args+-- | This Rule catches multiple consecutive `RUN` instructions.+-- It ignores the case where multiple commands are chained together (e.g. with+-- `&&`) because in that case the programmer most likely has deliberately+-- chosen to use multiuple `RUN` instructions. Cases where --mount=xxx flags+-- differ are excluded as well.+rule :: Rule Shell.ParsedShell rule = customRule check (emptyState Empty) where code = "DL3059" severity = DLInfoC message = "Multiple consecutive `RUN` instructions. Consider consolidation." - check line st (Run (RunArgs _ flags))- | state st == Acc flags = st |> addFail CheckFailure {..}- | otherwise = st |> modify (remember flags)+ check line st (Run (RunArgs ar fl))+ | state st == Empty =+ st |> modify (remember fl (foldArguments countCommands ar))+ | flags (state st) /= fl =+ st |> modify (remember fl (foldArguments countCommands ar))+ | foldArguments countCommands ar > 2 || count (state st) > 2 =+ st |> modify (remember fl (foldArguments countCommands ar))+ | otherwise = st |> addFail CheckFailure {..}+ check _ st (Comment _) = st check _ st _ = st |> modify reset {-# INLINEABLE rule #-} -remember :: RunFlags -> Acc -> Acc-remember flags _ = Acc flags+remember :: RunFlags -> Int -> Acc -> Acc+remember fl cn _ = Acc { flags = fl, count = cn } reset :: Acc -> Acc reset _ = Empty++countCommands :: Shell.ParsedShell -> Int+countCommands script = length $ Shell.presentCommands script
src/Hadolint/Rule/Shellcheck.hs view
@@ -3,27 +3,125 @@ import qualified Data.Set as Set import qualified Data.Text as Text import Hadolint.Rule-import Hadolint.Shell (ParsedShell)+import qualified Hadolint.Shell+import Hadolint.Pragma (parseShell) import qualified Hadolint.Shell as Shell import Language.Docker.Syntax import qualified ShellCheck.Interface -rule :: Rule ParsedShell-rule = customRule check (emptyState Shell.defaultShellOpts)++data Acc+ = Acc+ { opts :: Shell.ShellOpts,+ defaultOpts :: Shell.ShellOpts+ }+ | Empty+++rule :: Rule Shell.ParsedShell+rule = customRule check (emptyState Empty) where- check _ st (From _) = st |> replaceWith Shell.defaultShellOpts -- Reset the state- check _ st (Arg name _) = st |> modify (Shell.addVars [name])- check _ st (Env pairs) = st |> modify (Shell.addVars (map fst pairs))- check _ st (Shell args) = st |> modify (Shell.setShell (foldArguments Shell.original args))- check line st (Run (RunArgs args _)) = getFailures |> foldr addFail st+ check _ st (From _) = st |> modify newStage+ check _ st (Arg name _) = st |> modify (addVars [name])+ check _ st (Env pairs) = st |> modify (addVars (map fst pairs))+ check _ st (Shell args) =+ st |> modify (setShell (foldArguments Shell.original args))+ check _ st (Comment com) =+ case Hadolint.Pragma.parseShell com of+ Just sh -> st |> modify (shellPragma sh)+ _ -> st+ check line st (Run (RunArgs args _)) = getFailures (state st) |> foldr addFail st where- getFailures = foldArguments (runShellCheck (state st)) args- runShellCheck opts script = Set.fromList [toFailure line c | c <- Shell.shellcheck opts script]+ getFailures Empty = foldArguments (runShellCheck Shell.defaultShellOpts) args+ getFailures s = foldArguments (runShellCheck (opts s)) args+ runShellCheck options script =+ Set.fromList+ [ toFailure line c+ | c <- Shell.shellcheck options script+ ] check _ st _ = st {-# INLINEABLE rule #-} +newStage :: Acc -> Acc+newStage Empty =+ Acc+ { opts = Shell.defaultShellOpts,+ defaultOpts = Shell.defaultShellOpts+ }+newStage Acc {..} =+ Acc+ { opts = defaultOpts,+ defaultOpts+ }++addVars :: [Text.Text] -> Acc -> Acc+addVars vars Empty =+ Acc+ { opts =+ Shell.ShellOpts+ { shellName = Shell.shellName Shell.defaultShellOpts,+ envVars = Shell.envVars Shell.defaultShellOpts <> Set.fromList vars+ },+ defaultOpts = Shell.defaultShellOpts+ }+addVars vars Acc {..} =+ Acc+ { opts =+ Shell.ShellOpts+ { shellName = Shell.shellName opts,+ envVars = Shell.envVars opts <> Set.fromList vars+ },+ defaultOpts+ }++setShell :: Text.Text -> Acc -> Acc+setShell sh Empty =+ Acc+ { opts =+ Shell.ShellOpts+ { shellName = sh,+ envVars = Shell.envVars Shell.defaultShellOpts+ },+ defaultOpts = Shell.defaultShellOpts+ }+setShell sh Acc {..} =+ Acc+ { opts =+ Shell.ShellOpts+ { shellName = sh,+ envVars = Shell.envVars opts+ },+ defaultOpts+ }++shellPragma :: Text.Text -> Acc -> Acc+shellPragma sh Empty =+ Acc+ { opts =+ Shell.ShellOpts+ { shellName = sh,+ envVars = Shell.envVars Shell.defaultShellOpts+ },+ defaultOpts =+ Shell.ShellOpts+ { shellName = sh,+ envVars = Shell.envVars Shell.defaultShellOpts+ }+ }+shellPragma sh Acc {..} =+ Acc+ { opts =+ Shell.ShellOpts+ { shellName = sh,+ envVars = Shell.envVars opts+ },+ defaultOpts+ }+ -- | Converts ShellCheck errors into our own errors type-toFailure :: Linenumber -> ShellCheck.Interface.PositionedComment -> CheckFailure+toFailure :: Linenumber ->+ ShellCheck.Interface.PositionedComment ->+ CheckFailure toFailure line c = CheckFailure { code = RuleCode $ Text.pack ("SC" ++ show (code c)),@@ -32,13 +130,16 @@ line = line } where- getDLSeverity :: ShellCheck.Interface.Severity -> DLSeverity- getDLSeverity s =- case s of- ShellCheck.Interface.WarningC -> DLWarningC- ShellCheck.Interface.InfoC -> DLInfoC- ShellCheck.Interface.StyleC -> DLStyleC- _ -> DLErrorC- severity pc = ShellCheck.Interface.cSeverity $ ShellCheck.Interface.pcComment pc+ severity pc =+ ShellCheck.Interface.cSeverity $ ShellCheck.Interface.pcComment pc code pc = ShellCheck.Interface.cCode $ ShellCheck.Interface.pcComment pc- message pc = ShellCheck.Interface.cMessage $ ShellCheck.Interface.pcComment pc+ message pc =+ ShellCheck.Interface.cMessage $ ShellCheck.Interface.pcComment pc++getDLSeverity :: ShellCheck.Interface.Severity -> DLSeverity+getDLSeverity s =+ case s of+ ShellCheck.Interface.WarningC -> DLWarningC+ ShellCheck.Interface.InfoC -> DLInfoC+ ShellCheck.Interface.StyleC -> DLStyleC+ _ -> DLErrorC
src/Hadolint/Shell.hs view
@@ -61,7 +61,7 @@ shellcheck :: ShellOpts -> ParsedShell -> [PositionedComment] shellcheck (ShellOpts sh env) (ParsedShell txt _ _) =- if any (`Text.isPrefixOf` sh) nonPosixShells || hasUnsupportedShebang txt+ if any (`Text.isInfixOf` sh) nonPosixShells || hasUnsupportedShebang txt then [] -- Do no run for non-posix shells i.e. powershell, cmd.exe else runShellCheck where
− test/ConfigSpec.hs
@@ -1,136 +0,0 @@-module ConfigSpec where--import Control.Monad (unless)-import qualified Data.ByteString.Char8 as Bytes-import Data.Map-import qualified Data.YAML as Yaml-import Hadolint.Config-import Hadolint.Rule as Rule-import Test.HUnit-import Test.Hspec--tests :: SpecWith ()-tests =- describe "Config" $ do- it "Parses config with only error severities" $- let configFile =- [ "override:",- " error:",- " - DL3000",- " - SC1010"- ]- override = Just (OverrideConfig (Just ["DL3000", "SC1010"]) Nothing Nothing Nothing)- expected = ConfigFile override Nothing Nothing Nothing Nothing Nothing- in assertConfig expected (Bytes.unlines configFile)-- it "Parses config with only warning severities" $- let configFile =- [ "override:",- " warning:",- " - DL3000",- " - SC1010"- ]- override = Just (OverrideConfig Nothing (Just ["DL3000", "SC1010"]) Nothing Nothing)- expected = ConfigFile override Nothing Nothing Nothing Nothing Nothing- in assertConfig expected (Bytes.unlines configFile)-- it "Parses config with only info severities" $- let configFile =- [ "override:",- " info:",- " - DL3000",- " - SC1010"- ]- override = Just (OverrideConfig Nothing Nothing (Just ["DL3000", "SC1010"]) Nothing)- expected = ConfigFile override Nothing Nothing Nothing Nothing Nothing- in assertConfig expected (Bytes.unlines configFile)-- it "Parses config with only style severities" $- let configFile =- [ "override:",- " style:",- " - DL3000",- " - SC1010"- ]- override = Just (OverrideConfig Nothing Nothing Nothing (Just ["DL3000", "SC1010"]))- expected = ConfigFile override Nothing Nothing Nothing Nothing Nothing- in assertConfig expected (Bytes.unlines configFile)-- it "Parses config with only ignores" $- let configFile =- [ "ignored:",- "- DL3000",- "- SC1010"- ]- expected = ConfigFile Nothing (Just ["DL3000", "SC1010"]) Nothing Nothing Nothing Nothing- in assertConfig expected (Bytes.unlines configFile)-- it "Parses config with only trustedRegistries" $- let configFile =- [ "trustedRegistries:",- "- hub.docker.com",- "- my.shady.xyz"- ]- expected = ConfigFile Nothing Nothing (Just ["hub.docker.com", "my.shady.xyz"]) Nothing Nothing Nothing- in assertConfig expected (Bytes.unlines configFile)-- it "Parses config with only label-schema" $- let configFile =- [ "label-schema:",- " author: text",- " url: url"- ]- expected = ConfigFile Nothing Nothing Nothing (Just (fromList [("author", Rule.RawText), ("url", Rule.Url)])) Nothing Nothing- in assertConfig expected (Bytes.unlines configFile)-- it "Parses config with only label-schema" $- let configFile = [ "strict-labels: true" ]- expected = ConfigFile Nothing Nothing Nothing Nothing (Just True) Nothing- in assertConfig expected (Bytes.unlines configFile)-- it "Parses config with failure-threshold" $- let configFile = [ "failure-threshold: error" ]- expected = ConfigFile Nothing Nothing Nothing Nothing Nothing (Just Rule.DLErrorC)- in assertConfig expected (Bytes.unlines configFile)-- it "Parses full file" $- let configFile =- [ "override:",- " info:",- " - DL3002",- " style:",- " - DL3004",- " warning:",- " - DL3003",- " error:",- " - DL3001",- "",- "trustedRegistries:",- "- hub.docker.com",- "",- "ignored:",- "- DL3000",- "",- "strict-labels: false",- "label-schema:",- " author: text",- " url: url",- "",- "failure-threshold: style"- ]- override = Just (OverrideConfig (Just ["DL3001"]) (Just ["DL3003"]) (Just ["DL3002"]) (Just ["DL3004"]))- labelschema = Just (fromList [("author", Rule.RawText), ("url", Rule.Url)])- expected = ConfigFile override (Just ["DL3000"]) (Just ["hub.docker.com"]) labelschema (Just False) (Just Rule.DLStyleC)- in assertConfig expected (Bytes.unlines configFile)--assertConfig :: HasCallStack => ConfigFile -> Bytes.ByteString -> Assertion-assertConfig config s =- case Yaml.decode1Strict s of- Left (_, err) ->- assertFailure err- Right result ->- checkResult result- where- checkResult result =- unless (result == config) $- assertFailure ("Config \n\n" ++ show config ++ "\n\n is not \n\n" ++ show result)
− test/DL3000.hs
@@ -1,26 +0,0 @@-module DL3000 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3000 - Use absolute WORKDIR." $ do- it "workdir relative" $ ruleCatches "DL3000" "WORKDIR relative/dir"- it "workdir absolute" $ ruleCatchesNot "DL3000" "WORKDIR /usr/local"- it "workdir variable" $ ruleCatchesNot "DL3000" "WORKDIR ${work}"- it "workdir relative single quotes" $ ruleCatches "DL3000" "WORKDIR \'relative/dir\'"- it "workdir absolute single quotes" $ ruleCatchesNot "DL3000" "WORKDIR \'/usr/local\'"- -- no test for variable/single quotes since the variable would not expand.- it "workdir relative double quotes" $ ruleCatches "DL3000" "WORKDIR \"relative/dir\""- it "workdir absolute double quotes" $ ruleCatchesNot "DL3000" "WORKDIR \"/usr/local\""- it "workdir variable double quotes" $ ruleCatchesNot "DL3000" "WORKDIR \"${dir}\""- --- it "workdir absolute windows" $ ruleCatchesNot "DL3000" "WORKDIR \'C:\\\'"- it "workdir absolute windows quotes" $ ruleCatchesNot "DL3000" "WORKDIR \"C:\\\""- it "workdir absolute windows alternative" $- ruleCatchesNot "DL3000" "WORKDIR C:/"- it "workdir absolute windows quotes alternative" $- ruleCatchesNot "DL3000" "WORKDIR \"C:/\""
− test/DL3001.hs
@@ -1,16 +0,0 @@-module DL3001 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3001 - invalid CMD rules" $ do- it "invalid cmd" $ do- ruleCatches "DL3001" "RUN top"- onBuildRuleCatches "DL3001" "RUN top"- it "install ssh" $ do- ruleCatchesNot "DL3001" "RUN apt-get install ssh"- onBuildRuleCatchesNot "DL3001" "RUN apt-get install ssh"
− test/DL3002.hs
@@ -1,86 +0,0 @@-module DL3002 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3002 - Last user should not be root." $ do- it "last user should not be root" $- let dockerFile =- [ "FROM scratch",- "USER root"- ]- in ruleCatches "DL3002" $ Text.unlines dockerFile-- it "no root" $- let dockerFile =- [ "FROM scratch",- "USER foo"- ]- in ruleCatchesNot "DL3002" $ Text.unlines dockerFile-- it "no root UID" $- let dockerFile =- [ "FROM scratch",- "USER 0"- ]- in ruleCatches "DL3002" $ Text.unlines dockerFile-- it "no root:root" $- let dockerFile =- [ "FROM scratch",- "USER root:root"- ]- in ruleCatches "DL3002" $ Text.unlines dockerFile-- it "no UID:GID" $- let dockerFile =- [ "FROM scratch",- "USER 0:0"- ]- in ruleCatches "DL3002" $ Text.unlines dockerFile-- it "can switch back to non root" $- let dockerFile =- [ "FROM scratch",- "USER root",- "RUN something",- "USER foo"- ]- in ruleCatchesNot "DL3002" $ Text.unlines dockerFile-- it "warns on transitive root user" $- let dockerFile =- [ "FROM debian as base",- "USER root",- "RUN something",- "FROM base",- "RUN something else"- ]- in ruleCatches "DL3002" $ Text.unlines dockerFile-- it "warns on multiple stages" $- let dockerFile =- [ "FROM debian as base",- "USER root",- "RUN something",- "FROM scratch",- "USER foo",- "RUN something else"- ]- in ruleCatches "DL3002" $ Text.unlines dockerFile-- it "does not warn when switching in multiple stages" $- let dockerFile =- [ "FROM debian as base",- "USER root",- "RUN something",- "USER foo",- "FROM scratch",- "RUN something else"- ]- in ruleCatchesNot "DL3002" $ Text.unlines dockerFile
− test/DL3003.hs
@@ -1,12 +0,0 @@-module DL3003 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3003 - Use WORKDIR to switch to a directory." $ do- it "ok using WORKDIR" $ ruleCatchesNot "DL3003" "WORKDIR /opt"- it "not ok using cd" $ ruleCatches "DL3003" "RUN cd /opt"
− test/DL3004.hs
@@ -1,19 +0,0 @@-module DL3004 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3004 - Do not use sudo as it leads to unpredictable behavior. Use a tool like gosu to enforce root." $ do- it "sudo" $ do- ruleCatches "DL3004" "RUN sudo apt-get update"- onBuildRuleCatches "DL3004" "RUN sudo apt-get update"- it "install sudo" $ do- ruleCatchesNot "DL3004" "RUN apt-get install sudo"- onBuildRuleCatchesNot "DL3004" "RUN apt-get install sudo"- it "sudo chained programs" $ do- ruleCatches "DL3004" "RUN apt-get update && sudo apt-get install"- onBuildRuleCatches "DL3004" "RUN apt-get update && sudo apt-get install"
− test/DL3005.hs
@@ -1,13 +0,0 @@-module DL3005 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3005" $ do- it "apt-get dist-upgrade" $ do- ruleCatches "DL3005" "RUN apt-get update && apt-get dist-upgrade"- onBuildRuleCatches "DL3005" "RUN apt-get update && apt-get dist-upgrade"
− test/DL3006.hs
@@ -1,42 +0,0 @@-module DL3006 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3006 - Always tag the version of an image explicitly." $ do- it "no untagged" $ ruleCatches "DL3006" "FROM debian"- it "no untagged with name" $ ruleCatches "DL3006" "FROM debian AS builder"- it "untagged digest is not an error" $- ruleCatchesNot "DL3006" "FROM ruby@sha256:f1dbca0f5dbc9"- it "untagged digest is not an error" $- ruleCatchesNot "DL3006" "FROM ruby:2"- it "local aliases are OK to be untagged" $- let dockerFile =- [ "FROM golang:1.9.3-alpine3.7 AS build",- "RUN foo",- "FROM build as unit-test",- "RUN bar",- "FROM alpine:3.7",- "RUN baz"- ]- in do- ruleCatchesNot "DL3006" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3006" $ Text.unlines dockerFile- it "other untagged cases are not ok" $- let dockerFile =- [ "FROM golang:1.9.3-alpine3.7 AS build",- "RUN foo",- "FROM node as unit-test",- "RUN bar",- "FROM alpine:3.7",- "RUN baz"- ]- in do- ruleCatches "DL3006" $ Text.unlines dockerFile- onBuildRuleCatches "DL3006" $ Text.unlines dockerFile- it "scratch" $ ruleCatchesNot "DL3006" "FROM scratch"
− test/DL3007.hs
@@ -1,20 +0,0 @@-module DL3007 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3007 - Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag." $ do- it "explicit latest" $ ruleCatches "DL3007" "FROM debian:latest"- it "explicit latest with name" $ ruleCatches "DL3007" "FROM debian:latest AS builder"- it "explicit tagged" $ ruleCatchesNot "DL3007" "FROM debian:jessie"- it "explicit SHA" $- ruleCatchesNot- "DL3007"- "FROM hub.docker.io/debian@sha256:\- \7959ed6f7e35f8b1aaa06d1d8259d4ee25aa85a086d5c125480c333183f9deeb"- it "explicit tagged with name" $- ruleCatchesNot "DL3007" "FROM debian:jessie AS builder"
− test/DL3008.hs
@@ -1,61 +0,0 @@-module DL3008 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3008 - Pin versions in apt-get install." $ do- it "apt-get version pinning" $ do- ruleCatches "DL3008" "RUN apt-get update && apt-get install python"- onBuildRuleCatches "DL3008" "RUN apt-get update && apt-get install python"- it "apt-get pinned chained" $- let dockerFile =- [ "RUN apt-get update \\",- " && apt-get -yqq --no-install-recommends install nodejs=0.10 \\",- " && rm -rf /var/lib/apt/lists/*"- ]- in do- ruleCatchesNot "DL3008" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3008" $ Text.unlines dockerFile-- it "apt-get pinned regression" $- let dockerFile =- [ "RUN apt-get update && apt-get install --no-install-recommends -y \\",- "python-demjson=2.2.2* \\",- "wget=1.16.1* \\",- "git=1:2.5.0* \\",- "ruby=1:2.1.*"- ]- in do- ruleCatchesNot "DL3008" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3008" $ Text.unlines dockerFile- it "apt-get version" $ do- ruleCatchesNot "DL3008" "RUN apt-get install -y python=1.2.2"- onBuildRuleCatchesNot "DL3008" "RUN apt-get install -y python=1.2.2"- it "apt-get version" $ do- ruleCatchesNot "DL3008" "RUN apt-get install ./wkhtmltox_0.12.5-1.bionic_amd64.deb"- onBuildRuleCatchesNot "DL3008" "RUN apt-get install ./wkhtmltox_0.12.5-1.bionic_amd64.deb"- it "apt-get pinned" $ do- ruleCatchesNot- "DL3008"- "RUN apt-get -y --no-install-recommends install nodejs=0.10"- onBuildRuleCatchesNot- "DL3008"- "RUN apt-get -y --no-install-recommends install nodejs=0.10"- it "apt-get tolerate target-release" $- let dockerFile =- [ "RUN set -e &&\\",- " echo \"deb http://http.debian.net/debian jessie-backports main\" \- \> /etc/apt/sources.list.d/jessie-backports.list &&\\",- " apt-get update &&\\",- " apt-get install -y --no-install-recommends -t jessie-backports \- \openjdk-8-jdk=8u131-b11-1~bpo8+1 &&\\",- " rm -rf /var/lib/apt/lists/*"- ]- in do- ruleCatchesNot "DL3008" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3008" $ Text.unlines dockerFile
− test/DL3009.hs
@@ -1,81 +0,0 @@-module DL3009 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3009 - Delete the apt-get lists after installing something." $ do- it "apt-get no cleanup" $- let dockerFile =- [ "FROM scratch",- "RUN apt-get update && apt-get install python"- ]- in do- ruleCatches "DL3009" $ Text.unlines dockerFile- onBuildRuleCatches "DL3009" $ Text.unlines dockerFile- it "apt-get cleanup in stage image" $- let dockerFile =- [ "FROM ubuntu as foo",- "RUN apt-get update && apt-get install python",- "FROM scratch",- "RUN echo hey!"- ]- in do- ruleCatchesNot "DL3009" $ Text.unlines dockerFile- onBuildRuleCatches "DL3009" $ Text.unlines dockerFile- it "apt-get no cleanup in last stage" $- let dockerFile =- [ "FROM ubuntu as foo",- "RUN hey!",- "FROM scratch",- "RUN apt-get update && apt-get install python"- ]- in do- ruleCatches "DL3009" $ Text.unlines dockerFile- onBuildRuleCatches "DL3009" $ Text.unlines dockerFile- it "apt-get no cleanup in intermediate stage" $- let dockerFile =- [ "FROM ubuntu as foo",- "RUN apt-get update && apt-get install python",- "FROM foo",- "RUN hey!"- ]- in do- ruleCatches "DL3009" $ Text.unlines dockerFile- onBuildRuleCatches "DL3009" $ Text.unlines dockerFile- it "no warn apt-get cleanup in intermediate stage that cleans lists" $- let dockerFile =- [ "FROM ubuntu as foo",- "RUN apt-get update && apt-get install python && rm -rf /var/lib/apt/lists/*",- "FROM foo",- "RUN hey!"- ]- in do- ruleCatchesNot "DL3009" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile- it "no warn apt-get cleanup in intermediate stage when stage not used later" $- let dockerFile =- [ "FROM ubuntu as foo",- "RUN apt-get update && apt-get install python",- "FROM scratch",- "RUN hey!"- ]- in do- ruleCatchesNot "DL3009" $ Text.unlines dockerFile- onBuildRuleCatches "DL3009" $ Text.unlines dockerFile- it "apt-get cleanup" $- let dockerFile =- [ "FROM scratch",- "RUN apt-get update && apt-get install python && rm -rf /var/lib/apt/lists/*"- ]- in do- ruleCatchesNot "DL3009" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile-- it "don't warn: BuildKit cache mount to apt lists directory" $ do- ruleCatchesNot "DL3009" "RUN --mount=type=cache,target=/var/lib/apt/lists apt-get update && apt-get install python"- onBuildRuleCatchesNot "DL3009" "RUN --mount=type=cache,target=/var/lib/apt/lists apt-get update && apt-get install python"
− test/DL3010.hs
@@ -1,13 +0,0 @@-module DL3010 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "COPY rules" $ do- it "use add" $ ruleCatches "DL3010" "COPY packaged-app.tar /usr/src/app"- it "use not add" $ ruleCatchesNot "DL3010" "COPY package.json /usr/src/app"- it "ignore: copy from previous stage" $ ruleCatchesNot "DL3010" "COPY --from=builder /usr/local/share/some.tar /opt/some.tar"
− test/DL3011.hs
@@ -1,12 +0,0 @@-module DL3011 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "EXPOSE rules" $ do- it "invalid port" $ ruleCatches "DL3011" "EXPOSE 80000"- it "valid port" $ ruleCatchesNot "DL3011" "EXPOSE 60000"
− test/DL3012.hs
@@ -1,18 +0,0 @@-module DL3012 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3012 - Multiple `HEALTHCHECK` instructions" $ do- it "ok with no HEALTHCHECK instruction" $- ruleCatchesNot "DL3012" "FROM scratch"- it "ok with one HEALTHCHECK instruction" $- ruleCatchesNot "DL3012" "FROM scratch\nHEALTHCHECK CMD /bin/bla"- it "ok with two HEALTHCHECK instructions in two stages" $- ruleCatchesNot "DL3012" "FROM scratch\nHEALTHCHECK CMD /bin/bla1\nFROM scratch\nHEALTHCHECK CMD /bin/bla2"- it "warn with two HEALTHCHECK instructions" $- ruleCatches "DL3012" "FROM scratch\nHEALTHCHECK CMD /bin/bla1\nHEALTHCHECK CMD /bin/bla2"
− test/DL3013.hs
@@ -1,139 +0,0 @@-module DL3013 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3013 - Pin versions in pip." $ do- it "pip2 version not pinned" $ do- ruleCatches "DL3013" "RUN pip2 install MySQL_python"- onBuildRuleCatches "DL3013" "RUN pip2 install MySQL_python"- it "pip3 version not pinned" $ do- ruleCatches "DL3013" "RUN pip3 install MySQL_python"- onBuildRuleCatches "DL3013" "RUN pip2 install MySQL_python"- it "pip3 version pinned" $ do- ruleCatchesNot "DL3013" "RUN pip3 install MySQL_python==1.2.2"- onBuildRuleCatchesNot "DL3013" "RUN pip3 install MySQL_python==1.2.2"- it "pip3 install from local package" $ do- ruleCatchesNot "DL3013" "RUN pip3 install mypkg.whl"- ruleCatchesNot "DL3013" "RUN pip3 install mypkg.tar.gz"- onBuildRuleCatchesNot "DL3013" "RUN pip3 install mypkg.whl"- onBuildRuleCatchesNot "DL3013" "RUN pip3 install mypkg.tar.gz"- it "pip install requirements" $ do- ruleCatchesNot "DL3013" "RUN pip install -r requirements.txt"- onBuildRuleCatchesNot "DL3013" "RUN pip install -r requirements.txt"- it "pip install requirements with long flag" $ do- ruleCatchesNot "DL3013" "RUN pip install --requirement requirements.txt"- onBuildRuleCatchesNot "DL3013" "RUN pip install --requirement requirements.txt"- it "pip install use setup.py" $ do- ruleCatchesNot "DL3013" "RUN pip install ."- onBuildRuleCatchesNot "DL3013" "RUN pip install ."- it "pip version not pinned" $ do- ruleCatches "DL3013" "RUN pip install MySQL_python"- onBuildRuleCatches "DL3013" "RUN pip install MySQL_python"- it "pip version pinned" $ do- ruleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2"- onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2"- it "pip version pinned with ~= operator" $ do- ruleCatchesNot "DL3013" "RUN pip install MySQL_python~=1.2.2"- onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python~=1.2.2"- it "pip version pinned with === operator" $ do- ruleCatchesNot "DL3013" "RUN pip install MySQL_python===1.2.2"- onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python===1.2.2"- it "pip version pinned with flag --ignore-installed" $ do- ruleCatchesNot "DL3013" "RUN pip install --ignore-installed MySQL_python==1.2.2"- onBuildRuleCatchesNot "DL3013" "RUN pip install --ignore-installed MySQL_python==1.2.2"- it "pip version pinned with flag --build" $ do- ruleCatchesNot "DL3013" "RUN pip3 install --build /opt/yamllint yamllint==1.20.0"- onBuildRuleCatchesNot "DL3013" "RUN pip3 install --build /opt/yamllint yamllint==1.20.0"- it "pip version pinned with flag --prefix" $ do- ruleCatchesNot "DL3013" "RUN pip3 install --prefix /opt/yamllint yamllint==1.20.0"- onBuildRuleCatchesNot "DL3013" "RUN pip3 install --prefix /opt/yamllint yamllint==1.20.0"- it "pip version pinned with flag --root" $ do- ruleCatchesNot "DL3013" "RUN pip3 install --root /opt/yamllint yamllint==1.20.0"- onBuildRuleCatchesNot "DL3013" "RUN pip3 install --root /opt/yamllint yamllint==1.20.0"- it "pip version pinned with flag --target" $ do- ruleCatchesNot "DL3013" "RUN pip3 install --target /opt/yamllint yamllint==1.20.0"- onBuildRuleCatchesNot "DL3013" "RUN pip3 install --target /opt/yamllint yamllint==1.20.0"- it "pip version pinned with flag --trusted-host" $ do- ruleCatchesNot "DL3013" "RUN pip3 install --trusted-host host example==1.2.2"- onBuildRuleCatchesNot "DL3013" "RUN pip3 install --trusted-host host example==1.2.2"- it "pip version pinned with python -m" $ do- ruleCatchesNot "DL3013" "RUN python -m pip install example==1.2.2"- onBuildRuleCatchesNot "DL3013" "RUN python -m pip install example==1.2.2"- it "pip version not pinned with python -m" $ do- ruleCatches "DL3013" "RUN python -m pip install example"- onBuildRuleCatches "DL3013" "RUN python -m pip install --index-url url example"- it "pip install git" $ do- ruleCatchesNot- "DL3013"- "RUN pip install git+https://github.com/rtfd/r-ext.git@0.6-alpha#egg=r-ext"- onBuildRuleCatchesNot- "DL3013"- "RUN pip install git+https://github.com/rtfd/r-ext.git@0.6-alpha#egg=r-ext"- it "pip install unversioned git" $ do- ruleCatches- "DL3013"- "RUN pip install git+https://github.com/rtfd/read-ext.git#egg=read-ext"- onBuildRuleCatches- "DL3013"- "RUN pip install git+https://github.com/rtfd/read-ext.git#egg=read-ext"- it "pip install upper bound" $ do- ruleCatchesNot "DL3013" "RUN pip install 'alabaster>=0.7'"- onBuildRuleCatchesNot "DL3013" "RUN pip install 'alabaster>=0.7'"- it "pip install lower bound" $ do- ruleCatchesNot "DL3013" "RUN pip install 'alabaster<0.7'"- onBuildRuleCatchesNot "DL3013" "RUN pip install 'alabaster<0.7'"- it "pip install excluded version" $ do- ruleCatchesNot "DL3013" "RUN pip install 'alabaster!=0.7'"- onBuildRuleCatchesNot "DL3013" "RUN pip install 'alabaster!=0.7'"- it "pip install user directory" $ do- ruleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2 --user"- onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2 --user"- it "pip install no pip version check" $ do- ruleCatchesNot- "DL3013"- "RUN pip install MySQL_python==1.2.2 --disable-pip-version-check"- onBuildRuleCatchesNot- "DL3013"- "RUN pip install MySQL_python==1.2.2 --disable-pip-version-check"- it "pip install --index-url" $ do- ruleCatchesNot- "DL3013"- "RUN pip install --index-url https://eg.com/foo foobar==1.0.0"- onBuildRuleCatchesNot- "DL3013"- "RUN pip install --index-url https://eg.com/foo foobar==1.0.0"- it "pip install index-url with -i flag" $ do- ruleCatchesNot- "DL3013"- "RUN pip install --index-url https://eg.com/foo foobar==1.0.0"- onBuildRuleCatchesNot- "DL3013"- "RUN pip install --index-url https://eg.com/foo foobar==1.0.0"- it "pip install --index-url with --extra-index-url" $ do- ruleCatchesNot- "DL3013"- "RUN pip install --index-url https://eg.com/foo --extra-index-url https://ex-eg.io/foo foobar==1.0.0"- onBuildRuleCatchesNot- "DL3013"- "RUN pip install --index-url https://eg.com/foo --extra-index-url https://ex-eg.io/foo foobar==1.0.0"- it "pip install no cache dir" $ do- ruleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2 --no-cache-dir"- onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2 --no-cache-dir"- it "pip install constraints file - long version argument" $ do- ruleCatchesNot "DL3013" "RUN pip install pykafka --constraint http://foo.bar.baz"- onBuildRuleCatchesNot "DL3013" "RUN pip install pykafka --constraint http://foo.bar.baz"- it "pip install constraints file - short version argument" $ do- ruleCatchesNot "DL3013" "RUN pip install pykafka -c http://foo.bar.baz"- onBuildRuleCatchesNot "DL3013" "RUN pip install pykafka -c http://foo.bar.baz"- it "pip install --index-url with --extra-index-url with basic auth" $ do- ruleCatchesNot- "DL3013"- "RUN pip install --index-url https://user:pass@eg.com/foo --extra-index-url https://user:pass@ex-eg.io/foo foobar==1.0.0"- onBuildRuleCatchesNot- "DL3013"- "RUN pip install --index-url https://user:pass@eg.com/foo --extra-index-url https://user:pass@ex-eg.io/foo foobar==1.0.0"
− test/DL3014.hs
@@ -1,31 +0,0 @@-module DL3014 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3014 - Use the -y switch." $ do- it "apt-get auto yes" $ do- ruleCatches "DL3014" "RUN apt-get install python"- onBuildRuleCatches "DL3014" "RUN apt-get install python"- it "apt-get yes shortflag" $ do- ruleCatchesNot "DL3014" "RUN apt-get install -yq python"- onBuildRuleCatchesNot "DL3014" "RUN apt-get install -yq python"- it "apt-get yes quiet level 2 implies -y" $ do- ruleCatchesNot "DL3014" "RUN apt-get install -qq python"- onBuildRuleCatchesNot "DL3014" "RUN apt-get install -qq python"- it "apt-get yes different pos" $ do- ruleCatchesNot "DL3014" "RUN apt-get install -y python"- onBuildRuleCatchesNot "DL3014" "RUN apt-get install -y python"- it "apt-get with auto yes" $ do- ruleCatchesNot "DL3014" "RUN apt-get -y install python"- onBuildRuleCatchesNot "DL3014" "RUN apt-get -y install python"- it "apt-get with auto expanded yes" $ do- ruleCatchesNot "DL3014" "RUN apt-get --yes install python"- onBuildRuleCatchesNot "DL3014" "RUN apt-get --yes install python"- it "apt-get with assume-yes" $ do- ruleCatchesNot "DL3014" "RUN apt-get --assume-yes install python"- onBuildRuleCatchesNot "DL3014" "RUN apt-get --assume-yes install python"
− test/DL3015.hs
@@ -1,26 +0,0 @@-module DL3015 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3015 - Avoid additional packages by specifying `--no-install-recommends`." $ do- it "apt-get install recommends" $ do- ruleCatchesNot- "DL3015"- "RUN apt-get install --no-install-recommends python"- onBuildRuleCatchesNot- "DL3015"- "RUN apt-get install --no-install-recommends python"- it "apt-get no install recommends" $ do- ruleCatches "DL3015" "RUN apt-get install python"- onBuildRuleCatches "DL3015" "RUN apt-get install python"- it "apt-get no install recommends" $ do- ruleCatches "DL3015" "RUN apt-get -y install python"- onBuildRuleCatches "DL3015" "RUN apt-get -y install python"- it "apt-get no install recommends via option" $ do- ruleCatchesNot "DL3015" "RUN apt-get -o APT::Install-Recommends=false install python"- onBuildRuleCatchesNot "DL3015" "RUN apt-get -o APT::Install-Recommends=false install python"
− test/DL3016.hs
@@ -1,123 +0,0 @@-module DL3016 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3016 - Pin versions in npm." $ do- it "version pinned in package.json" $ do- ruleCatchesNot "DL3016" "RUN npm install"- onBuildRuleCatchesNot "DL3016" "RUN npm install"- it "version pinned in package.json with arguments" $ do- ruleCatchesNot "DL3016" "RUN npm install --progress=false"- onBuildRuleCatchesNot "DL3016" "RUN npm install --progress=false"- it "version pinned" $ do- ruleCatchesNot "DL3016" "RUN npm install express@4.1.1"- onBuildRuleCatchesNot "DL3016" "RUN npm install express@4.1.1"- it "version pinned with scope" $ do- ruleCatchesNot "DL3016" "RUN npm install @myorg/privatepackage@\">=0.1.0\""- onBuildRuleCatchesNot "DL3016" "RUN npm install @myorg/privatepackage@\">=0.1.0\""- it "version pinned multiple packages" $ do- ruleCatchesNot "DL3016" "RUN npm install express@\"4.1.1\" sax@0.1.1"- onBuildRuleCatchesNot "DL3016" "RUN npm install express@\"4.1.1\" sax@0.1.1"- it "version pinned with --global" $ do- ruleCatchesNot "DL3016" "RUN npm install --global express@\"4.1.1\""- onBuildRuleCatchesNot "DL3016" "RUN npm install --global express@\"4.1.1\""- it "version pinned with -g" $ do- ruleCatchesNot "DL3016" "RUN npm install -g express@\"4.1.1\""- onBuildRuleCatchesNot "DL3016" "RUN npm install -g express@\"4.1.1\""- it "version does not have to be pinned for tarball suffix .tar" $ do- ruleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tar"- onBuildRuleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tar"- it "version does not have to be pinned for tarball suffix .tar.gz" $ do- ruleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tar.gz"- onBuildRuleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tar.gz"- it "version does not have to be pinned for tarball suffix .tgz" $ do- ruleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tgz"- onBuildRuleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tgz"- it "version does not have to be pinned for folder - absolute path" $ do- ruleCatchesNot "DL3016" "RUN npm install /folder"- onBuildRuleCatchesNot "DL3016" "RUN npm install /folder"- it "version does not have to be pinned for folder - relative path from current folder" $ do- ruleCatchesNot "DL3016" "RUN npm install ./folder"- onBuildRuleCatchesNot "DL3016" "RUN npm install ./folder"- it "version does not have to be pinned for folder - relative path to parent folder" $ do- ruleCatchesNot "DL3016" "RUN npm install ../folder"- onBuildRuleCatchesNot "DL3016" "RUN npm install ../folder"- it "version does not have to be pinned for folder - relative path from home" $ do- ruleCatchesNot "DL3016" "RUN npm install ~/folder"- onBuildRuleCatchesNot "DL3016" "RUN npm install ~/folder"- it "commit pinned for git+ssh" $ do- ruleCatchesNot- "DL3016"- "RUN npm install git+ssh://git@github.com:npm/npm.git#v1.0.27"- onBuildRuleCatchesNot- "DL3016"- "RUN npm install git+ssh://git@github.com:npm/npm.git#v1.0.27"- it "commit pinned for git+http" $ do- ruleCatchesNot- "DL3016"- "RUN npm install git+http://isaacs@github.com/npm/npm#semver:^5.0"- onBuildRuleCatchesNot- "DL3016"- "RUN npm install git+http://isaacs@github.com/npm/npm#semver:^5.0"- it "commit pinned for git+https" $ do- ruleCatchesNot- "DL3016"- "RUN npm install git+https://isaacs@github.com/npm/npm.git#v1.0.27"- onBuildRuleCatchesNot- "DL3016"- "RUN npm install git+https://isaacs@github.com/npm/npm.git#v1.0.27"- it "commit pinned for git" $ do- ruleCatchesNot- "DL3016"- "RUN npm install git://github.com/npm/npm.git#v1.0.27"- onBuildRuleCatchesNot- "DL3016"- "RUN npm install git://github.com/npm/npm.git#v1.0.27"- it "npm run install is fine" $ do- ruleCatchesNot- "DL3016"- "RUN npm run --crazy install"- onBuildRuleCatchesNot- "DL3016"- "RUN npm run --crazy install"-- --version range is not supported- it "version pinned with scope" $ do- ruleCatchesNot "DL3016" "RUN npm install @myorg/privatepackage@\">=0.1.0 <0.2.0\""- onBuildRuleCatchesNot "DL3016" "RUN npm install @myorg/privatepackage@\">=0.1.0 <0.2.0\""- it "version not pinned" $ do- ruleCatches "DL3016" "RUN npm install express"- onBuildRuleCatches "DL3016" "RUN npm install express"- it "version not pinned with scope" $ do- ruleCatches "DL3016" "RUN npm install @myorg/privatepackage"- onBuildRuleCatches "DL3016" "RUN npm install @myorg/privatepackage"- it "version not pinned multiple packages" $ do- ruleCatches "DL3016" "RUN npm install express sax@0.1.1"- onBuildRuleCatches "DL3016" "RUN npm install express sax@0.1.1"- it "version not pinned with --global" $ do- ruleCatches "DL3016" "RUN npm install --global express"- onBuildRuleCatches "DL3016" "RUN npm install --global express"- it "commit not pinned for git+ssh" $ do- ruleCatches "DL3016" "RUN npm install git+ssh://git@github.com:npm/npm.git"- onBuildRuleCatches "DL3016" "RUN npm install git+ssh://git@github.com:npm/npm.git"- it "commit not pinned for git+http" $ do- ruleCatches "DL3016" "RUN npm install git+http://isaacs@github.com/npm/npm"- onBuildRuleCatches "DL3016" "RUN npm install git+http://isaacs@github.com/npm/npm"- it "commit not pinned for git+https" $ do- ruleCatches- "DL3016"- "RUN npm install git+https://isaacs@github.com/npm/npm.git"- onBuildRuleCatches- "DL3016"- "RUN npm install git+https://isaacs@github.com/npm/npm.git"- it "commit not pinned for git" $ do- ruleCatches "DL3016" "RUN npm install git://github.com/npm/npm.git"- onBuildRuleCatches "DL3016" "RUN npm install git://github.com/npm/npm.git"- it "don't fire on loglevel flag" $ do- ruleCatchesNot "DL3016" "RUN npm install --loglevel verbose sax@0.1.1"- onBuildRuleCatchesNot "DL3016" "RUN npm install --loglevel verbose sax@0.1.1"
− test/DL3018.hs
@@ -1,90 +0,0 @@-module DL3018 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3018 - Pin versions in `apk add`." $ do- it "apk add version pinning single" $ do- ruleCatches "DL3018" "RUN apk add flex"- onBuildRuleCatches "DL3018" "RUN apk add flex"- it "apk add no version pinning single" $ do- ruleCatchesNot "DL3018" "RUN apk add flex=2.6.4-r1"- onBuildRuleCatchesNot "DL3018" "RUN apk add flex=2.6.4-r1"- it "apk add version pinned chained" $- let dockerFile =- [ "RUN apk add --no-cache flex=2.6.4-r1 \\",- " && pip install -r requirements.txt"- ]- in do- ruleCatchesNot "DL3018" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile- it "apk add version pinned regression" $- let dockerFile =- [ "RUN apk add --no-cache \\",- "flex=2.6.4-r1 \\",- "libffi=3.2.1-r3 \\",- "python2=2.7.13-r1 \\",- "libbz2=1.0.6-r5"- ]- in do- ruleCatchesNot "DL3018" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile- it "apk add version pinned regression - one missed" $- let dockerFile =- [ "RUN apk add --no-cache \\",- "flex=2.6.4-r1 \\",- "libffi \\",- "python2=2.7.13-r1 \\",- "libbz2=1.0.6-r5"- ]- in do- ruleCatches "DL3018" $ Text.unlines dockerFile- onBuildRuleCatches "DL3018" $ Text.unlines dockerFile- it "apk add virtual package" $- let dockerFile =- [ "RUN apk add \\",- "--virtual build-dependencies \\",- "python-dev=1.1.1 build-base=2.2.2 wget=3.3.3 \\",- "&& pip install -r requirements.txt \\",- "&& python setup.py install \\",- "&& apk del build-dependencies"- ]- in do- ruleCatchesNot "DL3018" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile- it "apk add with repository without equal sign" $- let dockerFile =- [ "RUN apk add --no-cache \\",- "--repository https://nl.alpinelinux.org/alpine/edge/testing \\",- "flow=0.78.0-r0"- ]- in do- ruleCatchesNot "DL3018" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile- it "apk add with repository with equal sign" $- let dockerFile =- [ "RUN apk add --no-cache \\",- "--repository=https://nl.alpinelinux.org/alpine/edge/testing \\",- "flow=0.78.0-r0"- ]- in do- ruleCatchesNot "DL3018" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile- it "apk add with repository (-X) without equal sign" $- let dockerFile =- [ "RUN apk add --no-cache \\",- "-X https://nl.alpinelinux.org/alpine/edge/testing \\",- "flow=0.78.0-r0"- ]- in do- ruleCatchesNot "DL3018" $ Text.unlines dockerFile- onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile-- it "don't trigger when installing from .apk file" $ do- ruleCatchesNot "DL3018" "RUN apk add mypackage-1.1.1.apk"- onBuildRuleCatchesNot "DL3018" "RUN apk add mypackage-1.1.1.apk"
− test/DL3019.hs
@@ -1,25 +0,0 @@-module DL3019 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3019 - Use the --no-cache switch." $ do- it "apk add with --no-cache" $ do- ruleCatches "DL3019" "RUN apk add flex=2.6.4-r1"- onBuildRuleCatches "DL3019" "RUN apk add flex=2.6.4-r1"- it "apk add without --no-cache" $ do- ruleCatchesNot "DL3019" "RUN apk add --no-cache flex=2.6.4-r1"- onBuildRuleCatchesNot "DL3019" "RUN apk add --no-cache flex=2.6.4-r1"- it "don't warn: apk add with BuildKit cache mount" $ do- ruleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/apk apk add -U curl=7.77.0"- onBuildRuleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/apk apk add -U curl=7.77.0"- it "don't warn: apk add with BuildKit cache mount in wrong dir and --no-cache" $ do- ruleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add --no-cache -U curl=7.77.0"- onBuildRuleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add --no-cache -U curl=7.77.0"- it "warn: apk add with BuildKit cache mount to wrong dir" $ do- ruleCatches "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add -U curl=7.77.0"- onBuildRuleCatches "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add -U curl=7.77.0"
− test/DL3020.hs
@@ -1,18 +0,0 @@-module DL3020 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3020 - Use `COPY` instead of `ADD` for files and folders." $ do- it "add for tar" $ ruleCatchesNot "DL3020" "ADD file.tar /usr/src/app/"- it "add for gzip" $ ruleCatchesNot "DL3020" "ADD file.gz /usr/src/app/"- it "add for bz2" $ ruleCatchesNot "DL3020" "ADD file.bz2 /usr/src/app/"- it "add for xz" $ ruleCatchesNot "DL3020" "ADD file.xz /usr/src/app/"- it "add for tgz" $ ruleCatchesNot "DL3020" "ADD file.tgz /usr/src/app/"- it "add for url" $ ruleCatchesNot "DL3020" "ADD http://file.com /usr/src/app/"- it "using add" $ ruleCatches "DL3020" "ADD file /usr/src/app/"- it "warn for zip" $ ruleCatches "DL3020" "ADD file.zip /usr/src/app/"
− test/DL3021.hs
@@ -1,13 +0,0 @@-module DL3021 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3021 - `COPY` with more than 2 arguments requires the last argument to end with `/`" $ do- it "no warn on 2 args" $ ruleCatchesNot "DL3021" "COPY foo bar"- it "warn on 3 args" $ ruleCatches "DL3021" "COPY foo bar baz"- it "no warn on 3 args" $ ruleCatchesNot "DL3021" "COPY foo bar baz/"
− test/DL3022.hs
@@ -1,29 +0,0 @@-module DL3022 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3022 - `COPY --from` should reference a previously defined `FROM` alias" $ do- it "warn on missing alias" $ ruleCatches "DL3022" "COPY --from=foo bar ."- it "warn on alias defined after" $- let dockerFile =- [ "FROM scratch",- "COPY --from=build foo .",- "FROM node as build",- "RUN baz"- ]- in ruleCatches "DL3022" $ Text.unlines dockerFile- it "don't warn on correctly defined aliases" $- let dockerFile =- [ "FROM scratch as build",- "RUN foo",- "FROM node",- "COPY --from=build foo .",- "RUN baz"- ]- in ruleCatchesNot "DL3022" $ Text.unlines dockerFile
− test/DL3023.hs
@@ -1,26 +0,0 @@-module DL3023 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3023 - `COPY --from` cannot reference its own `FROM` alias" $ do- it "warn on copying from your the same FROM" $- let dockerFile =- [ "FROM node as foo",- "COPY --from=foo bar ."- ]- in ruleCatches "DL3023" $ Text.unlines dockerFile- it "don't warn on copying from other sources" $- let dockerFile =- [ "FROM scratch as build",- "RUN foo",- "FROM node as run",- "COPY --from=build foo .",- "RUN baz"- ]- in ruleCatchesNot "DL3023" $ Text.unlines dockerFile
− test/DL3024.hs
@@ -1,27 +0,0 @@-module DL3024 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3024 - Duplicate aliases" $ do- it "warn on duplicate aliases" $- let dockerFile =- [ "FROM node as foo",- "RUN something",- "FROM scratch as foo",- "RUN something"- ]- in ruleCatches "DL3024" $ Text.unlines dockerFile- it "don't warn on unique aliases" $- let dockerFile =- [ "FROM scratch as build",- "RUN foo",- "FROM node as run",- "RUN baz"- ]- in ruleCatchesNot "DL3024" $ Text.unlines dockerFile
− test/DL3025.hs
@@ -1,36 +0,0 @@-module DL3025 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3025 - Use arguments JSON notation for `CMD` and `ENTRYPOINT` arguments" $ do- it "warn on ENTRYPOINT" $- let dockerFile =- [ "FROM node as foo",- "ENTRYPOINT something"- ]- in ruleCatches "DL3025" $ Text.unlines dockerFile- it "don't warn on ENTRYPOINT json notation" $- let dockerFile =- [ "FROM scratch as build",- "ENTRYPOINT [\"foo\", \"bar\"]"- ]- in ruleCatchesNot "DL3025" $ Text.unlines dockerFile- it "warn on CMD" $- let dockerFile =- [ "FROM node as foo",- "CMD something"- ]- in ruleCatches "DL3025" $ Text.unlines dockerFile- it "don't warn on CMD json notation" $- let dockerFile =- [ "FROM scratch as build",- "CMD [\"foo\", \"bar\"]",- "CMD [ \"foo\", \"bar\" ]"- ]- in ruleCatchesNot "DL3025" $ Text.unlines dockerFile
− test/DL3026.hs
@@ -1,56 +0,0 @@-module DL3026 (tests) where--import Data.Map as Map-import Data.Text as Text-import Hadolint.Process-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL2036 - Use only an allowed registry in the FROM image" $ do- it "does not warn on empty allowed registries" $ do- let dockerFile =- [ "FROM random.com/debian"- ]- ruleCatchesNot "DL3026" $ Text.unlines dockerFile-- it "warn on non-allowed registry" $ do- let dockerFile =- [ "FROM random.com/debian"- ]- let ?rulesConfig = Hadolint.Process.RulesConfig ["docker.io"] Map.empty False- ruleCatches "DL3026" $ Text.unlines dockerFile-- it "does not warn on allowed registries" $ do- let dockerFile =- [ "FROM random.com/debian"- ]- let ?rulesConfig = Hadolint.Process.RulesConfig ["x.com", "random.com"] Map.empty False- ruleCatchesNot "DL3026" $ Text.unlines dockerFile-- it "doesn't warn on scratch image" $ do- let dockerFile =- [ "FROM scratch"- ]- let ?rulesConfig = Hadolint.Process.RulesConfig ["x.com", "random.com"] Map.empty False- ruleCatchesNot "DL3026" $ Text.unlines dockerFile-- it "allows boths all forms of docker.io" $ do- let dockerFile =- [ "FROM ubuntu:18.04 AS builder1",- "FROM zemanlx/ubuntu:18.04 AS builder2",- "FROM docker.io/zemanlx/ubuntu:18.04 AS builder3"- ]- let ?rulesConfig = Hadolint.Process.RulesConfig ["docker.io"] Map.empty False- ruleCatchesNot "DL3026" $ Text.unlines dockerFile-- it "allows using previous stages" $ do- let dockerFile =- [ "FROM random.com/foo AS builder1",- "FROM builder1 AS builder2"- ]- let ?rulesConfig = Hadolint.Process.RulesConfig ["random.com"] Map.empty False- ruleCatchesNot "DL3026" $ Text.unlines dockerFile
− test/DL3027.hs
@@ -1,19 +0,0 @@-module DL3027 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3027 - Do not use `apt` as it is meant to be a end-user tool, use `apt-get` or `apt-cache` instead" $ do- it "apt" $- let dockerFile =- [ "FROM ubuntu",- "RUN apt install python"- ]- in do- ruleCatches "DL3027" $ Text.unlines dockerFile- onBuildRuleCatches "DL3027" $ Text.unlines dockerFile
− test/DL3028.hs
@@ -1,42 +0,0 @@-module DL3028 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3028 - Pin versions in gem install." $- describe "version pinning" $ do- describe "i" $ do- it "unpinned" $ do- ruleCatches "DL3028" "RUN gem i bundler"- onBuildRuleCatches "DL3028" "RUN gem i bundler"- it "pinned" $ do- ruleCatchesNot "DL3028" "RUN gem i bundler:1"- onBuildRuleCatchesNot "DL3028" "RUN gem i bundler:1"- it "multi" $ do- ruleCatches "DL3028" "RUN gem i bunlder:1 nokogiri"- onBuildRuleCatches "DL3028" "RUN gem i bunlder:1 nokogiri"- ruleCatchesNot "DL3028" "RUN gem i bunlder:1 nokogirii:1"- onBuildRuleCatchesNot "DL3028" "RUN gem i bunlder:1 nokogiri:1"- describe "install" $ do- it "unpinned" $ do- ruleCatches "DL3028" "RUN gem install bundler"- onBuildRuleCatches "DL3028" "RUN gem install bundler"- it "pinned" $ do- ruleCatchesNot "DL3028" "RUN gem install bundler:1"- onBuildRuleCatchesNot "DL3028" "RUN gem install bundler:1"- it "does not warn on -v" $ do- ruleCatchesNot "DL3028" "RUN gem install bundler -v '2.0.1'"- onBuildRuleCatchesNot "DL3028" "RUN gem install bundler -v '2.0.1'"- it "does not warn on --version without =" $ do- ruleCatchesNot "DL3028" "RUN gem install bundler --version '2.0.1'"- onBuildRuleCatchesNot "DL3028" "RUN gem install bundler --version '2.0.1'"- it "does not warn on --version with =" $ do- ruleCatchesNot "DL3028" "RUN gem install bundler --version='2.0.1'"- onBuildRuleCatchesNot "DL3028" "RUN gem install bundler --version='2.0.1'"- it "does not warn on extra flags" $ do- ruleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 -- --use-system-libraries=true"- onBuildRuleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 -- --use-system-libraries=true"
− test/DL3029.hs
@@ -1,12 +0,0 @@-module DL3029 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3029 - Do not use --platform flag with FROM." $ do- it "explicit platform flag" $ ruleCatches "DL3029" "FROM --platform=linux debian:jessie"- it "no platform flag" $ ruleCatchesNot "DL3029" "FROM debian:jessie"
− test/DL3030.hs
@@ -1,18 +0,0 @@-module DL3030 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3030 - Use the `-y` switch to avoid manual input `yum install -y <package>`" $ do- it "not ok when not using `-y` switch" $ do- ruleCatches "DL3030" "RUN yum install httpd-2.4.24 && yum clean all"- onBuildRuleCatches "DL3030" "RUN yum install httpd-2.4.24 && yum clean all"- it "ok when using `-y` switch" $ do- ruleCatchesNot "DL3030" "RUN yum install -y httpd-2.4.24 && yum clean all"- ruleCatchesNot "DL3030" "RUN bash -c `# not even a yum command`"- onBuildRuleCatchesNot "DL3030" "RUN yum install -y httpd-2.4.24 && yum clean all"- onBuildRuleCatchesNot "DL3030" "RUN bash -c `# not even a yum command`"
− test/DL3032.hs
@@ -1,18 +0,0 @@-module DL3032 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3032 - `yum clean all` missing after yum command." $ do- it "not ok with no clean all" $ do- ruleCatches "DL3032" "RUN yum install -y mariadb-10.4"- onBuildRuleCatches "DL3032" "RUN yum install -y mariadb-10.4"- it "ok with yum clean all " $ do- ruleCatchesNot "DL3032" "RUN yum install -y mariadb-10.4 && yum clean all"- ruleCatchesNot "DL3032" "RUN bash -c `# not even a yum command`"- onBuildRuleCatchesNot "DL3032" "RUN yum install -y mariadb-10.4 && yum clean all"- onBuildRuleCatchesNot "DL3032" "RUN bash -c `# not even a yum command`"
− test/DL3033.hs
@@ -1,18 +0,0 @@-module DL3033 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3033 - Specify version with `yum install -y <package>-<version>`" $ do- it "not ok wihout yum version pinning" $ do- ruleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"- onBuildRuleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"- it "ok with yum version pinning" $ do- ruleCatchesNot "DL3033" "RUN yum install -y tomcat-9.2 && yum clean all"- ruleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"- onBuildRuleCatchesNot "DL3033" "RUN yum install -y tomcat-9.2 && yum clean all"- onBuildRuleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"
− test/DL3034.hs
@@ -1,18 +0,0 @@-module DL3034 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3034 - Non-interactive switch missing from zypper command: `zypper install -y`" $ do- it "not ok without non-interactive switch" $ do- ruleCatches "DL3034" "RUN zypper install httpd=2.4.24 && zypper clean"- onBuildRuleCatches "DL3034" "RUN zypper install httpd=2.4.24 && zypper clean"- it "ok with non-interactive switch present" $ do- ruleCatchesNot "DL3034" "RUN zypper install -y httpd=2.4.24 && zypper clean"- ruleCatchesNot "DL3034" "RUN zypper install --no-confirm httpd=2.4.24 && zypper clean"- onBuildRuleCatchesNot "DL3034" "RUN zypper install -y httpd=2.4.24 && zypper clean"- onBuildRuleCatchesNot "DL3034" "RUN zypper install --no-confirm httpd=2.4.24 && zypper clean"
− test/DL3035.hs
@@ -1,15 +0,0 @@-module DL3035 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3035" $ do- it "not ok: zypper dist-upgrade" $ do- ruleCatches "DL3035" "RUN zypper dist-upgrade"- ruleCatches "DL3035" "RUN zypper dup"- onBuildRuleCatches "DL3035" "RUN zypper dist-upgrade"- onBuildRuleCatches "DL3035" "RUN zypper dup"
− test/DL3036.hs
@@ -1,18 +0,0 @@-module DL3036 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3036 - `zypper clean` missing after zypper use." $ do- it "not ok without zypper clean" $ do- ruleCatches "DL3036" "RUN zypper install -y mariadb=10.4"- onBuildRuleCatches "DL3036" "RUN zypper install -y mariadb=10.4"- it "ok with zypper clean" $ do- ruleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper clean"- ruleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper cc"- onBuildRuleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper clean"- onBuildRuleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper cc"
− test/DL3037.hs
@@ -1,28 +0,0 @@-module DL3037 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3037 - Specify version with `zypper install -y <package>[=]<version>`." $ do- -- NOTE: In Haskell strings, '\' has to be escaped. And in shell commands, '>'- -- and '<' have to be escaped. Hence the double escaping.- it "not ok without zypper version pinning" $ do- ruleCatches "DL3037" "RUN zypper install -y tomcat && zypper clean"- onBuildRuleCatches "DL3037" "RUN zypper install -y tomcat && zypper clean"- it "ok with different variants of zypper version pinning" $ do- ruleCatchesNot "DL3037" "RUN zypper install -y tomcat=9.0.39 && zypper clean"- ruleCatchesNot "DL3037" "RUN zypper install -y tomcat\\>=9.0 && zypper clean"- ruleCatchesNot "DL3037" "RUN zypper install -y tomcat\\>9.0 && zypper clean"- ruleCatchesNot "DL3037" "RUN zypper install -y tomcat\\<=9.0 && zypper clean"- ruleCatchesNot "DL3037" "RUN zypper install -y tomcat\\<9.0 && zypper clean"- ruleCatchesNot "DL3037" "RUN zypper install -y tomcat-9.0.39-1.rpm && zypper clean"- onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat=9.0.39 && zypper clean"- onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat\\>=9.0 && zypper clean"- onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat\\>9.0 && zypper clean"- onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat\\<=9.0 && zypper clean"- onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat\\<9.0 && zypper clean"- onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat-9.0.39-1.rpm && zypper clean"
− test/DL3038.hs
@@ -1,18 +0,0 @@-module DL3038 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3038 - Use the `-y` switch to avoid manual input `dnf install -y <package>`" $ do- it "not ok without dnf non-interactive flag" $ do- ruleCatches "DL3038" "RUN dnf install httpd-2.4.24 && dnf clean all"- onBuildRuleCatches "DL3038" "RUN dnf install httpd-2.4.24 && dnf clean all"- it "ok with dnf non-interactive flag" $ do- ruleCatchesNot "DL3038" "RUN dnf install -y httpd-2.4.24 && dnf clean all"- ruleCatchesNot "DL3038" "RUN notdnf install httpd"- onBuildRuleCatchesNot "DL3038" "RUN dnf install -y httpd-2.4.24 && dnf clean all"- onBuildRuleCatchesNot "DL3038" "RUN notdnf install httpd"
− test/DL3040.hs
@@ -1,18 +0,0 @@-module DL3040 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3040 - `dnf clean all` missing after dnf command." $ do- it "no ok without dnf clean all" $ do- ruleCatches "DL3040" "RUN dnf install -y mariadb-10.4"- onBuildRuleCatches "DL3040" "RUN dnf install -y mariadb-10.4"- it "ok with dnf clean all" $ do- ruleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && dnf clean all"- ruleCatchesNot "DL3040" "RUN notdnf install mariadb"- onBuildRuleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && dnf clean all"- onBuildRuleCatchesNot "DL3040" "RUN notdnf install mariadb"
− test/DL3041.hs
@@ -1,18 +0,0 @@-module DL3041 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3041 - Specify version with `dnf install -y <package>-<version>`" $ do- it "not ok without dnf version pinning" $ do- ruleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"- onBuildRuleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"- it "ok with dnf version pinning" $ do- ruleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"- ruleCatchesNot "DL3041" "RUN notdnf install tomcat"- onBuildRuleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"- onBuildRuleCatchesNot "DL3041" "RUN notdnf install tomcat"
− test/DL3042.hs
@@ -1,86 +0,0 @@-module DL3042 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3042 - Avoid cache directory with `pip install --no-cache-dir <package>`." $ do- it "pip2 --no-cache-dir not used" $ do- ruleCatches "DL3042" "RUN pip2 install MySQL_python"- onBuildRuleCatches "DL3042" "RUN pip2 install MySQL_python"- it "pip3 --no-cache-dir not used" $ do- ruleCatches "DL3042" "RUN pip3 install MySQL_python"- onBuildRuleCatches "DL3042" "RUN pip3 install MySQL_python"- it "pip --no-cache-dir not used" $ do- ruleCatches "DL3042" "RUN pip install MySQL_python"- onBuildRuleCatches "DL3042" "RUN pip install MySQL_python"- it "pip2 --no-cache-dir used" $ do- ruleCatchesNot "DL3042" "RUN pip2 install MySQL_python --no-cache-dir"- onBuildRuleCatchesNot "DL3042" "RUN pip2 install MySQL_python --no-cache-dir"- it "pip3 --no-cache-dir used" $ do- ruleCatchesNot "DL3042" "RUN pip3 install --no-cache-dir MySQL_python"- onBuildRuleCatchesNot "DL3042" "RUN pip3 install --no-cache-dir MySQL_python"- it "pip --no-cache-dir used" $ do- ruleCatchesNot "DL3042" "RUN pip install MySQL_python --no-cache-dir"- onBuildRuleCatchesNot "DL3042" "RUN pip install MySQL_python --no-cache-dir"- it "don't match on pipx" $ do- ruleCatchesNot "DL3042" "RUN pipx install software"- onBuildRuleCatchesNot "DL3042" "Run pipx install software"- it "don't match on pipenv" $ do- ruleCatchesNot "DL3042" "RUN pipenv install library"- onBuildRuleCatchesNot "DL3042" "RUN pipenv install library"-- -- ENV won't work with the onBuildRuleCatches[Not] wrapper:- it "respect ENV PIP_NO_CACHE_DIR with truthy values" $ do- ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=1\nRUN pip install MySQL_python"- ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=on\nRUN pip install MySQL_python"- ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=yes\nRUN pip install MySQL_python"- ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=true\nRUN pip install MySQL_python"- it "respect RUN PIP_NO_CACHE_DIR=... with truthy values" $ do- ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=1 pip install MySQL_python"- ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=on pip install MySQL_python"- ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=yes pip install MySQL_python"- ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=true pip install MySQL_python"- it "respect RUN export PIP_NO_CACHE_DIR=... with truthy values" $ do- ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=1 && pip install MySQL_python"- ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=on && pip install MySQL_python"- ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=yes && pip install MySQL_python"- ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=true && pip install MySQL_python"- it "respect ENV PIP_NO_CACHE_DIR with falsy values" $ do- ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=0\nRUN pip install MySQL_python"- ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=off\nRUN pip install MySQL_python"- ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=no\nRUN pip install MySQL_python"- ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=false\nRUN pip install MySQL_python"- it "respect RUN PIP_NO_CACHE_DIR=... with falsy values" $ do- ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=0 pip install MySQL_python"- ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=off pip install MySQL_python"- ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=no pip install MySQL_python"- ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=false pip install MySQL_python"- it "respect RUN export PIP_NO_CACHE_DIR=... with falsy values" $ do- ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=0 && pip install MySQL_python"- ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=off && pip install MySQL_python"- ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=no && pip install MySQL_python"- ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=false && pip install MySQL_python"-- it "don't trigger if PIP_NO_CACHE_DIR is inherited" $- let dockerFile = Text.unlines- [ "FROM debian:buster as base",- "ENV PIP_NO_CACHE_DIR=1",- "FROM base",- "RUN pip install six"- ]- in do- ruleCatchesNot "DL3042" dockerFile- it "trigger if PIP_NO_CACHE_DIR is not inherited" $- let dockerFile = Text.unlines- [ "FROM debian:buster as base",- "ENV PIP_NO_CACHE_DIR=1",- "FROM debian:buster",- "RUN pip install six"- ]- in do- ruleCatches "DL3042" dockerFile
− test/DL3043.hs
@@ -1,43 +0,0 @@-module DL3043 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3043 - `ONBUILD`, `FROM` or `MAINTAINER` triggered from within `ONBUILD` instruction." $ do- it "error when using `ONBUILD` within `ONBUILD`" $- let dockerFile =- [ "ONBUILD ONBUILD RUN anything"- ]- in ruleCatches "DL3043" $ Text.unlines dockerFile- it "error when using `FROM` within `ONBUILD`" $- let dockerFile =- [ "ONBUILD FROM debian:buster"- ]- in ruleCatches "DL3043" $ Text.unlines dockerFile- it "error when using `MAINTAINER` within `ONBUILD`" $- let dockerFile =- [ "ONBUILD MAINTAINER \"BoJack Horseman\""- ]- in ruleCatches "DL3043" $ Text.unlines dockerFile- it "ok with `ADD`" $ ruleCatchesNot "DL3043" "ONBUILD ADD anything anywhere"- it "ok with `USER`" $ ruleCatchesNot "DL3043" "ONBUILD USER anything"- it "ok with `LABEL`" $ ruleCatchesNot "DL3043" "ONBUILD LABEL bla=\"blubb\""- it "ok with `STOPSIGNAL`" $ ruleCatchesNot "DL3043" "ONBUILD STOPSIGNAL anything"- it "ok with `COPY`" $ ruleCatchesNot "DL3043" "ONBUILD COPY anything anywhere"- it "ok with `RUN`" $ ruleCatchesNot "DL3043" "ONBUILD RUN anything"- it "ok with `CMD`" $ ruleCatchesNot "DL3043" "ONBUILD CMD anything"- it "ok with `SHELL`" $ ruleCatchesNot "DL3043" "ONBUILD SHELL anything"- it "ok with `WORKDIR`" $ ruleCatchesNot "DL3043" "ONBUILD WORKDIR anything"- it "ok with `EXPOSE`" $ ruleCatchesNot "DL3043" "ONBUILD EXPOSE 69"- it "ok with `VOLUME`" $ ruleCatchesNot "DL3043" "ONBUILD VOLUME anything"- it "ok with `ENTRYPOINT`" $ ruleCatchesNot "DL3043" "ONBUILD ENTRYPOINT anything"- it "ok with `ENV`" $ ruleCatchesNot "DL3043" "ONBUILD ENV MYVAR=\"bla\""- it "ok with `ARG`" $ ruleCatchesNot "DL3043" "ONBUILD ARG anything"- it "ok with `HEALTHCHECK`" $ ruleCatchesNot "DL3043" "ONBUILD HEALTHCHECK NONE"- it "ok with `FROM` outside of `ONBUILD`" $ ruleCatchesNot "DL3043" "FROM debian:buster"- it "ok with `MAINTAINER` outside of `ONBUILD`" $ ruleCatchesNot "DL3043" "MAINTAINER \"Some Guy\""
− test/DL3044.hs
@@ -1,42 +0,0 @@-module DL3044 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3044 - Do not refer to an environment variable within the same `ENV` statement where it is defined." $ do- it "ok with normal ENV" $- ruleCatchesNot "DL3044" "ENV BLA=\"blubb\"\nENV BLUBB=\"${BLA}/blubb\""- it "ok with partial match 1" $- ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"${FOOBLA}/blubb\""- it "ok with partial match 2" $- ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"${BLAFOO}/blubb\""- it "ok with partial match 3" $- ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$FOOBLA/blubb\""- it "ok with partial match 4" $- ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$BLAFOO/blubb\""- it "fail with partial match 5" $- ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$BLA/$BLAFOO/blubb\""- it "ok with parial match 6" $- ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"BLA/$BLAFOO/BLA\""- it "ok when previously defined in `ARG`" $- ruleCatchesNot "DL3044" "ARG BLA\nENV BLA=${BLA}"- it "ok when previously defined in `ENV`" $- ruleCatchesNot "DL3044" "ENV BLA blubb\nENV BLA=${BLA}"- it "ok with referencing a variable on its own right hand side" $- ruleCatchesNot "DL3044" "ENV PATH=/bla:${PATH}"- it "ok with referencing a variable on its own right side twice in different `ENV`s" $- ruleCatchesNot "DL3044" "ENV PATH=/bla:${PATH}\nENV PATH=/blubb:${PATH}"- it "fail when referencing a variable on its own right side twice within the same `ENV`" $- ruleCatches "DL3044" "ENV PATH=/bla:${PATH} PATH=/blubb:${PATH}"- it "fail with selfreferencing with curly braces ENV" $- ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"${BLA}/blubb\""- it "fail with selfreferencing without curly braces ENV" $- ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$BLA/blubb\""- it "fail with full match 1" $- ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$BLA\""- it "fail with full match 2" $- ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"${BLA}\""
− test/DL3045.hs
@@ -1,156 +0,0 @@-module DL3045 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec--tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3045 - `COPY` without `WORKDIR` set" $ do- it "ok: `COPY` with absolute destination and no `WORKDIR` set" $ do- ruleCatchesNot "DL3045" "COPY bla.sh /usr/local/bin/blubb.sh"- onBuildRuleCatchesNot "DL3045" "COPY bla.sh /usr/local/bin/blubb.sh"-- it "ok: `COPY` with absolute destination and no `WORKDIR` set with quotes" $ do- ruleCatchesNot "DL3045" "COPY bla.sh \"/usr/local/bin/blubb.s\""- onBuildRuleCatchesNot "DL3045" "COPY bla.sh \"/usr/local/bin/blubb.sh\""-- it "ok: `COPY` with absolute destination and no `WORKDIR` set - windows" $ do- ruleCatchesNot "DL3045" "COPY bla.sh c:\\system32\\blubb.sh"- onBuildRuleCatchesNot "DL3045" "COPY bla.sh d:\\mypath\\blubb.sh"-- it "ok: `COPY` with absolute destination and no `WORKDIR` set - windows with quotes" $ do- ruleCatchesNot "DL3045" "COPY bla.sh \"c:\\system32\\blubb.sh\""- onBuildRuleCatchesNot "DL3045" "COPY bla.sh \"d:\\mypath\\blubb.sh\""-- it "ok: `COPY` with absolute destination and no `WORKDIR` set - windows with alternative paths" $ do- ruleCatchesNot "DL3045" "COPY bla.sh c:/system32/blubb.sh"- onBuildRuleCatchesNot "DL3045" "COPY bla.sh d:/mypath/blubb.sh"-- it "ok: `COPY` with relative destination and `WORKDIR` set" $ do- ruleCatchesNot "DL3045" "FROM scratch\nWORKDIR /usr\nCOPY bla.sh blubb.sh"- onBuildRuleCatchesNot "FROM scratch\nDL3045" "WORKDIR /usr\nCOPY bla.sh blubb.sh"-- it "ok: `COPY` with relative destination and `WORKDIR` set - windows" $ do- ruleCatchesNot "DL3045" "FROM scratch\nWORKDIR c:\\system32\nCOPY bla.sh blubb.sh"- onBuildRuleCatchesNot "DL3045" "FROM scratch\nWORKDIR c:\\system32\nCOPY bla.sh blubb.sh"-- it "ok: `COPY` with destination being an environment variable 1" $ do- ruleCatchesNot "DL3045" "COPY src.sh ${SRC_BASE_ENV}"- onBuildRuleCatchesNot "DL3045" "COPY src.sh ${SRC_BASE_ENV}"-- it "ok: `COPY` with destination being an environment variable 2" $ do- ruleCatchesNot "DL3045" "COPY src.sh $SRC_BASE_ENV"- onBuildRuleCatchesNot "DL3045" "COPY src.sh $SRC_BASE_ENV"-- it "ok: `COPY` with destination being an environment variable 3" $ do- ruleCatchesNot "DL3045" "COPY src.sh \"${SRC_BASE_ENV}\""- onBuildRuleCatchesNot "DL3045" "COPY src.sh \"${SRC_BASE_ENV}\""-- it "ok: `COPY` with destination being an environment variable 4" $ do- ruleCatchesNot "DL3045" "COPY src.sh \"$SRC_BASE_ENV\""- onBuildRuleCatchesNot "DL3045" "COPY src.sh \"$SRC_BASE_ENV\""-- it "not ok: `COPY` with relative destination and no `WORKDIR` set" $ do- ruleCatches "DL3045" "COPY bla.sh blubb.sh"- onBuildRuleCatches "DL3045" "COPY bla.sh blubb.sh"-- it "not ok: `COPY` with relative destination and no `WORKDIR` set with quotes" $ do- ruleCatches "DL3045" "COPY bla.sh \"blubb.sh\""- onBuildRuleCatches "DL3045" "COPY bla.sh \"blubb.sh\""-- it "not ok: `COPY` to relative destination if `WORKDIR` is set in a previous stage but not inherited" $- let dockerFile =- Text.unlines- [ "FROM debian:buster as stage1",- "WORKDIR /usr",- "FROM debian:buster",- "COPY foo bar"- ]- in do- ruleCatches "DL3045" dockerFile- onBuildRuleCatches "DL3045" dockerFile-- it "not ok: `COPY` to relative destination if `WORKDIR` is set in a previous stage but not inherited - windows" $- let dockerFile =- Text.unlines- [ "FROM microsoft/windowsservercore as stage1",- "WORKDIR c:\\system32",- "FROM microsoft/windowsservercore",- "COPY foo bar"- ]- in do- ruleCatches "DL3045" dockerFile- onBuildRuleCatches "DL3045" dockerFile-- it "ok: `COPY` to relative destination if `WORKDIR` has been set in base image" $- let dockerFile =- Text.unlines- [ "FROM debian:buster as base",- "WORKDIR /usr",- "FROM debian:buster as stage-inbetween",- "RUN foo",- "FROM base",- "COPY foo bar"- ]- in do- ruleCatchesNot "DL3045" dockerFile- onBuildRuleCatchesNot "DL3045" dockerFile-- it "ok: `COPY` to relative destination if `WORKDIR` has been set in base image - windows" $- let dockerFile =- Text.unlines- [ "FROM microsoft/windowsservercore as base",- "WORKDIR c:\\system32",- "FROM microsoft/windowsservercore as stage-inbetween",- "RUN foo",- "FROM base",- "COPY foo bar"- ]- in do- ruleCatchesNot "DL3045" dockerFile- onBuildRuleCatchesNot "DL3045" dockerFile-- it "ok: `COPY` to relative destination if `WORKDIR` has been set in previous stage, deep case" $- let dockerFile =- Text.unlines- [ "FROM debian:buster as base1",- "WORKDIR /usr",- "FROM base1 as base2",- "RUN foo",- "FROM base2",- "COPY foo bar"- ]- in do- ruleCatchesNot "DL3045" dockerFile- onBuildRuleCatchesNot "DL3045" dockerFile-- it "ok: `COPY` to relative destination if `WORKDIR` has been set in previous stage, deep case - windows" $- let dockerFile =- Text.unlines- [ "FROM microsoft/windowsservercore as base1",- "WORKDIR c:\\system32",- "FROM base1 as base2",- "RUN foo",- "FROM base2",- "COPY foo bar"- ]- in do- ruleCatchesNot "DL3045" dockerFile- onBuildRuleCatchesNot "DL3045" dockerFile-- it "ok: `COPY` to relative destination if `WORKDIR` has been set, both within an `ONBUILD` context" $- let dockerFile =- Text.unlines- [ "FROM debian:buster",- "ONBUILD WORKDIR /usr/local/lib",- "ONBUILD COPY foo bar"- ]- in do- ruleCatchesNot "DL3045" dockerFile- onBuildRuleCatchesNot "DL3045" dockerFile-- it "regression: don't crash with single character paths" $ do- ruleCatches "DL3045" "COPY a b"- onBuildRuleCatches "DL3045" "COPY a b"
− test/DL3046.hs
@@ -1,15 +0,0 @@-module DL3046 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3046 - `useradd` without flag `-l` and high UID will result in excessively large Image." $ do- it "ok with `useradd` alone" $ ruleCatchesNot "DL3046" "RUN useradd luser"- it "ok with `useradd` short uid" $ ruleCatchesNot "DL3046" "RUN useradd -u 12345 luser"- it "ok with `useradd` long uid and flag `-l`" $ ruleCatchesNot "DL3046" "RUN useradd -l -u 123456 luser"- it "ok with `useradd` and just flag `-l`" $ ruleCatchesNot "DL3046" "RUN useradd -l luser"- it "warn when `useradd` and long uid without flag `-l`" $ ruleCatches "DL3046" "RUN useradd -u 123456 luser"
− test/DL3047.hs
@@ -1,71 +0,0 @@-module DL3047 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3047 - `wget` without flag `--progress` will result in excessively bloated build logs when downloading larger files." $ do- it "warns when using wget without --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget my.xyz"- ]- in ruleCatches "DL3047" $ Text.unlines dockerFile- it "does not warn when running with --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget --progress=dot:giga my.xyz"- ]- in ruleCatchesNot "DL3047" $ Text.unlines dockerFile- it "does not warn when running with -q (quiet short option) and without --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget -q my.xyz"- ]- in ruleCatchesNot "DL3047" $ Text.unlines dockerFile- it "does not warn when running with --quiet (quiet long option) and without --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget --quiet my.xyz"- ]- in ruleCatchesNot "DL3047" $ Text.unlines dockerFile- it "does not warn when running with -nv (no-verbose short option) and without --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget -nv my.xyz"- ]- in ruleCatchesNot "DL3047" $ Text.unlines dockerFile- it "does not warn when running with --no-verbose (no-verbose long option) and without --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget --no-verbose my.xyz"- ]- in ruleCatchesNot "DL3047" $ Text.unlines dockerFile- it "does not warn when running with --output-file (output-file long option) and without --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget --output-file=/tmp/wget.log my.xyz"- ]- in ruleCatchesNot "DL3047" $ Text.unlines dockerFile- it "does not warn when running with -o (output-file long option) and without --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget -o /tmp/wget.log my.xyz"- ]- in ruleCatchesNot "DL3047" $ Text.unlines dockerFile- it "does not warn when running with --append-output (append-output long option) and without --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget --append-output=/tmp/wget.log my.xyz"- ]- in ruleCatchesNot "DL3047" $ Text.unlines dockerFile- it "does not warn when running with -a (append-output long option) and without --progress option" $- let dockerFile =- [ "FROM node as foo",- "RUN wget -a /tmp/wget.log my.xyz"- ]- in ruleCatchesNot "DL3047" $ Text.unlines dockerFile
− test/DL3048.hs
@@ -1,37 +0,0 @@-module DL3048 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3048 - Invalid Label Key Rule" $ do- it "not ok with reserved namespace" $ do- ruleCatches "DL3048" "LABEL com.docker.label=\"foo\""- ruleCatches "DL3048" "LABEL io.docker.label=\"foo\""- ruleCatches "DL3048" "LABEL org.dockerproject.label=\"foo\""- onBuildRuleCatches "DL3048" "LABEL com.docker.label=\"foo\""- onBuildRuleCatches "DL3048" "LABEL io.docker.label=\"foo\""- onBuildRuleCatches "DL3048" "LABEL org.dockerproject.label=\"foo\""- it "not ok with invalid character" $ do- ruleCatches "DL3048" "LABEL invalid$character=\"foo\""- onBuildRuleCatches "DL3048" "LABEL invalid$character=\"foo\""- it "not ok with invalid start and end characters" $ do- ruleCatches "DL3048" "LABEL .invalid =\"foo\""- ruleCatches "DL3048" "LABEL -invalid =\"foo\""- ruleCatches "DL3048" "LABEL 1invalid =\"foo\""- onBuildRuleCatches "DL3048" "LABEL .invalid=\"foo\""- onBuildRuleCatches "DL3048" "LABEL -invalid=\"foo\""- onBuildRuleCatches "DL3048" "LABEL 1invalid=\"foo\""- it "not ok with consecutive dividers" $ do- ruleCatches "DL3048" "LABEL invalid..character=\"foo\""- ruleCatches "DL3048" "LABEL invalid--character=\"foo\""- onBuildRuleCatches "DL3048" "LABEL invalid..character=\"foo\""- onBuildRuleCatches "DL3048" "LABEL invalid--character=\"foo\""- it "ok with valid labels" $ do- ruleCatchesNot "DL3048" "LABEL org.valid-key.label3=\"foo\""- ruleCatchesNot "DL3048" "LABEL validlabel=\"foo\""- onBuildRuleCatchesNot "DL3048" "LABEL org.valid-key.label3=\"foo\""- onBuildRuleCatchesNot "DL3048" "LABEL validlabel=\"foo\""
− test/DL3049.hs
@@ -1,80 +0,0 @@-module DL3049 (tests) where--import Data.Text as Text-import qualified Data.Map as Map-import qualified Hadolint.Process-import qualified Hadolint.Rule as Rule-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("foo", Rule.RawText)]) False- describe "DL3049 - Missing label rule tests" $ do- -- single stage tests- it "not ok: single stage, no label" $ do- ruleCatches "DL3049" "FROM baseimage"- onBuildRuleCatches "DL3049" "FROM baseimage"- it "not ok: single stage, wrong label" $ do- ruleCatches "DL3049" "FROM baseimage\nLABEL bar=\"baz\""- onBuildRuleCatches "DL3049" "FROM baseimage\nLABEL bar=\"baz\""- it "ok: single stage, label present" $ do- ruleCatchesNot "DL3049" "FROM baseimage\nLABEL foo=\"bar\""- onBuildRuleCatchesNot "DL3049" "FROM baseimage\nLABEL foo=\"bar\""- -- multi stage tests- it "warn twice: two stages, no labels" $- let dockerFile =- [ "FROM stage1",- "FROM stage2"- ]- in assertChecks- (Text.unlines dockerFile)- (failsWith 2 "DL3049")- it "warn twice: two stages, wrong labels only" $- let dockerFile =- [ "FROM stage1",- "LABEL bar=\"baz\"",- "FROM stage2",- "LABEL buzz=\"fuzz\""- ]- in assertChecks- (Text.unlines dockerFile)- (failsWith 2 "DL3049")- it "warn once: two stages, label present in second only" $- let dockerFile =- [ "FROM baseimage",- "FROM newimage",- "LABEL foo=\"bar\""- ]- in assertChecks- (Text.unlines dockerFile)- (failsWith 1 "DL3049")- it "warn once: two stages, no inheritance, wrong label in one" $- let dockerFile =- [ "FROM baseimage",- "LABEL baz=\"bar\"",- "FROM newimage",- "LABEL foo=\"bar\""- ]- in assertChecks- (Text.unlines dockerFile)- (failsWith 1 "DL3049")- it "warn once: two stages, inheritance, label only defined in second stage" $- let dockerFile =- [ "FROM baseimage as base",- "FROM base",- "LABEL foo=\"bar\""- ]- in assertChecks- (Text.unlines dockerFile)- (failsWith 1 "DL3049")- it "don't warn: two stages, inheritance" $- let dockerFile =- [ "FROM baseimage as base",- "LABEL foo=\"bar\"",- "FROM base"- ]- in assertChecks- (Text.unlines dockerFile)- (failsWith 0 "DL3049")
− test/DL3050.hs
@@ -1,25 +0,0 @@-module DL3050 (tests) where--import qualified Data.Map as Map-import qualified Hadolint.Process-import qualified Hadolint.Rule as Rule-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("required", Rule.RawText)]) True- describe "DL3050 - Superfluous label(s) present." $ do- it "ok with no label" $ do- ruleCatchesNot "DL3050" ""- onBuildRuleCatchesNot "DL3050" ""- it "ok with required label" $ do- ruleCatchesNot "DL3050" "LABEL required=\"foo\""- onBuildRuleCatchesNot "DL3050" "LABEL required=\"bar\""- it "not ok with just other label" $ do- ruleCatches "DL3050" "LABEL other=\"bar\""- onBuildRuleCatches "DL3050" "LABEL other=\"bar\""- it "not ok with other label and required label" $ do- ruleCatches "DL3050" "LABEL required=\"foo\" other=\"bar\""- onBuildRuleCatches "DL3050" "LABEL required=\"foo\" other=\"bar\""
− test/DL3051.hs
@@ -1,22 +0,0 @@-module DL3051 (tests) where--import qualified Data.Map as Map-import qualified Hadolint.Process-import qualified Hadolint.Rule as Rule-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("emptylabel", Rule.RawText)]) False- describe "DL3051 - Label `<label>` is empty." $ do- it "not ok with label empty" $ do- ruleCatches "DL3051" "LABEL emptylabel=\"\""- onBuildRuleCatches "DL3051" "LABEL emptylabel=\"\""- it "ok with label not empty" $ do- ruleCatchesNot "DL3051" "LABEL emptylabel=\"foo\""- onBuildRuleCatchesNot "DL3051" "LABEL emptylabel=\"bar\""- it "ok with other label empty" $ do- ruleCatchesNot "DL3051" "LABEL other=\"\""- onBuildRuleCatchesNot "DL3051" "LABEL other=\"\""
− test/DL3052.hs
@@ -1,22 +0,0 @@-module DL3052 (tests) where--import qualified Data.Map as Map-import qualified Hadolint.Process-import qualified Hadolint.Rule as Rule-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("urllabel", Rule.Url)]) False- describe "DL3052 - Label `<label>` is not a valid URL." $ do- it "not ok with label not containing URL" $ do- ruleCatches "DL3052" "LABEL urllabel=\"not-url\""- onBuildRuleCatches "DL3052" "LABEL urllabel=\"not-url\""- it "ok with label containing URL" $ do- ruleCatchesNot "DL3052" "LABEL urllabel=\"http://example.com\""- onBuildRuleCatchesNot "DL3052" "LABEL urllabel=\"http://example.com\""- it "ok with other label not containing URL" $ do- ruleCatchesNot "DL3052" "LABEL other=\"foo\""- onBuildRuleCatchesNot "DL3052" "LABEL other=\"bar\""
− test/DL3053.hs
@@ -1,22 +0,0 @@-module DL3053 (tests) where--import qualified Data.Map as Map-import qualified Hadolint.Process-import qualified Hadolint.Rule as Rule-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("datelabel", Rule.Rfc3339)]) False- describe "DL3053 - Label `<label>` is not a valid time format - must be conform to RFC3339." $ do- it "not ok with label not containing RFC3339 date" $ do- ruleCatches "DL3053" "LABEL datelabel=\"not-date\""- onBuildRuleCatches "DL3053" "LABEL datelabel=\"not-date\""- it "ok with label containing RFC3339 date" $ do- ruleCatchesNot "DL3053" "LABEL datelabel=\"2021-03-10T10:26:33.564595127+01:00\""- onBuildRuleCatchesNot "DL3053" "LABEL datelabel=\"2021-03-10T10:26:33.564595127+01:00\""- it "ok with other label not containing RFC3339 date" $ do- ruleCatchesNot "DL3053" "LABEL other=\"doo\""- onBuildRuleCatchesNot "DL3053" "LABEL other=\"bar\""
− test/DL3054.hs
@@ -1,22 +0,0 @@-module DL3054 (tests) where--import qualified Data.Map as Map-import qualified Hadolint.Process-import qualified Hadolint.Rule as Rule-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("spdxlabel", Rule.Spdx)]) False- describe "DL3054 - Label `<label>` is not a valid SPDX license identifier." $ do- it "not ok with label not containing SPDX identifier" $ do- ruleCatches "DL3054" "LABEL spdxlabel=\"not-spdx\""- onBuildRuleCatches "DL3054" "LABEL spdxlabel=\"not-spdx\""- it "ok with label containing SPDX identifier" $ do- ruleCatchesNot "DL3054" "LABEL spdxlabel=\"BSD-3-Clause\""- onBuildRuleCatchesNot "DL3054" "LABEL spdxlabel=\"MIT\""- it "ok with other label not containing SPDX identifier" $ do- ruleCatchesNot "DL3054" "LABEL other=\"fooo\""- onBuildRuleCatchesNot "DL3054" "LABEL other=\"bar\""
− test/DL3055.hs
@@ -1,25 +0,0 @@-module DL3055 (tests) where--import qualified Data.Map as Map-import qualified Hadolint.Process-import qualified Hadolint.Rule as Rule-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("githash", Rule.GitHash)]) False- describe "DL3055 - Label `<label>` is not a valid git hash." $ do- it "not ok with label not containing git hash" $ do- ruleCatches "DL3055" "LABEL githash=\"not-git-hash\""- onBuildRuleCatches "DL3055" "LABEL githash=\"not-git-hash\""- it "ok with label containing short git hash" $ do- ruleCatchesNot "DL3055" "LABEL githash=\"2dbfae9\""- onBuildRuleCatchesNot "DL3055" "LABEL githash=\"2dbfae9\""- it "ok with label containing long git hash" $ do- ruleCatchesNot "DL3055" "LABEL githash=\"43c572f1272b6b3171dd1db9e41b7027128ce080\""- onBuildRuleCatchesNot "DL3055" "LABEL githash=\"43c572f1272b6b3171dd1db9e41b7027128ce080\""- it "ok with other label not containing git hash" $ do- ruleCatchesNot "DL3055" "LABEL other=\"foo\""- onBuildRuleCatchesNot "DL3055" "LABEL other=\"bar\""
− test/DL3056.hs
@@ -1,22 +0,0 @@-module DL3056 (tests) where--import qualified Data.Map as Map-import qualified Hadolint.Process-import qualified Hadolint.Rule as Rule-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("semver", Rule.SemVer)]) False- describe "DL3056 - Label `<label>` does not conform to semantic versioning." $ do- it "not ok with label not containing semantic version" $ do- ruleCatches "DL3056" "LABEL semver=\"not-sem-ver\""- onBuildRuleCatches "DL3056" "LABEL semver=\"not-sem-ver\""- it "ok with label containing semantic version" $ do- ruleCatchesNot "DL3056" "LABEL semver=\"1.0.0\""- onBuildRuleCatchesNot "DL3056" "LABEL semver=\"2.0.1-rc1\""- it "ok with other label not containing semantic version" $ do- ruleCatchesNot "DL3056" "LABEL other=\"foo\""- onBuildRuleCatchesNot "DL3056" "LABEL other=\"bar\""
− test/DL3057.hs
@@ -1,18 +0,0 @@-module DL3057 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3057 - `HEALTHCHECK instruction missing" $ do- it "warn with no HEALTHCHECK instructions" $- ruleCatches "DL3057" "FROM scratch"- it "ok with one HEALTHCHECK instruction" $- ruleCatchesNot "DL3057" "FROM scratch\nHEALTHCHECK CMD /bin/bla"- it "ok with inheriting HEALTHCHECK instruction" $- ruleCatchesNot "DL3057" "FROM scratch AS base\nHEALTHCHECK CMD /bin/bla\nFROM base"- it "warn when not inheriting with no HEALTHCHECK instruction" $- ruleCatches "DL3057" "FROM scratch AS base\nHEALTHCHECK CMD /bin/bla\nFROM scratch"
− test/DL3058.hs
@@ -1,22 +0,0 @@-module DL3058 (tests) where--import qualified Data.Map as Map-import qualified Hadolint.Process-import qualified Hadolint.Rule as Rule-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("maintainer", Rule.Email)]) False- describe "DL3058 - Label `<label>` is not a valid email format - must be conform to RFC5322." $ do- it "not ok with label not containing valid email" $ do- ruleCatches "DL3058" "LABEL maintainer=\"not-email\""- onBuildRuleCatches "DL3058" "LABEL maintainer=\"not-email\""- it "ok with label containing valid email" $ do- ruleCatchesNot "DL3058" "LABEL maintainer=\"abcd@google.com\""- onBuildRuleCatchesNot "DL3058" "LABEL maintainer=\"abcd@google.com\""- it "ok with other label not containing valid email" $ do- ruleCatchesNot "DL3058" "LABEL other=\"doo\""- onBuildRuleCatchesNot "DL3058" "LABEL other=\"bar\""
− test/DL3059.hs
@@ -1,31 +0,0 @@-module DL3059 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3059 - Multiple consecutive `RUN` instructions" $ do- it "ok with no `RUN` at all" $ do- ruleCatchesNot "DL3059" "FROM debian:10"- it "ok with one `RUN`" $ do- ruleCatchesNot "DL3059" "RUN /foo.sh"- it "ok with two not consecutive `RUN`" $ do- ruleCatchesNot "DL3059" "RUN /foo.sh\nWORKDIR /\nRUN /bar.sh"- it "not ok with two consecutive `RUN`s" $ do- ruleCatches "DL3059" "RUN /foo.sh\nRUN /bar.sh"- it "ok with two consecutive `RUN`s when flags are different 1" $ do- ruleCatchesNot "DL3059" "RUN --mount=type=secret,id=foo /foo.sh\nRUN /bar.sh"- it "ok with two consecutive `RUN`s when flags are different 2" $ do- let dfile = [ "RUN --mount=type=secret,id=foo /foo.sh",- "RUN --mount=type=secret,id=bar /bar.sh"- ]- in ruleCatchesNot "DL3059" $ Text.unlines dfile- it "not ok with two consecutive `RUN`s when flags are equal" $ do- let dfile = [ "RUN --mount=type=secret,id=foo /foo.sh",- "RUN --mount=type=secret,id=foo /bar.sh"- ]- in ruleCatches "DL3059" $ Text.unlines dfile
− test/DL3060.hs
@@ -1,68 +0,0 @@-module DL3060 (tests) where--import qualified Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL3060 - `yarn cache clean` missing after `yarn install`" $ do- it "ok with non-yarn commands" $ do- ruleCatchesNot "DL3060" "RUN foo"- onBuildRuleCatchesNot "DL3060" "RUN foo"- it "not ok with no cache clean" $ do- ruleCatches "DL3060" "RUN yarn install foo"- onBuildRuleCatches "DL3060" "RUN yarn install foo"- it "ok with cache clean" $ do- ruleCatchesNot "DL3060" "RUN yarn install bar && yarn cache clean"- onBuildRuleCatchesNot "DL3060" "RUN yarn install bar && yarn cache clean"-- it "not ok when yarn install is in last stage w/o yarn clean" $- let dockerFile =- Text.unlines- [ "FROM node:lts-alpine as foo",- "RUN hey!",- "FROM scratch",- "RUN yarn install"- ]- in do- ruleCatches "DL3060" dockerFile- onBuildRuleCatches "DL3060" dockerFile-- it "not ok when inheriting from stage with yarn install w/o yarn clean" $- let dockerFile =- Text.unlines- [ "FROM node:lts-alpine as foo",- "RUN yarn install",- "FROM foo",- "RUN hey!"- ]- in do- ruleCatches "DL3060" dockerFile- onBuildRuleCatches "DL3060" dockerFile-- it "ok when inheriting from stage with yarn cache clear" $- let dockerFile =- Text.unlines- [ "FROM node:lts-alpine as foo",- "RUN yarn install && yarn cache clean",- "FROM foo",- "RUN hey!"- ]- in do- ruleCatchesNot "DL3060" dockerFile- onBuildRuleCatchesNot "DL3060" dockerFile-- it "ok when omitting yarn cache clean in stage that is not reused later" $- let dockerFile =- Text.unlines- [ "FROM node:lts-alpine as foo",- "RUN yarn install foo",- "FROM scratch",- "RUN hey!"- ]- in do- ruleCatchesNot "DL3060" dockerFile- onBuildRuleCatches "DL3060" dockerFile
− test/DL4000.hs
@@ -1,15 +0,0 @@-module DL4000 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL4000 - MAINTAINER is deprecated." $ do- it "has deprecated maintainer" $- ruleCatches "DL4000" "FROM busybox\nMAINTAINER hudu@mail.com"- it "has maintainer" $ ruleCatches "DL4000" "FROM debian\nMAINTAINER Lukas"- it "has maintainer first" $ ruleCatches "DL4000" "MAINTAINER Lukas\nFROM DEBIAN"- it "has no maintainer" $ ruleCatchesNot "DL4000" "FROM debian"
− test/DL4001.hs
@@ -1,69 +0,0 @@-module DL4001 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL4001 - Either use Wget or Curl but not both." $ do- it "warns when using both wget and curl" $- let dockerFile =- [ "FROM node as foo",- "RUN wget my.xyz",- "RUN curl localhost"- ]- in ruleCatches "DL4001" $ Text.unlines dockerFile- it "warns when using both wget and curl in same instruction" $- let dockerFile =- [ "FROM node as foo",- "RUN wget my.xyz && curl localhost"- ]- in ruleCatches "DL4001" $ Text.unlines dockerFile- it "does not warn when using only wget" $- let dockerFile =- [ "FROM node as foo",- "RUN wget my.xyz"- ]- in ruleCatchesNot "DL4001" $ Text.unlines dockerFile- it "does not warn when using both curl and wget in different stages" $- let dockerFile =- [ "FROM node as foo",- "RUN wget my.xyz",- "FROM scratch",- "RUN curl localhost"- ]- in ruleCatchesNot "DL4001" $ Text.unlines dockerFile- it "does not warns when using both, on a single stage" $- let dockerFile =- [ "FROM node as foo",- "RUN wget my.xyz",- "RUN curl localhost",- "FROM scratch",- "RUN curl localhost"- ]- in ruleCatches "DL4001" $ Text.unlines dockerFile- it "only warns on the relevant RUN instruction" $- let dockerFile =- [ "FROM node as foo",- "RUN wget my.xyz",- "RUN curl my.xyz",- "RUN echo hello"- ]- in assertChecks- (Text.unlines dockerFile)- (failsWith 1 "DL4001")-- it "only warns on many relevant RUN instructions" $- let dockerFile =- [ "FROM node as foo",- "RUN wget my.xyz",- "RUN curl my.xyz",- "RUN echo hello",- "RUN wget foo.com"- ]- in assertChecks- (Text.unlines dockerFile)- (failsWith 2 "DL4001")
− test/DL4003.hs
@@ -1,42 +0,0 @@-module DL4003 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL4003" $ do- it "many cmds" $- let dockerFile =- [ "FROM debian",- "CMD bash",- "RUN foo",- "CMD another"- ]- in ruleCatches "DL4003" $ Text.unlines dockerFile-- it "single cmds, different stages" $- let dockerFile =- [ "FROM debian as distro1",- "CMD bash",- "RUN foo",- "FROM debian as distro2",- "CMD another"- ]- in ruleCatchesNot "DL4003" $ Text.unlines dockerFile-- it "many cmds, different stages" $- let dockerFile =- [ "FROM debian as distro1",- "CMD bash",- "RUN foo",- "CMD another",- "FROM debian as distro2",- "CMD another"- ]- in ruleCatches "DL4003" $ Text.unlines dockerFile-- it "single cmd" $ ruleCatchesNot "DL4003" "CMD /bin/true"
− test/DL4004.hs
@@ -1,45 +0,0 @@-module DL4004 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL4004" $ do- it "no cmd" $ ruleCatchesNot "DL4004" "FROM busybox"-- it "many entrypoints" $- let dockerFile =- [ "FROM debian",- "ENTRYPOINT bash",- "RUN foo",- "ENTRYPOINT another"- ]- in ruleCatches "DL4004" $ Text.unlines dockerFile-- it "single entrypoint, different stages" $- let dockerFile =- [ "FROM debian as distro1",- "ENTRYPOINT bash",- "RUN foo",- "FROM debian as distro2",- "ENTRYPOINT another"- ]- in ruleCatchesNot "DL4004" $ Text.unlines dockerFile-- it "many entrypoints, different stages" $- let dockerFile =- [ "FROM debian as distro1",- "ENTRYPOINT bash",- "RUN foo",- "ENTRYPOINT another",- "FROM debian as distro2",- "ENTRYPOINT another"- ]- in ruleCatches "DL4004" $ Text.unlines dockerFile-- it "single entry" $ ruleCatchesNot "DL4004" "ENTRYPOINT /bin/true"- it "no entry" $ ruleCatchesNot "DL4004" "FROM busybox"
− test/DL4005.hs
@@ -1,19 +0,0 @@-module DL4005 (tests) where--import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL4005 - Use `SHELL` to change the default shell." $ do- it "RUN ln" $ do- ruleCatches "DL4005" "RUN ln -sfv /bin/bash /bin/sh"- onBuildRuleCatches "DL4005" "RUN ln -sfv /bin/bash /bin/sh"- it "RUN ln with unrelated symlinks" $ do- ruleCatchesNot "DL4005" "RUN ln -sf /bin/true /sbin/initctl"- onBuildRuleCatchesNot "DL4005" "RUN ln -sf /bin/true /sbin/initctl"- it "RUN ln with multiple acceptable commands" $ do- ruleCatchesNot "DL4005" "RUN ln -s foo bar && unrelated && something_with /bin/sh"- onBuildRuleCatchesNot "DL4005" "RUN ln -s foo bar && unrelated && something_with /bin/sh"
− test/DL4006.hs
@@ -1,104 +0,0 @@-module DL4006 (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "DL4006 - Set the `SHELL` option `-o pipefail` before RUN with a pipe in it" $ do- it "warn on missing pipefail" $- let dockerFile =- [ "FROM scratch",- "RUN wget -O - https://some.site | wc -l > /number"- ]- in ruleCatches "DL4006" $ Text.unlines dockerFile- it "don't warn on commands with no pipes" $- let dockerFile =- [ "FROM scratch as build",- "RUN wget -O - https://some.site && wc -l file > /number"- ]- in ruleCatchesNot "DL4006" $ Text.unlines dockerFile- it "don't warn on commands with pipes and the pipefail option" $- let dockerFile =- [ "FROM scratch as build",- "SHELL [\"/bin/bash\", \"-eo\", \"pipefail\", \"-c\"]",- "RUN wget -O - https://some.site | wc -l file > /number"- ]- in ruleCatchesNot "DL4006" $ Text.unlines dockerFile- it "don't warn on commands with pipes and the pipefail option 2" $- let dockerFile =- [ "FROM scratch as build",- "SHELL [\"/bin/bash\", \"-e\", \"-o\", \"pipefail\", \"-c\"]",- "RUN wget -O - https://some.site | wc -l file > /number"- ]- in ruleCatchesNot "DL4006" $ Text.unlines dockerFile- it "don't warn on commands with pipes and the pipefail option 3" $- let dockerFile =- [ "FROM scratch as build",- "SHELL [\"/bin/bash\", \"-o\", \"errexit\", \"-o\", \"pipefail\", \"-c\"]",- "RUN wget -O - https://some.site | wc -l file > /number"- ]- in ruleCatchesNot "DL4006" $ Text.unlines dockerFile- it "don't warn on commands with pipes and the pipefail zsh" $- let dockerFile =- [ "FROM scratch as build",- "SHELL [\"/bin/zsh\", \"-o\", \"pipefail\", \"-c\"]",- "RUN wget -O - https://some.site | wc -l file > /number"- ]- in ruleCatchesNot "DL4006" $ Text.unlines dockerFile- it "don't warn on powershell" $- let dockerFile =- [ "FROM scratch as build",- "SHELL [\"pwsh\", \"-c\"]",- "RUN Get-Variable PSVersionTable | Select-Object -ExpandProperty Value"- ]- in ruleCatchesNot "DL4006" $ Text.unlines dockerFile- it "warns when using plain sh" $- let dockerFile =- [ "FROM scratch as build",- "SHELL [\"/bin/sh\", \"-o\", \"pipefail\", \"-c\"]",- "RUN wget -O - https://some.site | wc -l file > /number"- ]- in ruleCatches "DL4006" $ Text.unlines dockerFile- it "warn on missing pipefail in the next image" $- let dockerFile =- [ "FROM scratch as build",- "SHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]",- "RUN wget -O - https://some.site | wc -l file > /number",- "FROM scratch as build2",- "RUN wget -O - https://some.site | wc -l file > /number"- ]- in ruleCatches "DL4006" $ Text.unlines dockerFile- it "warn on missing pipefail if next SHELL is not using it" $- let dockerFile =- [ "FROM scratch as build",- "SHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]",- "RUN wget -O - https://some.site | wc -l file > /number",- "SHELL [\"/bin/sh\", \"-c\"]",- "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/Hadolint/Config/CommandlineSpec.hs view
@@ -0,0 +1,346 @@+module Hadolint.Config.CommandlineSpec (spec) where++import Test.Hspec+import Test.HUnit hiding (Label)+import Hadolint+import Options.Applicative+ ( ParserResult (..),+ defaultPrefs,+ execParserPure,+ fullDesc,+ info,+ )+import qualified Data.Map as Map+import qualified Data.Set as Set+++spec :: SpecWith ()+spec = do+ describe "Config from Commandline" $ do+ it "parse with no arguments given" $ do+ checkCommandline [] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty++ describe "parse version flag" $ do+ it "parse -v" $ do+ checkCommandline ["-v"] $ CommandlineConfig+ True+ Nothing+ []+ Nothing+ mempty++ it "parse --version" $ do+ checkCommandline ["--version"] $ CommandlineConfig+ True+ Nothing+ []+ Nothing+ mempty++ describe "parse config file option" $ do+ it "parse -c hadolint.yaml" $ do+ checkCommandline ["-c", "hadolint.yaml"] $ CommandlineConfig+ False+ (Just "hadolint.yaml")+ []+ Nothing+ mempty++ it "parse --config hadolint.yaml" $ do+ checkCommandline ["--config", "hadolint.yaml"] $ CommandlineConfig+ False+ (Just "hadolint.yaml")+ []+ Nothing+ mempty++ describe "parse file arguments" $ do+ it "parse `Dockerfile`" $ do+ checkCommandline ["Dockerfile"] $ CommandlineConfig+ False+ Nothing+ ["Dockerfile"]+ Nothing+ mempty++ it "parse `Dockerfile1 Dockerfile2`" $ do+ checkCommandline ["Dockerfile1", "Dockerfile2"] $ CommandlineConfig+ False+ Nothing+ ["Dockerfile1", "Dockerfile2"]+ Nothing+ mempty++ it "parse --file-path-in-report foobar/Dockerfile" $ do+ checkCommandline+ ["--file-path-in-report", "foobar/Dockerfile"]+ ( CommandlineConfig+ False+ Nothing+ []+ (Just "foobar/Dockerfile")+ mempty+ )++ describe "parse general configuration" $ do+ it "parse --no-fail" $ do+ checkCommandline ["--no-fail"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialNoFail = Just True }++ it "parse --no-color" $ do+ checkCommandline ["--no-color"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialNoColor = Just True }++ it "parse -V" $ do+ checkCommandline ["-V"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialVerbose = Just True }++ it "parse --verbose" $ do+ checkCommandline ["--verbose"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialVerbose = Just True }++ it "parse -f json" $ do+ checkCommandline ["-f", "json"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialFormat = Just Json }++ it "parse --format" $ do+ checkCommandline ["--format", "sarif"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialFormat = Just Sarif }++ describe "parse severity overrides" $ do+ it "parse --error=DL3010" $ do+ checkCommandline ["--error", "DL3010"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialErrorRules = ["DL3010"] }++ it "parse --error=DL3010 --error=DL3020" $ do+ checkCommandline+ ["--error", "DL3010", "--error=DL3020"]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialErrorRules = ["DL3010", "DL3020"] }+ )++ it "parse --warning=DL3010" $ do+ checkCommandline ["--warning", "DL3010"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialWarningRules = ["DL3010"] }++ it "parse --warning=DL3010 --warning=DL3020" $ do+ checkCommandline+ ["--warning", "DL3010", "--warning=DL3020"]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialWarningRules = ["DL3010", "DL3020"] }+ )++ it "parse --info=DL3010" $ do+ checkCommandline ["--info", "DL3010"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialInfoRules = ["DL3010"] }++ it "parse --info=DL3010 --info=DL3020" $ do+ checkCommandline+ ["--info", "DL3010", "--info=DL3020"]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialInfoRules = ["DL3010", "DL3020"] }+ )++ it "parse --style=DL3010" $ do+ checkCommandline ["--style", "DL3010"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialStyleRules = ["DL3010"] }++ it "parse --style=DL3010 --style=DL3020" $ do+ checkCommandline+ ["--style", "DL3010", "--style=DL3020"]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialStyleRules = ["DL3010", "DL3020"] }+ )++ it "parse --ignore=DL3010" $ do+ checkCommandline ["--ignore", "DL3010"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialIgnoreRules = ["DL3010"] }++ it "parse --ignore=DL3010 --ignore=DL3020" $ do+ checkCommandline+ ["--ignore", "DL3010", "--ignore=DL3020"]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialIgnoreRules = ["DL3010", "DL3020"] }+ )++ describe "parse trusted registries" $ do+ it "parse --trusted-registry foobar.com" $ do+ checkCommandline+ ["--trusted-registry", "foobar.com"]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialAllowedRegistries = Set.fromList ["foobar.com"] }+ )++ it "parse --trusted-registry foobar.com --trusted-registry barfoo.io" $ do+ checkCommandline+ [ "--trusted-registry",+ "foobar.com",+ "--trusted-registry",+ "barfoo.io"+ ]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty+ { partialAllowedRegistries =+ Set.fromList ["foobar.com", "barfoo.io"]+ }+ )++ describe "parse required labels" $ do+ it "parse --require-label foo:email" $ do+ checkCommandline+ ["--require-label", "foo:email"]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialLabelSchema = Map.fromList [("foo", Email)] }+ )++ it "parse --require-label foo:email --require-label bar:text" $ do+ checkCommandline+ ["--require-label", "foo:email", "--require-label", "bar:text"]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty+ { partialLabelSchema =+ Map.fromList [("foo", Email), ("bar", RawText)]+ }+ )++ describe "parse strict labels" $ do+ it "parse --strict-labels" $ do+ checkCommandline+ ["--strict-labels"]+ ( CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty { partialStrictLabels = Just True }+ )++ describe "parse failure thresholds" $ do+ it "parse -t warning" $ do+ checkCommandline ["-t", "warning"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty+ { partialFailureThreshold = Just DLWarningC }++ it "parse --failure-threshold style" $ do+ checkCommandline ["--failure-threshold", "style"] $ CommandlineConfig+ False+ Nothing+ []+ Nothing+ mempty+ { partialFailureThreshold = Just DLStyleC }++ describe "fail parsing on garbage input" $ do+ it "fail parsing --blafoo" $ do+ checkCommandlineFail+ ["--blafoo"]++ it "fail parsing --require-label foo:bar" $ do+ checkCommandlineFail+ ["--require-label", "foo:bar"]+++checkCommandline :: [String] -> CommandlineConfig -> Assertion+checkCommandline args config = do+ let inf = info parseCommandline fullDesc+ res = execParserPure defaultPrefs inf args+ case res of+ Success cfg -> cfg `shouldBe` config+ _ -> assertFailure "should have parsed commandline arguments"++checkCommandlineFail :: [String] -> Assertion+checkCommandlineFail args = do+ let inf = info parseCommandline fullDesc+ res = execParserPure defaultPrefs inf args+ case res of+ Failure _ -> assertEqual "" (0 :: Int) (0 :: Int) -- inverse asserFailure+ _ -> assertFailure "should have failed parsing commandline arguments"
+ test/Hadolint/Config/ConfigfileSpec.hs view
@@ -0,0 +1,169 @@+module Hadolint.Config.ConfigfileSpec (spec) where++import Hadolint+import Test.Hspec+import qualified Data.ByteString.Char8 as Bytes+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.YAML as Yaml+++spec :: SpecWith ()+spec =+ describe "Config from Configfile" $ do+ it "parse empty config file" $ do+ let yaml = []+ conf = parseYaml yaml+ conf `shouldBe` Left "empty YAML stream"++ it "parse `no-fail: true`" $ do+ let yaml = ["no-fail: true"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialNoFail = Just True }++ it "parse `no-fail: false`" $ do+ let yaml = ["no-fail: false"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialNoFail = Just False }++ it "parse `no-color: true`" $ do+ let yaml = ["no-color: true"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialNoColor = Just True }++ it "parse `no-color: false`" $ do+ let yaml = ["no-color: false"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialNoColor = Just False }++ it "parse `verbose: true`" $ do+ let yaml = ["verbose: true"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialVerbose = Just True }++ it "parse `verbose: false`" $ do+ let yaml = ["verbose: false"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialVerbose = Just False }++ it "parse `output-format: json`" $ do+ let yaml = ["output-format: json"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialFormat = Just Json }++ it "parse `output-format: sarif`" $ do+ let yaml = ["output-format: sarif"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialFormat = Just Sarif }++ it "parse override error rules" $ do+ let yaml = [ "override:",+ " error:",+ " - DL3020",+ " - DL3040",+ " - SC1020"+ ]+ conf = parseYaml yaml+ conf `shouldBe` Right+ mempty+ { partialErrorRules = ["DL3020", "DL3040", "SC1020"] }++ it "parse override warning rules" $ do+ let yaml = [ "override:",+ " warning:",+ " - DL3020",+ " - DL3040",+ " - SC1020"+ ]+ conf = parseYaml yaml+ conf `shouldBe` Right+ mempty+ { partialWarningRules = ["DL3020", "DL3040", "SC1020"]+ }++ it "parse override info rules" $ do+ let yaml = [ "override:",+ " info:",+ " - DL3020",+ " - DL3040",+ " - SC1020"+ ]+ conf = parseYaml yaml+ conf `shouldBe` Right+ mempty+ { partialInfoRules = ["DL3020", "DL3040", "SC1020"] }++ it "parse override style rules" $ do+ let yaml = [ "override:",+ " style:",+ " - DL3020",+ " - DL3040",+ " - SC1020"+ ]+ conf = parseYaml yaml+ conf `shouldBe` Right+ mempty+ { partialStyleRules = ["DL3020", "DL3040", "SC1020"] }++ it "parse ignored rules" $ do+ let yaml = [ "ignored:",+ " - DL3020",+ " - DL3040",+ " - SC1020"+ ]+ conf = parseYaml yaml+ conf `shouldBe` Right+ mempty+ { partialIgnoreRules = ["DL3020", "DL3040", "SC1020"] }++ it "parse trusted registries" $ do+ let yaml = [ "trusted-registries:",+ " - foobar.com",+ " - barfoo.com"+ ]+ conf = parseYaml yaml+ conf `shouldBe` Right+ mempty+ { partialAllowedRegistries =+ Set.fromList ["foobar.com", "barfoo.com"]+ }++ it "parse label schema" $ do+ let yaml = [ "label-schema:",+ " foo: email",+ " bar: text"+ ]+ conf = parseYaml yaml+ conf `shouldBe` Right+ mempty+ { partialLabelSchema =+ Map.fromList [("foo", Email), ("bar", RawText)]+ }++ it "parse strict-labels: true" $ do+ let yaml = [ "strict-labels: true" ]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialStrictLabels = Just True }++ it "parse strict-labels: false" $ do+ let yaml = [ "strict-labels: false" ]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialStrictLabels = Just False }++ it "parse `failure-threshold: warning`" $ do+ let yaml = ["failure-threshold: warning"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialFailureThreshold = Just DLWarningC }++ it "parse `failure-threshold: style`" $ do+ let yaml = ["failure-threshold: style"]+ conf = parseYaml yaml+ conf `shouldBe` Right mempty { partialFailureThreshold = Just DLStyleC }++-- Helper functions for parsing config files++parseYaml :: [Bytes.ByteString] -> Either String PartialConfiguration+parseYaml ls =+ case (Yaml.decode1Strict . Bytes.unlines) ls of+ Left (_, err) -> Left err+ Right res -> Right res
+ test/Hadolint/Config/ConfigurationSpec.hs view
@@ -0,0 +1,219 @@+module Hadolint.Config.ConfigurationSpec where++import Data.Default+import qualified Data.Map as Map+import qualified Data.Set as Set+import Hadolint+import Test.Hspec++spec :: SpecWith ()+spec = do+ describe "Configuration" $ do+ it "default configuration" $ do+ def+ `shouldBe` Configuration+ False+ False+ False+ TTY+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ False+ DLInfoC++ it "override default configuration with empty config" $ do+ applyPartialConfiguration def mempty+ `shouldBe` Configuration+ False+ False+ False+ TTY+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ False+ DLInfoC++ it "override default with specific configuration: no-fail" $ do+ let config =+ PartialConfiguration+ (Just True)+ Nothing+ Nothing+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ Nothing+ mempty+ applyPartialConfiguration def config+ `shouldBe` Configuration+ True+ False+ False+ TTY+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ False+ DLInfoC++ it "override default with specific configuration: no-color" $ do+ let config =+ PartialConfiguration+ Nothing+ (Just True)+ Nothing+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ Nothing+ mempty+ applyPartialConfiguration def config+ `shouldBe` Configuration+ False+ True+ False+ TTY+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ False+ DLInfoC++ it "empty should not override: no-color" $ do+ let config =+ PartialConfiguration+ Nothing+ (Just True)+ Nothing+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ Nothing+ mempty+ config2 =+ PartialConfiguration+ Nothing+ Nothing+ Nothing+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ Nothing+ mempty+ applyPartialConfiguration def (config <> config2)+ `shouldBe` Configuration+ False+ True+ False+ TTY+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ False+ DLInfoC++ it "override default with specific configuration: verbose" $ do+ let config =+ PartialConfiguration+ Nothing+ Nothing+ (Just True)+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ Nothing+ mempty+ applyPartialConfiguration def config+ `shouldBe` Configuration+ False+ False+ True+ TTY+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ False+ DLInfoC++ it "override default with specific configuration: output-format json" $ do+ let config =+ PartialConfiguration+ Nothing+ Nothing+ Nothing+ (Just Json)+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ Nothing+ mempty+ applyPartialConfiguration def config+ `shouldBe` Configuration+ False+ False+ False+ Json+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ mempty+ False+ DLInfoC
+ test/Hadolint/Config/EnvironmentSpec.hs view
@@ -0,0 +1,186 @@+module Hadolint.Config.EnvironmentSpec (spec) where++import Hadolint+import System.Environment+import Test.Hspec+import qualified Data.Map as Map+import qualified Data.Set as Set+++spec :: SpecWith ()+spec = do+ describe "Config from Environment" $ do+ withoutAll $ do+ it "Empty config when nothing is set" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty++ withJustEnv "HADOLINT_NOFAIL" "1" $ do+ it "parse HADOLINT_NOFAIL=1" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialNoFail = Just True }++ withJustEnv "HADOLINT_NOFAIL" "0" $ do+ it "parse HADOLINT_NOFAIL=0" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty {partialNoFail = Just False }++ withJustEnv "NO_COLOR" "y" $ do+ it "parse NO_COLOR=y" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialNoColor = Just True }++ withJustEnv "NO_COLOR" "n" $ do+ it "parse NO_COLOR=n" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialNoColor = Just False }++ withJustEnv "HADOLINT_VERBOSE" "on" $ do+ it "parse HADOLINT_VERBOSE=on" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialVerbose = Just True }++ withJustEnv "HADOLINT_VERBOSE" "off" $ do+ it "parse HADOLINT_VERBOSE=off" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialVerbose = Just False }++ withJustEnv "HADOLINT_FORMAT" "json" $ do+ it "parse HADOLINT_FORMAT=json" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialFormat = Just Json }++ withJustEnv "HADOLINT_FORMAT" "sarif" $ do+ it "parse HADOLINT_FORMAT=sarif" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialFormat = Just Sarif }++ withJustEnv "HADOLINT_OVERRIDE_ERROR" "DL3010" $ do+ it "parse HADOLINT_OVERRIDE_ERROR=DL3010" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialErrorRules = ["DL3010"] }++ withJustEnv "HADOLINT_OVERRIDE_ERROR" "DL3010,DL3011" $ do+ it "parse HADOLINT_OVERRIDE_ERROR=DL3010,DL3011" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialErrorRules = ["DL3010", "DL3011"] }++ withJustEnv "HADOLINT_OVERRIDE_WARNING" "DL3010" $ do+ it "parse HADOLINT_OVERRIDE_WARNING=DL3010" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialWarningRules = ["DL3010"] }++ withJustEnv "HADOLINT_OVERRIDE_WARNING" "DL3010,DL3011" $ do+ it "parse HADOLINT_OVERRIDE_WARNING=DL3010,DL3011" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialWarningRules = ["DL3010", "DL3011"] }++ withJustEnv "HADOLINT_OVERRIDE_INFO" "DL3010" $ do+ it "parse HADOLINT_OVERRIDE_INFO=DL3010" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialInfoRules = ["DL3010"] }++ withJustEnv "HADOLINT_OVERRIDE_INFO" "DL3010,DL3011" $ do+ it "parse HADOLINT_OVERRIDE_INFO=DL3010,DL3011" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialInfoRules = ["DL3010", "DL3011"] }++ withJustEnv "HADOLINT_OVERRIDE_STYLE" "DL3010" $ do+ it "parse HADOLINT_OVERRIDE_STYLE=DL3010" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialStyleRules = ["DL3010"] }++ withJustEnv "HADOLINT_OVERRIDE_STYLE" "DL3010,DL3011" $ do+ it "parse HADOLINT_OVERRIDE_STYLE=DL3010,DL3011" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialStyleRules = ["DL3010", "DL3011"] }++ withJustEnv "HADOLINT_IGNORE" "DL3010" $ do+ it "parse HADOLINT_IGNORE=DL3010" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialIgnoreRules = ["DL3010"] }++ withJustEnv "HADOLINT_IGNORE" "DL3010,DL3011" $ do+ it "parse HADOLINT_IGNORE=DL3010,DL3011" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialIgnoreRules = ["DL3010", "DL3011"] }++ withJustEnv "HADOLINT_ALLOWED_REGISTRIES" "foobar.com" $ do+ it "parse HADOLINT_ALLOWED_REGISTRIES=foobar.com" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty+ { partialAllowedRegistries =+ Set.fromList ["foobar.com"]+ }++ withJustEnv "HADOLINT_ALLOWED_REGISTRIES" "foobar.com,barfoo.com" $ do+ it "parse HADOLINT_ALLOWED_REGISTIRES=foobar.com,barfoo.com" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty+ { partialAllowedRegistries =+ Set.fromList ["foobar.com", "barfoo.com"]+ }++ withJustEnv "HADOLINT_REQUIRE_LABELS" "foo:email" $ do+ it "parse HADOLINT_REQUIRE_LABELS=foo:email" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty+ { partialLabelSchema = Map.fromList [("foo", Email)] }++ withJustEnv "HADOLINT_REQUIRE_LABELS" "foo:email,bar:text" $ do+ it "parse HADOLINT_REQUIRE_LABELS=foo:email,bar:text" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty+ { partialLabelSchema =+ Map.fromList [("foo", Email), ("bar", RawText)]+ }++ withJustEnv "HADOLINT_STRICT_LABELS" "yes" $ do+ it "parse HADOLINT_STRICT_LABELS=yes" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialStrictLabels = Just True }++ withJustEnv "HADOLINT_STRICT_LABELS" "false" $ do+ it "parse HADOLINT_STRICT_LABELS=false" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialStrictLabels = Just False }++ withJustEnv "HADOLINT_FAILURE_THRESHOLD" "error" $ do+ it "parse HADOLINT_FAILURE_THRESHOLD=error" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialFailureThreshold = Just DLErrorC }++ withJustEnv "HADOLINT_FAILURE_THRESHOLD" "style" $ do+ it "parse HADOLINT_FAILURE_THRESHOLD=style" $ do+ conf <- getConfigFromEnvironment+ conf `shouldBe` mempty { partialFailureThreshold = Just DLStyleC }++-- Helper functions for setup and teardown of the environment++withoutAll :: SpecWith () -> SpecWith ()+withoutAll = before unsetAll++withJustEnv :: String -> String -> SpecWith () -> SpecWith ()+withJustEnv name value test =+ before_ (setJustEnv name value) $ after_ (unsetEnv name) test++setJustEnv :: String -> String -> IO ()+setJustEnv name value = do+ unsetAll+ setEnv name value++unsetAll :: IO ()+unsetAll = do+ unsetEnv "HADOLINT_NOFAIL"+ unsetEnv "NO_COLOR"+ unsetEnv "HADOLINT_VERBOSE"+ unsetEnv "HADOLINT_FORMAT"+ unsetEnv "HADOLINT_OVERRIDE_ERROR"+ unsetEnv "HADOLINT_OVERRIDE_WARNING"+ unsetEnv "HADOLINT_OVERRIDE_INFO"+ unsetEnv "HADOLINT_OVERRIDE_STYLE"+ unsetEnv "HADOLINT_IGNORE"+ unsetEnv "HADOLINT_ALLOWED_REGISTRIES"+ unsetEnv "HADOLINT_REQUIRE_LABELS"+ unsetEnv "HADOLINT_STRICT_LABELS"+ unsetEnv "HADOLINT_FAILURE_THRESHOLD"
+ test/Hadolint/Config/SpecHook.hs view
@@ -0,0 +1,8 @@+module Hadolint.Config.SpecHook (hook) where++import System.Environment+import Test.Hspec+++hook :: SpecWith () -> SpecWith ()+hook = before_ (setEnv "BAR" "bar")
+ test/Hadolint/Formatter/ParseErrorSpec.hs view
@@ -0,0 +1,31 @@+module Hadolint.Formatter.ParseErrorSpec (spec) where++import Hadolint.Formatter.Format (errorMessage)+import Hadolint.Formatter.TTY (formatError)+import Language.Docker.Parser+import Test.HUnit hiding (Label)+import Test.Hspec+++spec :: SpecWith ()+spec = do+ describe "Parse Errors" $ do+ it "display error after line pos" $ do+ let ast = parseText "FOM debian:jessie"+ expectedMsg =+ "<string>:1:1 unexpected 'F' expecting '#', ADD, ARG, CMD, COPY,\+ \ ENTRYPOINT, ENV, EXPOSE, FROM, HEALTHCHECK, LABEL, MAINTAINER,\+ \ ONBUILD, RUN, SHELL, STOPSIGNAL, USER, VOLUME, WORKDIR, a pragma,\+ \ end of input, or whitespaces "+ case ast of+ Left err ->+ assertEqual "Unexpected error msg" expectedMsg (formatError err)+ Right _ -> assertFailure "AST should fail parsing"++ it "display just the error message" $ do+ let ast = parseText "RUNNN"+ expectedMsg = "missing whitespace"+ case ast of+ Left err ->+ assertEqual "Unexpected error msg" expectedMsg (errorMessage err)+ Right _ -> assertFailure "AST should fail parsing"
+ test/Hadolint/Formatter/SarifSpec.hs view
@@ -0,0 +1,32 @@+module Hadolint.Formatter.SarifSpec (spec) where++import Helpers+import Hadolint+ ( CheckFailure (..),+ DLSeverity (..),+ Result(..),+ printResults,+ OutputFormat (..),+ getShortVersion,+ )+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?noColor = True++ describe "Formatter: Sarif" $ do+ it "print empty results" $ do+ let checkFails = []+ expectation = "{\"runs\":[{\"defaultSourceLanguage\":\"dockerfi\+ \le\",\"results\":[],\"tool\":{\"driver\":{\"full\+ \Name\":\"Haskell Dockerfile Linter\",\"downloadU\+ \ri\":\"https://github.com/hadolint/hadolint\",\"\+ \shortDescription\":{\"text\":\"Dockerfile linter\+ \, validate inline bash, written in Haskell\"},\"\+ \name\":\"Hadolint\",\"version\":\""+ ++ getShortVersion+ ++ "\"}}}],\"version\":\"2.1.0\",\"$schema\+ \\":\"http://json.schemastore.org/sarif-2.1.0\"}"+ assertFormatter Sarif checkFails expectation
+ test/Hadolint/Formatter/TTYSpec.hs view
@@ -0,0 +1,87 @@+module Hadolint.Formatter.TTYSpec (spec) where++import Helpers+import Data.List.NonEmpty as NonEmpty+import Hadolint (OutputFormat (..), printResults)+import Hadolint.Formatter.Format (Result (..))+import Hadolint.Rule (CheckFailure (..), DLSeverity (..))+import Test.Hspec+import qualified Data.Sequence as Seq+++spec :: SpecWith ()+spec = do+ let ?noColor = True+ describe "Formatter: TTY" $ do+ it "print empty results" $ do+ let results = NonEmpty.fromList [Result "<string>" mempty Seq.empty]+ printResults TTY True (Just "<string>") results `shouldReturn` ()++ it "print some result: no colors" $ do+ let checkFails = [ CheckFailure+ { code = "DL2001",+ severity = DLInfoC,+ message = "test",+ line = 1+ }+ ]+ expectation = unlines+ [ "<string>:1 DL2001 info: test"+ ]+ assertFormatter TTY checkFails expectation++ it "print some result: with colors" $ do+ let ?noColor = False+ let checkFails = [ CheckFailure+ { code = "DL2001",+ severity = DLInfoC,+ message = "test",+ line = 1+ }+ ]+ expectation = unlines+ [ "<string>:1 DL2001 \ESC[92minfo\ESC[0m: test"+ ]+ assertFormatter TTY checkFails expectation++ it "print multiple results: no colors" $ do+ let checkFails = [ CheckFailure+ { code = "DL2001",+ severity = DLInfoC,+ message = "test",+ line = 1+ },+ CheckFailure+ { code = "DL2002",+ severity = DLWarningC,+ message = "foo",+ line = 3+ }+ ]+ expectation = unlines+ [ "<string>:1 DL2001 info: test",+ "<string>:3 DL2002 warning: foo"+ ]+ assertFormatter TTY checkFails expectation++ it "print multiple results: with colors" $ do+ let ?noColor = False+ let checkFails = [ CheckFailure+ { code = "DL2001",+ severity = DLInfoC,+ message = "test",+ line = 1+ },+ CheckFailure+ { code = "DL2002",+ severity = DLWarningC,+ message = "foo",+ line = 3+ }+ ]+ expectation = unlines+ [ "<string>:1 DL2001 \ESC[92minfo\ESC[0m: test",+ "<string>:3 DL2002 \ESC[1m\ESC[93mwarning\ESC[0m:\+ \ foo"+ ]+ assertFormatter TTY checkFails expectation
+ test/Hadolint/PragmaSpec.hs view
@@ -0,0 +1,57 @@+module Hadolint.PragmaSpec (spec) where++import Data.Default+import Helpers+import Test.Hspec+import qualified Data.Text as Text+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "Rules can be ignored with inline comments" $ do+ it "ignores single rule" $+ let dockerFile =+ [ "FROM ubuntu",+ "# hadolint ignore=DL3002",+ "USER root"+ ]+ in ruleCatchesNot "DL3002" $ Text.unlines dockerFile+ it "ignores only the given rule" $+ let dockerFile =+ [ "FROM scratch",+ "# hadolint ignore=DL3001",+ "USER root"+ ]+ in ruleCatches "DL3002" $ Text.unlines dockerFile+ it "ignores only the given rule, when multiple passed" $+ let dockerFile =+ [ "FROM scratch",+ "# hadolint ignore=DL3001,DL3002",+ "USER root"+ ]+ in ruleCatchesNot "DL3002" $ Text.unlines dockerFile+ it "ignores the rule only if directly above the instruction" $+ let dockerFile =+ [ "# hadolint ignore=DL3001,DL3002",+ "FROM ubuntu",+ "USER root"+ ]+ in ruleCatches "DL3002" $ Text.unlines dockerFile+ it "won't ignore the rule if passed invalid rule names" $+ let dockerFile =+ [ "FROM scratch",+ "# hadolint ignore=crazy,DL3002",+ "USER root"+ ]+ in ruleCatches "DL3002" $ Text.unlines dockerFile+ it "ignores multiple rules correctly, even with some extra whitespace" $+ let dockerFile =+ [ "FROM node as foo",+ "# hadolint ignore=DL3023, DL3021",+ "COPY --from=foo bar baz ."+ ]+ in do+ ruleCatchesNot "DL3023" $ Text.unlines dockerFile+ ruleCatchesNot "DL3021" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3000Spec.hs view
@@ -0,0 +1,28 @@+module Hadolint.Rule.DL3000Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3000 - Use absolute WORKDIR." $ do+ it "workdir relative" $ ruleCatches "DL3000" "WORKDIR relative/dir"+ it "workdir absolute" $ ruleCatchesNot "DL3000" "WORKDIR /usr/local"+ it "workdir variable" $ ruleCatchesNot "DL3000" "WORKDIR ${work}"+ it "workdir relative single quotes" $ ruleCatches "DL3000" "WORKDIR \'relative/dir\'"+ it "workdir absolute single quotes" $ ruleCatchesNot "DL3000" "WORKDIR \'/usr/local\'"+ -- no test for variable/single quotes since the variable would not expand.+ it "workdir relative double quotes" $ ruleCatches "DL3000" "WORKDIR \"relative/dir\""+ it "workdir absolute double quotes" $ ruleCatchesNot "DL3000" "WORKDIR \"/usr/local\""+ it "workdir variable double quotes" $ ruleCatchesNot "DL3000" "WORKDIR \"${dir}\""+ --+ it "workdir absolute windows" $ ruleCatchesNot "DL3000" "WORKDIR \'C:\\\'"+ it "workdir absolute windows quotes" $ ruleCatchesNot "DL3000" "WORKDIR \"C:\\\""+ it "workdir absolute windows alternative" $+ ruleCatchesNot "DL3000" "WORKDIR C:/"+ it "workdir absolute windows quotes alternative" $+ ruleCatchesNot "DL3000" "WORKDIR \"C:/\""
+ test/Hadolint/Rule/DL3001Spec.hs view
@@ -0,0 +1,18 @@+module Hadolint.Rule.DL3001Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3001 - invalid CMD rules" $ do+ it "invalid cmd" $ do+ ruleCatches "DL3001" "RUN top"+ onBuildRuleCatches "DL3001" "RUN top"+ it "install ssh" $ do+ ruleCatchesNot "DL3001" "RUN apt-get install ssh"+ onBuildRuleCatchesNot "DL3001" "RUN apt-get install ssh"
+ test/Hadolint/Rule/DL3002Spec.hs view
@@ -0,0 +1,88 @@+module Hadolint.Rule.DL3002Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3002 - Last user should not be root." $ do+ it "last user should not be root" $+ let dockerFile =+ [ "FROM scratch",+ "USER root"+ ]+ in ruleCatches "DL3002" $ Text.unlines dockerFile++ it "no root" $+ let dockerFile =+ [ "FROM scratch",+ "USER foo"+ ]+ in ruleCatchesNot "DL3002" $ Text.unlines dockerFile++ it "no root UID" $+ let dockerFile =+ [ "FROM scratch",+ "USER 0"+ ]+ in ruleCatches "DL3002" $ Text.unlines dockerFile++ it "no root:root" $+ let dockerFile =+ [ "FROM scratch",+ "USER root:root"+ ]+ in ruleCatches "DL3002" $ Text.unlines dockerFile++ it "no UID:GID" $+ let dockerFile =+ [ "FROM scratch",+ "USER 0:0"+ ]+ in ruleCatches "DL3002" $ Text.unlines dockerFile++ it "can switch back to non root" $+ let dockerFile =+ [ "FROM scratch",+ "USER root",+ "RUN something",+ "USER foo"+ ]+ in ruleCatchesNot "DL3002" $ Text.unlines dockerFile++ it "warns on transitive root user" $+ let dockerFile =+ [ "FROM debian as base",+ "USER root",+ "RUN something",+ "FROM base",+ "RUN something else"+ ]+ in ruleCatches "DL3002" $ Text.unlines dockerFile++ it "warns on multiple stages" $+ let dockerFile =+ [ "FROM debian as base",+ "USER root",+ "RUN something",+ "FROM scratch",+ "USER foo",+ "RUN something else"+ ]+ in ruleCatches "DL3002" $ Text.unlines dockerFile++ it "does not warn when switching in multiple stages" $+ let dockerFile =+ [ "FROM debian as base",+ "USER root",+ "RUN something",+ "USER foo",+ "FROM scratch",+ "RUN something else"+ ]+ in ruleCatchesNot "DL3002" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3003Spec.hs view
@@ -0,0 +1,14 @@+module Hadolint.Rule.DL3003Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3003 - Use WORKDIR to switch to a directory." $ do+ it "ok using WORKDIR" $ ruleCatchesNot "DL3003" "WORKDIR /opt"+ it "not ok using cd" $ ruleCatches "DL3003" "RUN cd /opt"
+ test/Hadolint/Rule/DL3004Spec.hs view
@@ -0,0 +1,21 @@+module Hadolint.Rule.DL3004Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3004 - Do not use sudo as it leads to unpredictable behavior. Use a tool like gosu to enforce root." $ do+ it "sudo" $ do+ ruleCatches "DL3004" "RUN sudo apt-get update"+ onBuildRuleCatches "DL3004" "RUN sudo apt-get update"+ it "install sudo" $ do+ ruleCatchesNot "DL3004" "RUN apt-get install sudo"+ onBuildRuleCatchesNot "DL3004" "RUN apt-get install sudo"+ it "sudo chained programs" $ do+ ruleCatches "DL3004" "RUN apt-get update && sudo apt-get install"+ onBuildRuleCatches "DL3004" "RUN apt-get update && sudo apt-get install"
+ test/Hadolint/Rule/DL3005Spec.hs view
@@ -0,0 +1,15 @@+module Hadolint.Rule.DL3005Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3005" $ do+ it "apt-get dist-upgrade" $ do+ ruleCatches "DL3005" "RUN apt-get update && apt-get dist-upgrade"+ onBuildRuleCatches "DL3005" "RUN apt-get update && apt-get dist-upgrade"
+ test/Hadolint/Rule/DL3006Spec.hs view
@@ -0,0 +1,44 @@+module Hadolint.Rule.DL3006Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3006 - Always tag the version of an image explicitly." $ do+ it "no untagged" $ ruleCatches "DL3006" "FROM debian"+ it "no untagged with name" $ ruleCatches "DL3006" "FROM debian AS builder"+ it "untagged digest is not an error" $+ ruleCatchesNot "DL3006" "FROM ruby@sha256:f1dbca0f5dbc9"+ it "untagged digest is not an error" $+ ruleCatchesNot "DL3006" "FROM ruby:2"+ it "local aliases are OK to be untagged" $+ let dockerFile =+ [ "FROM golang:1.9.3-alpine3.7 AS build",+ "RUN foo",+ "FROM build as unit-test",+ "RUN bar",+ "FROM alpine:3.7",+ "RUN baz"+ ]+ in do+ ruleCatchesNot "DL3006" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3006" $ Text.unlines dockerFile+ it "other untagged cases are not ok" $+ let dockerFile =+ [ "FROM golang:1.9.3-alpine3.7 AS build",+ "RUN foo",+ "FROM node as unit-test",+ "RUN bar",+ "FROM alpine:3.7",+ "RUN baz"+ ]+ in do+ ruleCatches "DL3006" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3006" $ Text.unlines dockerFile+ it "scratch" $ ruleCatchesNot "DL3006" "FROM scratch"
+ test/Hadolint/Rule/DL3007Spec.hs view
@@ -0,0 +1,24 @@+module Hadolint.Rule.DL3007Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3007 - Using latest is prone to errors if the image will ever up\+ \date. Pin the version explicitly to a release tag." $ do+ it "explicit latest" $ ruleCatches "DL3007" "FROM debian:latest"+ it "explicit latest with name" $+ ruleCatches "DL3007" "FROM debian:latest AS builder"+ it "explicit tagged" $ ruleCatchesNot "DL3007" "FROM debian:jessie"+ it "explicit SHA" $+ ruleCatchesNot+ "DL3007"+ "FROM hub.docker.io/debian@sha256:\+ \7959ed6f7e35f8b1aaa06d1d8259d4ee25aa85a086d5c125480c333183f9deeb"+ it "explicit tagged with name" $+ ruleCatchesNot "DL3007" "FROM debian:jessie AS builder"
+ test/Hadolint/Rule/DL3008Spec.hs view
@@ -0,0 +1,63 @@+module Hadolint.Rule.DL3008Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3008 - Pin versions in apt-get install." $ do+ it "apt-get version pinning" $ do+ ruleCatches "DL3008" "RUN apt-get update && apt-get install python"+ onBuildRuleCatches "DL3008" "RUN apt-get update && apt-get install python"+ it "apt-get pinned chained" $+ let dockerFile =+ [ "RUN apt-get update \\",+ " && apt-get -yqq --no-install-recommends install nodejs=0.10 \\",+ " && rm -rf /var/lib/apt/lists/*"+ ]+ in do+ ruleCatchesNot "DL3008" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3008" $ Text.unlines dockerFile++ it "apt-get pinned regression" $+ let dockerFile =+ [ "RUN apt-get update && apt-get install --no-install-recommends -y \\",+ "python-demjson=2.2.2* \\",+ "wget=1.16.1* \\",+ "git=1:2.5.0* \\",+ "ruby=1:2.1.*"+ ]+ in do+ ruleCatchesNot "DL3008" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3008" $ Text.unlines dockerFile+ it "apt-get version" $ do+ ruleCatchesNot "DL3008" "RUN apt-get install -y python=1.2.2"+ onBuildRuleCatchesNot "DL3008" "RUN apt-get install -y python=1.2.2"+ it "apt-get version" $ do+ ruleCatchesNot "DL3008" "RUN apt-get install ./wkhtmltox_0.12.5-1.bionic_amd64.deb"+ onBuildRuleCatchesNot "DL3008" "RUN apt-get install ./wkhtmltox_0.12.5-1.bionic_amd64.deb"+ it "apt-get pinned" $ do+ ruleCatchesNot+ "DL3008"+ "RUN apt-get -y --no-install-recommends install nodejs=0.10"+ onBuildRuleCatchesNot+ "DL3008"+ "RUN apt-get -y --no-install-recommends install nodejs=0.10"+ it "apt-get tolerate target-release" $+ let dockerFile =+ [ "RUN set -e &&\\",+ " echo \"deb http://http.debian.net/debian jessie-backports main\" \+ \> /etc/apt/sources.list.d/jessie-backports.list &&\\",+ " apt-get update &&\\",+ " apt-get install -y --no-install-recommends -t jessie-backports \+ \openjdk-8-jdk=8u131-b11-1~bpo8+1 &&\\",+ " rm -rf /var/lib/apt/lists/*"+ ]+ in do+ ruleCatchesNot "DL3008" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3008" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3009Spec.hs view
@@ -0,0 +1,133 @@+module Hadolint.Rule.DL3009Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3009 - Delete the apt-get lists after installing something." $ do+ it "apt-get no cleanup" $+ let dockerFile =+ [ "FROM scratch",+ "RUN apt-get update && apt-get install python"+ ]+ in do+ ruleCatches "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3009" $ Text.unlines dockerFile+ it "apt-get cleanup in stage image" $+ let dockerFile =+ [ "FROM ubuntu as foo",+ "RUN apt-get update && apt-get install python",+ "FROM scratch",+ "RUN echo hey!"+ ]+ in do+ ruleCatchesNot "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3009" $ Text.unlines dockerFile+ it "apt-get no cleanup in last stage" $+ let dockerFile =+ [ "FROM ubuntu as foo",+ "RUN hey!",+ "FROM scratch",+ "RUN apt-get update && apt-get install python"+ ]+ in do+ ruleCatches "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3009" $ Text.unlines dockerFile+ it "apt-get no cleanup in intermediate stage" $+ let dockerFile =+ [ "FROM ubuntu as foo",+ "RUN apt-get update && apt-get install python",+ "FROM foo",+ "RUN hey!"+ ]+ in do+ ruleCatches "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3009" $ Text.unlines dockerFile+ it "no warn apt-get cleanup in intermediate stage that cleans lists" $+ let dockerFile =+ [ "FROM ubuntu as foo",+ "RUN apt-get update && apt-get install python && rm -rf /var/lib/apt/lists/*",+ "FROM foo",+ "RUN hey!"+ ]+ in do+ ruleCatchesNot "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile+ it "no warn apt-get cleanup in intermediate stage when stage not used later" $+ let dockerFile =+ [ "FROM ubuntu as foo",+ "RUN apt-get update && apt-get install python",+ "FROM scratch",+ "RUN hey!"+ ]+ in do+ ruleCatchesNot "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3009" $ Text.unlines dockerFile+ it "apt-get cleanup" $+ let dockerFile =+ [ "FROM scratch",+ "RUN apt-get update && apt-get install python && rm -rf /var/lib/apt/lists/*"+ ]+ in do+ ruleCatchesNot "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile++ it "don't warn: BuildKit cache mount to apt lists directory" $ do+ ruleCatchesNot+ "DL3009"+ "RUN --mount=type=cache,target=/var/lib/apt/lists \\\+ \ apt-get update && apt-get install python"+ onBuildRuleCatchesNot+ "DL3009"+ "RUN --mount=type=cache,target=/var/lib/apt/lists \\\+ \ apt-get update && apt-get install python"++ it "don't warn: BuildKit cache mount to apt directories 1" $ do+ ruleCatchesNot+ "DL3009"+ "RUN --mount=type=cache,target=/var/lib/apt \\\+ \ --mount=type=cache,target=/var/cache/apt \\\+ \ rm -f /etc/apt/apt.conf.d/docker-clean && \\\+ \ apt-get update && apt-get install python"+ onBuildRuleCatchesNot+ "DL3009"+ "RUN --mount=type=cache,target=/var/lib/apt \\\+ \ --mount=type=cache,target=/var/cache/apt \\\+ \ rm -f /etc/apt/apt.conf.d/docker-clean && \\\+ \ apt-get update && apt-get install python"++ it "don't warn: BuildKit cache mount to apt directories 2" $ do+ let dockerFile =+ [ "RUN rm -f /etc/apt/apt.conf.d/docker-clean",+ "RUN --mount=type=cache,target=/var/cache/apt \\",+ " --mount=type=cache,target=/var/lib/apt \\",+ " apt-get update && apt-get install foo"+ ]+ ruleCatchesNot "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3009" $ Text.unlines dockerFile++ it "warn: BuildKit cache mount to apt cache directory only" $ do+ let dockerFile =+ [ "RUN --mount=type=cache,target=/var/cache/apt \\",+ " rm -f /etc/apt/apt.conf.d/docker-clean && \\",+ " apt-get update && apt-get install python"+ ]+ in do+ ruleCatches "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3009" $ Text.unlines dockerFile++ it "warn: BuildKit cache mount to apt lists directory only" $ do+ let dockerFile =+ [ "RUN rm -f /etc/apt/apt.conf.d/docker-clean",+ "RUN --mount=type=cache,target=/var/lib/apt \\",+ " apt-get update && apt-get install python"+ ]+ in do+ ruleCatches "DL3009" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3009" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3010Spec.hs view
@@ -0,0 +1,61 @@+module Hadolint.Rule.DL3010Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3010 - Use `ADD` for extracting archives into an image" $ do+ it "catch: copy archive then extract 1" $+ let dockerFile =+ [ "COPY packaged-app.tar /usr/src/app",+ "RUN tar -xf /usr/src/app/packaged-app.tar"+ ]+ in do+ ruleCatches "DL3010" $ Text.unlines dockerFile+ it "catch: copy archive then extract 2" $+ let dockerFile =+ [ "COPY packaged-app.tar /usr/src/app",+ "WORKDIR /usr/src/app",+ "RUN foo bar && echo something && tar -xf packaged-app.tar"+ ]+ in do+ ruleCatches "DL3010" $ Text.unlines dockerFile+ it "catch: copy archive then extract 3" $+ let dockerFile =+ [ "COPY foo/bar/packaged-app.tar /foo.tar",+ "RUN tar -xf /foo.tar"+ ]+ in do+ ruleCatches "DL3010" $ Text.unlines dockerFile+ it "catch: copy archive then extract windows paths 1" $+ let dockerFile =+ [ "COPY build\\foo\\bar.tar.gz \"C:\\Program Files\\Foo\"",+ "RUN tar -xf \"C:\\Program Files\\Foo\\bar.tar.gz\""+ ]+ in do+ ruleCatches "DL3010" $ Text.unlines dockerFile+ it "catch: copy archive then extract windows paths 2" $+ let dockerFile =+ [ "COPY build\\foo\\bar.tar.gz \"C:\\Program Files\\foo.tar.gz\"",+ "RUN tar -xf foo.tar.gz"+ ]+ in do+ ruleCatches "DL3010" $ Text.unlines dockerFile+ it "ignore: copy archive without extract" $+ let dockerFile =+ [ "COPY packaged-app.tar /usr/src/app",+ "FROM debian:11 as newstage"+ ]+ in do+ ruleCatchesNot "DL3010" $ Text.unlines dockerFile+ it "ignore: non archive" $+ ruleCatchesNot "DL3010" "COPY package.json /usr/src/app"+ it "ignore: copy from previous stage" $+ ruleCatchesNot "DL3010"+ "COPY --from=builder /usr/local/share/some.tar /opt/some.tar"
+ test/Hadolint/Rule/DL3011Spec.hs view
@@ -0,0 +1,14 @@+module Hadolint.Rule.DL3011Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "EXPOSE rules" $ do+ it "invalid port" $ ruleCatches "DL3011" "EXPOSE 80000"+ it "valid port" $ ruleCatchesNot "DL3011" "EXPOSE 60000"
+ test/Hadolint/Rule/DL3012Spec.hs view
@@ -0,0 +1,20 @@+module Hadolint.Rule.DL3012Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3012 - Multiple `HEALTHCHECK` instructions" $ do+ it "ok with no HEALTHCHECK instruction" $+ ruleCatchesNot "DL3012" "FROM scratch"+ it "ok with one HEALTHCHECK instruction" $+ ruleCatchesNot "DL3012" "FROM scratch\nHEALTHCHECK CMD /bin/bla"+ it "ok with two HEALTHCHECK instructions in two stages" $+ ruleCatchesNot "DL3012" "FROM scratch\nHEALTHCHECK CMD /bin/bla1\nFROM scratch\nHEALTHCHECK CMD /bin/bla2"+ it "warn with two HEALTHCHECK instructions" $+ ruleCatches "DL3012" "FROM scratch\nHEALTHCHECK CMD /bin/bla1\nHEALTHCHECK CMD /bin/bla2"
+ test/Hadolint/Rule/DL3013Spec.hs view
@@ -0,0 +1,141 @@+module Hadolint.Rule.DL3013Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3013 - Pin versions in pip." $ do+ it "pip2 version not pinned" $ do+ ruleCatches "DL3013" "RUN pip2 install MySQL_python"+ onBuildRuleCatches "DL3013" "RUN pip2 install MySQL_python"+ it "pip3 version not pinned" $ do+ ruleCatches "DL3013" "RUN pip3 install MySQL_python"+ onBuildRuleCatches "DL3013" "RUN pip2 install MySQL_python"+ it "pip3 version pinned" $ do+ ruleCatchesNot "DL3013" "RUN pip3 install MySQL_python==1.2.2"+ onBuildRuleCatchesNot "DL3013" "RUN pip3 install MySQL_python==1.2.2"+ it "pip3 install from local package" $ do+ ruleCatchesNot "DL3013" "RUN pip3 install mypkg.whl"+ ruleCatchesNot "DL3013" "RUN pip3 install mypkg.tar.gz"+ onBuildRuleCatchesNot "DL3013" "RUN pip3 install mypkg.whl"+ onBuildRuleCatchesNot "DL3013" "RUN pip3 install mypkg.tar.gz"+ it "pip install requirements" $ do+ ruleCatchesNot "DL3013" "RUN pip install -r requirements.txt"+ onBuildRuleCatchesNot "DL3013" "RUN pip install -r requirements.txt"+ it "pip install requirements with long flag" $ do+ ruleCatchesNot "DL3013" "RUN pip install --requirement requirements.txt"+ onBuildRuleCatchesNot "DL3013" "RUN pip install --requirement requirements.txt"+ it "pip install use setup.py" $ do+ ruleCatchesNot "DL3013" "RUN pip install ."+ onBuildRuleCatchesNot "DL3013" "RUN pip install ."+ it "pip version not pinned" $ do+ ruleCatches "DL3013" "RUN pip install MySQL_python"+ onBuildRuleCatches "DL3013" "RUN pip install MySQL_python"+ it "pip version pinned" $ do+ ruleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2"+ onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2"+ it "pip version pinned with ~= operator" $ do+ ruleCatchesNot "DL3013" "RUN pip install MySQL_python~=1.2.2"+ onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python~=1.2.2"+ it "pip version pinned with === operator" $ do+ ruleCatchesNot "DL3013" "RUN pip install MySQL_python===1.2.2"+ onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python===1.2.2"+ it "pip version pinned with flag --ignore-installed" $ do+ ruleCatchesNot "DL3013" "RUN pip install --ignore-installed MySQL_python==1.2.2"+ onBuildRuleCatchesNot "DL3013" "RUN pip install --ignore-installed MySQL_python==1.2.2"+ it "pip version pinned with flag --build" $ do+ ruleCatchesNot "DL3013" "RUN pip3 install --build /opt/yamllint yamllint==1.20.0"+ onBuildRuleCatchesNot "DL3013" "RUN pip3 install --build /opt/yamllint yamllint==1.20.0"+ it "pip version pinned with flag --prefix" $ do+ ruleCatchesNot "DL3013" "RUN pip3 install --prefix /opt/yamllint yamllint==1.20.0"+ onBuildRuleCatchesNot "DL3013" "RUN pip3 install --prefix /opt/yamllint yamllint==1.20.0"+ it "pip version pinned with flag --root" $ do+ ruleCatchesNot "DL3013" "RUN pip3 install --root /opt/yamllint yamllint==1.20.0"+ onBuildRuleCatchesNot "DL3013" "RUN pip3 install --root /opt/yamllint yamllint==1.20.0"+ it "pip version pinned with flag --target" $ do+ ruleCatchesNot "DL3013" "RUN pip3 install --target /opt/yamllint yamllint==1.20.0"+ onBuildRuleCatchesNot "DL3013" "RUN pip3 install --target /opt/yamllint yamllint==1.20.0"+ it "pip version pinned with flag --trusted-host" $ do+ ruleCatchesNot "DL3013" "RUN pip3 install --trusted-host host example==1.2.2"+ onBuildRuleCatchesNot "DL3013" "RUN pip3 install --trusted-host host example==1.2.2"+ it "pip version pinned with python -m" $ do+ ruleCatchesNot "DL3013" "RUN python -m pip install example==1.2.2"+ onBuildRuleCatchesNot "DL3013" "RUN python -m pip install example==1.2.2"+ it "pip version not pinned with python -m" $ do+ ruleCatches "DL3013" "RUN python -m pip install example"+ onBuildRuleCatches "DL3013" "RUN python -m pip install --index-url url example"+ it "pip install git" $ do+ ruleCatchesNot+ "DL3013"+ "RUN pip install git+https://github.com/rtfd/r-ext.git@0.6-alpha#egg=r-ext"+ onBuildRuleCatchesNot+ "DL3013"+ "RUN pip install git+https://github.com/rtfd/r-ext.git@0.6-alpha#egg=r-ext"+ it "pip install unversioned git" $ do+ ruleCatches+ "DL3013"+ "RUN pip install git+https://github.com/rtfd/read-ext.git#egg=read-ext"+ onBuildRuleCatches+ "DL3013"+ "RUN pip install git+https://github.com/rtfd/read-ext.git#egg=read-ext"+ it "pip install upper bound" $ do+ ruleCatchesNot "DL3013" "RUN pip install 'alabaster>=0.7'"+ onBuildRuleCatchesNot "DL3013" "RUN pip install 'alabaster>=0.7'"+ it "pip install lower bound" $ do+ ruleCatchesNot "DL3013" "RUN pip install 'alabaster<0.7'"+ onBuildRuleCatchesNot "DL3013" "RUN pip install 'alabaster<0.7'"+ it "pip install excluded version" $ do+ ruleCatchesNot "DL3013" "RUN pip install 'alabaster!=0.7'"+ onBuildRuleCatchesNot "DL3013" "RUN pip install 'alabaster!=0.7'"+ it "pip install user directory" $ do+ ruleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2 --user"+ onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2 --user"+ it "pip install no pip version check" $ do+ ruleCatchesNot+ "DL3013"+ "RUN pip install MySQL_python==1.2.2 --disable-pip-version-check"+ onBuildRuleCatchesNot+ "DL3013"+ "RUN pip install MySQL_python==1.2.2 --disable-pip-version-check"+ it "pip install --index-url" $ do+ ruleCatchesNot+ "DL3013"+ "RUN pip install --index-url https://eg.com/foo foobar==1.0.0"+ onBuildRuleCatchesNot+ "DL3013"+ "RUN pip install --index-url https://eg.com/foo foobar==1.0.0"+ it "pip install index-url with -i flag" $ do+ ruleCatchesNot+ "DL3013"+ "RUN pip install --index-url https://eg.com/foo foobar==1.0.0"+ onBuildRuleCatchesNot+ "DL3013"+ "RUN pip install --index-url https://eg.com/foo foobar==1.0.0"+ it "pip install --index-url with --extra-index-url" $ do+ ruleCatchesNot+ "DL3013"+ "RUN pip install --index-url https://eg.com/foo --extra-index-url https://ex-eg.io/foo foobar==1.0.0"+ onBuildRuleCatchesNot+ "DL3013"+ "RUN pip install --index-url https://eg.com/foo --extra-index-url https://ex-eg.io/foo foobar==1.0.0"+ it "pip install no cache dir" $ do+ ruleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2 --no-cache-dir"+ onBuildRuleCatchesNot "DL3013" "RUN pip install MySQL_python==1.2.2 --no-cache-dir"+ it "pip install constraints file - long version argument" $ do+ ruleCatchesNot "DL3013" "RUN pip install pykafka --constraint http://foo.bar.baz"+ onBuildRuleCatchesNot "DL3013" "RUN pip install pykafka --constraint http://foo.bar.baz"+ it "pip install constraints file - short version argument" $ do+ ruleCatchesNot "DL3013" "RUN pip install pykafka -c http://foo.bar.baz"+ onBuildRuleCatchesNot "DL3013" "RUN pip install pykafka -c http://foo.bar.baz"+ it "pip install --index-url with --extra-index-url with basic auth" $ do+ ruleCatchesNot+ "DL3013"+ "RUN pip install --index-url https://user:pass@eg.com/foo --extra-index-url https://user:pass@ex-eg.io/foo foobar==1.0.0"+ onBuildRuleCatchesNot+ "DL3013"+ "RUN pip install --index-url https://user:pass@eg.com/foo --extra-index-url https://user:pass@ex-eg.io/foo foobar==1.0.0"
+ test/Hadolint/Rule/DL3014Spec.hs view
@@ -0,0 +1,33 @@+module Hadolint.Rule.DL3014Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3014 - Use the -y switch." $ do+ it "apt-get auto yes" $ do+ ruleCatches "DL3014" "RUN apt-get install python"+ onBuildRuleCatches "DL3014" "RUN apt-get install python"+ it "apt-get yes shortflag" $ do+ ruleCatchesNot "DL3014" "RUN apt-get install -yq python"+ onBuildRuleCatchesNot "DL3014" "RUN apt-get install -yq python"+ it "apt-get yes quiet level 2 implies -y" $ do+ ruleCatchesNot "DL3014" "RUN apt-get install -qq python"+ onBuildRuleCatchesNot "DL3014" "RUN apt-get install -qq python"+ it "apt-get yes different pos" $ do+ ruleCatchesNot "DL3014" "RUN apt-get install -y python"+ onBuildRuleCatchesNot "DL3014" "RUN apt-get install -y python"+ it "apt-get with auto yes" $ do+ ruleCatchesNot "DL3014" "RUN apt-get -y install python"+ onBuildRuleCatchesNot "DL3014" "RUN apt-get -y install python"+ it "apt-get with auto expanded yes" $ do+ ruleCatchesNot "DL3014" "RUN apt-get --yes install python"+ onBuildRuleCatchesNot "DL3014" "RUN apt-get --yes install python"+ it "apt-get with assume-yes" $ do+ ruleCatchesNot "DL3014" "RUN apt-get --assume-yes install python"+ onBuildRuleCatchesNot "DL3014" "RUN apt-get --assume-yes install python"
+ test/Hadolint/Rule/DL3015Spec.hs view
@@ -0,0 +1,28 @@+module Hadolint.Rule.DL3015Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3015 - Avoid additional packages by specifying `--no-install-recommends`." $ do+ it "apt-get install recommends" $ do+ ruleCatchesNot+ "DL3015"+ "RUN apt-get install --no-install-recommends python"+ onBuildRuleCatchesNot+ "DL3015"+ "RUN apt-get install --no-install-recommends python"+ it "apt-get no install recommends" $ do+ ruleCatches "DL3015" "RUN apt-get install python"+ onBuildRuleCatches "DL3015" "RUN apt-get install python"+ it "apt-get no install recommends" $ do+ ruleCatches "DL3015" "RUN apt-get -y install python"+ onBuildRuleCatches "DL3015" "RUN apt-get -y install python"+ it "apt-get no install recommends via option" $ do+ ruleCatchesNot "DL3015" "RUN apt-get -o APT::Install-Recommends=false install python"+ onBuildRuleCatchesNot "DL3015" "RUN apt-get -o APT::Install-Recommends=false install python"
+ test/Hadolint/Rule/DL3016Spec.hs view
@@ -0,0 +1,124 @@+module Hadolint.Rule.DL3016Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def+ describe "DL3016 - Pin versions in npm." $ do+ it "version pinned in package.json" $ do+ ruleCatchesNot "DL3016" "RUN npm install"+ onBuildRuleCatchesNot "DL3016" "RUN npm install"+ it "version pinned in package.json with arguments" $ do+ ruleCatchesNot "DL3016" "RUN npm install --progress=false"+ onBuildRuleCatchesNot "DL3016" "RUN npm install --progress=false"+ it "version pinned" $ do+ ruleCatchesNot "DL3016" "RUN npm install express@4.1.1"+ onBuildRuleCatchesNot "DL3016" "RUN npm install express@4.1.1"+ it "version pinned with scope" $ do+ ruleCatchesNot "DL3016" "RUN npm install @myorg/privatepackage@\">=0.1.0\""+ onBuildRuleCatchesNot "DL3016" "RUN npm install @myorg/privatepackage@\">=0.1.0\""+ it "version pinned multiple packages" $ do+ ruleCatchesNot "DL3016" "RUN npm install express@\"4.1.1\" sax@0.1.1"+ onBuildRuleCatchesNot "DL3016" "RUN npm install express@\"4.1.1\" sax@0.1.1"+ it "version pinned with --global" $ do+ ruleCatchesNot "DL3016" "RUN npm install --global express@\"4.1.1\""+ onBuildRuleCatchesNot "DL3016" "RUN npm install --global express@\"4.1.1\""+ it "version pinned with -g" $ do+ ruleCatchesNot "DL3016" "RUN npm install -g express@\"4.1.1\""+ onBuildRuleCatchesNot "DL3016" "RUN npm install -g express@\"4.1.1\""+ it "version does not have to be pinned for tarball suffix .tar" $ do+ ruleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tar"+ onBuildRuleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tar"+ it "version does not have to be pinned for tarball suffix .tar.gz" $ do+ ruleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tar.gz"+ onBuildRuleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tar.gz"+ it "version does not have to be pinned for tarball suffix .tgz" $ do+ ruleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tgz"+ onBuildRuleCatchesNot "DL3016" "RUN npm install package-v1.2.3.tgz"+ it "version does not have to be pinned for folder - absolute path" $ do+ ruleCatchesNot "DL3016" "RUN npm install /folder"+ onBuildRuleCatchesNot "DL3016" "RUN npm install /folder"+ it "version does not have to be pinned for folder - relative path from current folder" $ do+ ruleCatchesNot "DL3016" "RUN npm install ./folder"+ onBuildRuleCatchesNot "DL3016" "RUN npm install ./folder"+ it "version does not have to be pinned for folder - relative path to parent folder" $ do+ ruleCatchesNot "DL3016" "RUN npm install ../folder"+ onBuildRuleCatchesNot "DL3016" "RUN npm install ../folder"+ it "version does not have to be pinned for folder - relative path from home" $ do+ ruleCatchesNot "DL3016" "RUN npm install ~/folder"+ onBuildRuleCatchesNot "DL3016" "RUN npm install ~/folder"+ it "commit pinned for git+ssh" $ do+ ruleCatchesNot+ "DL3016"+ "RUN npm install git+ssh://git@github.com:npm/npm.git#v1.0.27"+ onBuildRuleCatchesNot+ "DL3016"+ "RUN npm install git+ssh://git@github.com:npm/npm.git#v1.0.27"+ it "commit pinned for git+http" $ do+ ruleCatchesNot+ "DL3016"+ "RUN npm install git+http://isaacs@github.com/npm/npm#semver:^5.0"+ onBuildRuleCatchesNot+ "DL3016"+ "RUN npm install git+http://isaacs@github.com/npm/npm#semver:^5.0"+ it "commit pinned for git+https" $ do+ ruleCatchesNot+ "DL3016"+ "RUN npm install git+https://isaacs@github.com/npm/npm.git#v1.0.27"+ onBuildRuleCatchesNot+ "DL3016"+ "RUN npm install git+https://isaacs@github.com/npm/npm.git#v1.0.27"+ it "commit pinned for git" $ do+ ruleCatchesNot+ "DL3016"+ "RUN npm install git://github.com/npm/npm.git#v1.0.27"+ onBuildRuleCatchesNot+ "DL3016"+ "RUN npm install git://github.com/npm/npm.git#v1.0.27"+ it "npm run install is fine" $ do+ ruleCatchesNot+ "DL3016"+ "RUN npm run --crazy install"+ onBuildRuleCatchesNot+ "DL3016"+ "RUN npm run --crazy install"++ --version range is not supported+ it "version pinned with scope" $ do+ ruleCatchesNot "DL3016" "RUN npm install @myorg/privatepackage@\">=0.1.0 <0.2.0\""+ onBuildRuleCatchesNot "DL3016" "RUN npm install @myorg/privatepackage@\">=0.1.0 <0.2.0\""+ it "version not pinned" $ do+ ruleCatches "DL3016" "RUN npm install express"+ onBuildRuleCatches "DL3016" "RUN npm install express"+ it "version not pinned with scope" $ do+ ruleCatches "DL3016" "RUN npm install @myorg/privatepackage"+ onBuildRuleCatches "DL3016" "RUN npm install @myorg/privatepackage"+ it "version not pinned multiple packages" $ do+ ruleCatches "DL3016" "RUN npm install express sax@0.1.1"+ onBuildRuleCatches "DL3016" "RUN npm install express sax@0.1.1"+ it "version not pinned with --global" $ do+ ruleCatches "DL3016" "RUN npm install --global express"+ onBuildRuleCatches "DL3016" "RUN npm install --global express"+ it "commit not pinned for git+ssh" $ do+ ruleCatches "DL3016" "RUN npm install git+ssh://git@github.com:npm/npm.git"+ onBuildRuleCatches "DL3016" "RUN npm install git+ssh://git@github.com:npm/npm.git"+ it "commit not pinned for git+http" $ do+ ruleCatches "DL3016" "RUN npm install git+http://isaacs@github.com/npm/npm"+ onBuildRuleCatches "DL3016" "RUN npm install git+http://isaacs@github.com/npm/npm"+ it "commit not pinned for git+https" $ do+ ruleCatches+ "DL3016"+ "RUN npm install git+https://isaacs@github.com/npm/npm.git"+ onBuildRuleCatches+ "DL3016"+ "RUN npm install git+https://isaacs@github.com/npm/npm.git"+ it "commit not pinned for git" $ do+ ruleCatches "DL3016" "RUN npm install git://github.com/npm/npm.git"+ onBuildRuleCatches "DL3016" "RUN npm install git://github.com/npm/npm.git"+ it "don't fire on loglevel flag" $ do+ ruleCatchesNot "DL3016" "RUN npm install --loglevel verbose sax@0.1.1"+ onBuildRuleCatchesNot "DL3016" "RUN npm install --loglevel verbose sax@0.1.1"
+ test/Hadolint/Rule/DL3018Spec.hs view
@@ -0,0 +1,92 @@+module Hadolint.Rule.DL3018Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3018 - Pin versions in `apk add`." $ do+ it "apk add version pinning single" $ do+ ruleCatches "DL3018" "RUN apk add flex"+ onBuildRuleCatches "DL3018" "RUN apk add flex"+ it "apk add no version pinning single" $ do+ ruleCatchesNot "DL3018" "RUN apk add flex=2.6.4-r1"+ onBuildRuleCatchesNot "DL3018" "RUN apk add flex=2.6.4-r1"+ it "apk add version pinned chained" $+ let dockerFile =+ [ "RUN apk add --no-cache flex=2.6.4-r1 \\",+ " && pip install -r requirements.txt"+ ]+ in do+ ruleCatchesNot "DL3018" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile+ it "apk add version pinned regression" $+ let dockerFile =+ [ "RUN apk add --no-cache \\",+ "flex=2.6.4-r1 \\",+ "libffi=3.2.1-r3 \\",+ "python2=2.7.13-r1 \\",+ "libbz2=1.0.6-r5"+ ]+ in do+ ruleCatchesNot "DL3018" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile+ it "apk add version pinned regression - one missed" $+ let dockerFile =+ [ "RUN apk add --no-cache \\",+ "flex=2.6.4-r1 \\",+ "libffi \\",+ "python2=2.7.13-r1 \\",+ "libbz2=1.0.6-r5"+ ]+ in do+ ruleCatches "DL3018" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3018" $ Text.unlines dockerFile+ it "apk add virtual package" $+ let dockerFile =+ [ "RUN apk add \\",+ "--virtual build-dependencies \\",+ "python-dev=1.1.1 build-base=2.2.2 wget=3.3.3 \\",+ "&& pip install -r requirements.txt \\",+ "&& python setup.py install \\",+ "&& apk del build-dependencies"+ ]+ in do+ ruleCatchesNot "DL3018" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile+ it "apk add with repository without equal sign" $+ let dockerFile =+ [ "RUN apk add --no-cache \\",+ "--repository https://nl.alpinelinux.org/alpine/edge/testing \\",+ "flow=0.78.0-r0"+ ]+ in do+ ruleCatchesNot "DL3018" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile+ it "apk add with repository with equal sign" $+ let dockerFile =+ [ "RUN apk add --no-cache \\",+ "--repository=https://nl.alpinelinux.org/alpine/edge/testing \\",+ "flow=0.78.0-r0"+ ]+ in do+ ruleCatchesNot "DL3018" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile+ it "apk add with repository (-X) without equal sign" $+ let dockerFile =+ [ "RUN apk add --no-cache \\",+ "-X https://nl.alpinelinux.org/alpine/edge/testing \\",+ "flow=0.78.0-r0"+ ]+ in do+ ruleCatchesNot "DL3018" $ Text.unlines dockerFile+ onBuildRuleCatchesNot "DL3018" $ Text.unlines dockerFile++ it "don't trigger when installing from .apk file" $ do+ ruleCatchesNot "DL3018" "RUN apk add mypackage-1.1.1.apk"+ onBuildRuleCatchesNot "DL3018" "RUN apk add mypackage-1.1.1.apk"
+ test/Hadolint/Rule/DL3019Spec.hs view
@@ -0,0 +1,27 @@+module Hadolint.Rule.DL3019Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3019 - Use the --no-cache switch." $ do+ it "apk add with --no-cache" $ do+ ruleCatches "DL3019" "RUN apk add flex=2.6.4-r1"+ onBuildRuleCatches "DL3019" "RUN apk add flex=2.6.4-r1"+ it "apk add without --no-cache" $ do+ ruleCatchesNot "DL3019" "RUN apk add --no-cache flex=2.6.4-r1"+ onBuildRuleCatchesNot "DL3019" "RUN apk add --no-cache flex=2.6.4-r1"+ it "don't warn: apk add with BuildKit cache mount" $ do+ ruleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/apk apk add -U curl=7.77.0"+ onBuildRuleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/apk apk add -U curl=7.77.0"+ it "don't warn: apk add with BuildKit cache mount in wrong dir and --no-cache" $ do+ ruleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add --no-cache -U curl=7.77.0"+ onBuildRuleCatchesNot "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add --no-cache -U curl=7.77.0"+ it "warn: apk add with BuildKit cache mount to wrong dir" $ do+ ruleCatches "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add -U curl=7.77.0"+ onBuildRuleCatches "DL3019" "RUN --mount=type=cache,target=/var/cache/foo apk add -U curl=7.77.0"
+ test/Hadolint/Rule/DL3020Spec.hs view
@@ -0,0 +1,23 @@+module Hadolint.Rule.DL3020Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3020 - Use `COPY` instead of `ADD` for files and folders." $ do+ it "add for tar" $ ruleCatchesNot "DL3020" "ADD file.tar /usr/src/app/"+ it "add for gzip" $ ruleCatchesNot "DL3020" "ADD file.gz /usr/src/app/"+ it "add for bz2" $ ruleCatchesNot "DL3020" "ADD file.bz2 /usr/src/app/"+ it "add for xz" $ ruleCatchesNot "DL3020" "ADD file.xz /usr/src/app/"+ it "add for tgz" $ ruleCatchesNot "DL3020" "ADD file.tgz /usr/src/app/"+ it "add for url" $ ruleCatchesNot "DL3020" "ADD http://file.com /usr/src/app/"+ it "using add" $ ruleCatches "DL3020" "ADD file /usr/src/app/"+ it "warn for zip" $ ruleCatches "DL3020" "ADD file.zip /usr/src/app/"+ it "add for tgz with quotes" $ ruleCatchesNot "DL3020" "ADD \"file.tgz\" /usr/src/app/"+ it "add for url with quotes" $ ruleCatchesNot "DL3020" "ADD \"http://file.com\" /usr/src/app/"+ it "warn for zip with quotes" $ ruleCatches "DL3020" "ADD \"file.zip\" /usr/src/app/"
+ test/Hadolint/Rule/DL3021Spec.hs view
@@ -0,0 +1,15 @@+module Hadolint.Rule.DL3021Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3021 - `COPY` with more than 2 arguments requires the last argument to end with `/`" $ do+ it "no warn on 2 args" $ ruleCatchesNot "DL3021" "COPY foo bar"+ it "warn on 3 args" $ ruleCatches "DL3021" "COPY foo bar baz"+ it "no warn on 3 args" $ ruleCatchesNot "DL3021" "COPY foo bar baz/"
+ test/Hadolint/Rule/DL3022Spec.hs view
@@ -0,0 +1,31 @@+module Hadolint.Rule.DL3022Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3022 - `COPY --from` should reference a previously defined `FROM` alias" $ do+ it "warn on missing alias" $ ruleCatches "DL3022" "COPY --from=foo bar ."+ it "warn on alias defined after" $+ let dockerFile =+ [ "FROM scratch",+ "COPY --from=build foo .",+ "FROM node as build",+ "RUN baz"+ ]+ in ruleCatches "DL3022" $ Text.unlines dockerFile+ it "don't warn on correctly defined aliases" $+ let dockerFile =+ [ "FROM scratch as build",+ "RUN foo",+ "FROM node",+ "COPY --from=build foo .",+ "RUN baz"+ ]+ in ruleCatchesNot "DL3022" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3023Spec.hs view
@@ -0,0 +1,28 @@+module Hadolint.Rule.DL3023Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3023 - `COPY --from` cannot reference its own `FROM` alias" $ do+ it "warn on copying from your the same FROM" $+ let dockerFile =+ [ "FROM node as foo",+ "COPY --from=foo bar ."+ ]+ in ruleCatches "DL3023" $ Text.unlines dockerFile+ it "don't warn on copying from other sources" $+ let dockerFile =+ [ "FROM scratch as build",+ "RUN foo",+ "FROM node as run",+ "COPY --from=build foo .",+ "RUN baz"+ ]+ in ruleCatchesNot "DL3023" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3024Spec.hs view
@@ -0,0 +1,29 @@+module Hadolint.Rule.DL3024Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3024 - Duplicate aliases" $ do+ it "warn on duplicate aliases" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN something",+ "FROM scratch as foo",+ "RUN something"+ ]+ in ruleCatches "DL3024" $ Text.unlines dockerFile+ it "don't warn on unique aliases" $+ let dockerFile =+ [ "FROM scratch as build",+ "RUN foo",+ "FROM node as run",+ "RUN baz"+ ]+ in ruleCatchesNot "DL3024" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3025Spec.hs view
@@ -0,0 +1,38 @@+module Hadolint.Rule.DL3025Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3025 - Use arguments JSON notation for `CMD` and `ENTRYPOINT` arguments" $ do+ it "warn on ENTRYPOINT" $+ let dockerFile =+ [ "FROM node as foo",+ "ENTRYPOINT something"+ ]+ in ruleCatches "DL3025" $ Text.unlines dockerFile+ it "don't warn on ENTRYPOINT json notation" $+ let dockerFile =+ [ "FROM scratch as build",+ "ENTRYPOINT [\"foo\", \"bar\"]"+ ]+ in ruleCatchesNot "DL3025" $ Text.unlines dockerFile+ it "warn on CMD" $+ let dockerFile =+ [ "FROM node as foo",+ "CMD something"+ ]+ in ruleCatches "DL3025" $ Text.unlines dockerFile+ it "don't warn on CMD json notation" $+ let dockerFile =+ [ "FROM scratch as build",+ "CMD [\"foo\", \"bar\"]",+ "CMD [ \"foo\", \"bar\" ]"+ ]+ in ruleCatchesNot "DL3025" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3026Spec.hs view
@@ -0,0 +1,62 @@+module Hadolint.Rule.DL3026Spec (spec) where++import Data.Default+import Data.Text as Text+import Hadolint (Configuration (..))+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL2036 - Use only an allowed registry in the FROM image" $ do+ it "does not warn on empty allowed registries" $ do+ let dockerFile =+ [ "FROM random.com/debian"+ ]+ ruleCatchesNot "DL3026" $ Text.unlines dockerFile++ it "warn on non-allowed registry" $ do+ let dockerFile =+ [ "FROM random.com/debian"+ ]+ let ?config = def { allowedRegistries = ["docker.io"] }++ ruleCatches "DL3026" $ Text.unlines dockerFile++ it "does not warn on allowed registries" $ do+ let dockerFile =+ [ "FROM random.com/debian"+ ]+ let ?config = def { allowedRegistries = ["x.com", "random.com"] }++ ruleCatchesNot "DL3026" $ Text.unlines dockerFile++ it "doesn't warn on scratch image" $ do+ let dockerFile =+ [ "FROM scratch"+ ]+ let ?config = def { allowedRegistries = ["x.com", "random.com"] }++ ruleCatchesNot "DL3026" $ Text.unlines dockerFile++ it "allows boths all forms of docker.io" $ do+ let dockerFile =+ [ "FROM ubuntu:18.04 AS builder1",+ "FROM zemanlx/ubuntu:18.04 AS builder2",+ "FROM docker.io/zemanlx/ubuntu:18.04 AS builder3"+ ]+ let ?config = def { allowedRegistries = ["docker.io"] }++ ruleCatchesNot "DL3026" $ Text.unlines dockerFile++ it "allows using previous stages" $ do+ let dockerFile =+ [ "FROM random.com/foo AS builder1",+ "FROM builder1 AS builder2"+ ]+ let ?config = def { allowedRegistries = ["random.com"] }++ ruleCatchesNot "DL3026" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3027Spec.hs view
@@ -0,0 +1,21 @@+module Hadolint.Rule.DL3027Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3027 - Do not use `apt` as it is meant to be a end-user tool, use `apt-get` or `apt-cache` instead" $ do+ it "apt" $+ let dockerFile =+ [ "FROM ubuntu",+ "RUN apt install python"+ ]+ in do+ ruleCatches "DL3027" $ Text.unlines dockerFile+ onBuildRuleCatches "DL3027" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3028Spec.hs view
@@ -0,0 +1,44 @@+module Hadolint.Rule.DL3028Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3028 - Pin versions in gem install." $+ describe "version pinning" $ do+ describe "i" $ do+ it "unpinned" $ do+ ruleCatches "DL3028" "RUN gem i bundler"+ onBuildRuleCatches "DL3028" "RUN gem i bundler"+ it "pinned" $ do+ ruleCatchesNot "DL3028" "RUN gem i bundler:1"+ onBuildRuleCatchesNot "DL3028" "RUN gem i bundler:1"+ it "multi" $ do+ ruleCatches "DL3028" "RUN gem i bunlder:1 nokogiri"+ onBuildRuleCatches "DL3028" "RUN gem i bunlder:1 nokogiri"+ ruleCatchesNot "DL3028" "RUN gem i bunlder:1 nokogirii:1"+ onBuildRuleCatchesNot "DL3028" "RUN gem i bunlder:1 nokogiri:1"+ describe "install" $ do+ it "unpinned" $ do+ ruleCatches "DL3028" "RUN gem install bundler"+ onBuildRuleCatches "DL3028" "RUN gem install bundler"+ it "pinned" $ do+ ruleCatchesNot "DL3028" "RUN gem install bundler:1"+ onBuildRuleCatchesNot "DL3028" "RUN gem install bundler:1"+ it "does not warn on -v" $ do+ ruleCatchesNot "DL3028" "RUN gem install bundler -v '2.0.1'"+ onBuildRuleCatchesNot "DL3028" "RUN gem install bundler -v '2.0.1'"+ it "does not warn on --version without =" $ do+ ruleCatchesNot "DL3028" "RUN gem install bundler --version '2.0.1'"+ onBuildRuleCatchesNot "DL3028" "RUN gem install bundler --version '2.0.1'"+ it "does not warn on --version with =" $ do+ ruleCatchesNot "DL3028" "RUN gem install bundler --version='2.0.1'"+ onBuildRuleCatchesNot "DL3028" "RUN gem install bundler --version='2.0.1'"+ it "does not warn on extra flags" $ do+ ruleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 -- --use-system-libraries=true"+ onBuildRuleCatchesNot "DL3028" "RUN gem install bundler:2.0.1 -- --use-system-libraries=true"
+ test/Hadolint/Rule/DL3029Spec.hs view
@@ -0,0 +1,14 @@+module Hadolint.Rule.DL3029Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3029 - Do not use --platform flag with FROM." $ do+ it "explicit platform flag" $ ruleCatches "DL3029" "FROM --platform=linux debian:jessie"+ it "no platform flag" $ ruleCatchesNot "DL3029" "FROM debian:jessie"
+ test/Hadolint/Rule/DL3030Spec.hs view
@@ -0,0 +1,19 @@+module Hadolint.Rule.DL3030Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def+ describe "DL3030 - Use the `-y` switch to avoid manual input `yum install -y <package>`" $ do+ it "not ok when not using `-y` switch" $ do+ ruleCatches "DL3030" "RUN yum install httpd-2.4.24 && yum clean all"+ onBuildRuleCatches "DL3030" "RUN yum install httpd-2.4.24 && yum clean all"+ it "ok when using `-y` switch" $ do+ ruleCatchesNot "DL3030" "RUN yum install -y httpd-2.4.24 && yum clean all"+ ruleCatchesNot "DL3030" "RUN bash -c `# not even a yum command`"+ onBuildRuleCatchesNot "DL3030" "RUN yum install -y httpd-2.4.24 && yum clean all"+ onBuildRuleCatchesNot "DL3030" "RUN bash -c `# not even a yum command`"
+ test/Hadolint/Rule/DL3032Spec.hs view
@@ -0,0 +1,20 @@+module Hadolint.Rule.DL3032Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3032 - `yum clean all` missing after yum command." $ do+ it "not ok with no clean all" $ do+ ruleCatches "DL3032" "RUN yum install -y mariadb-10.4"+ onBuildRuleCatches "DL3032" "RUN yum install -y mariadb-10.4"+ it "ok with yum clean all " $ do+ ruleCatchesNot "DL3032" "RUN yum install -y mariadb-10.4 && yum clean all"+ ruleCatchesNot "DL3032" "RUN bash -c `# not even a yum command`"+ onBuildRuleCatchesNot "DL3032" "RUN yum install -y mariadb-10.4 && yum clean all"+ onBuildRuleCatchesNot "DL3032" "RUN bash -c `# not even a yum command`"
+ test/Hadolint/Rule/DL3033Spec.hs view
@@ -0,0 +1,28 @@+module Hadolint.Rule.DL3033Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3033 - Specify version with `yum install -y <package>-<version>`" $ do+ it "not ok wihout yum version pinning" $ do+ ruleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"+ onBuildRuleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"+ it "ok with yum version pinning" $ do+ ruleCatchesNot "DL3033" "RUN yum install -y tomcat-9.2 && yum clean all"+ ruleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"+ onBuildRuleCatchesNot "DL3033" "RUN yum install -y tomcat-9.2 && yum clean all"+ onBuildRuleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"+ it "not ok wihout yum version pinning - modules" $ do+ ruleCatches "DL3033" "RUN yum module install -y tomcat && yum clean all"+ onBuildRuleCatches "DL3033" "RUN yum module install -y tomcat && yum clean all"+ it "ok with yum version pinning - modules" $ do+ ruleCatchesNot "DL3033" "RUN yum module install -y tomcat:9 && yum clean all"+ ruleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"+ onBuildRuleCatchesNot "DL3033" "RUN yum module install -y tomcat:9 && yum clean all"+ onBuildRuleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"
+ test/Hadolint/Rule/DL3034Spec.hs view
@@ -0,0 +1,24 @@+module Hadolint.Rule.DL3034Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3034 - Non-interactive switch missing from zypper command: `zypper install -y`" $ do+ it "not ok without non-interactive switch" $ do+ ruleCatches "DL3034" "RUN zypper install httpd=2.4.24 && zypper clean"+ onBuildRuleCatches "DL3034" "RUN zypper install httpd=2.4.24 && zypper clean"+ it "ok with non-interactive switch present" $ do+ ruleCatchesNot "DL3034" "RUN zypper install -n httpd=2.4.24 && zypper clean"+ ruleCatchesNot "DL3034" "RUN zypper install --non-interactive httpd=2.4.24 && zypper clean"+ ruleCatchesNot "DL3034" "RUN zypper install -y httpd=2.4.24 && zypper clean"+ ruleCatchesNot "DL3034" "RUN zypper install --no-confirm httpd=2.4.24 && zypper clean"+ onBuildRuleCatchesNot "DL3034" "RUN zypper install -n httpd=2.4.24 && zypper clean"+ onBuildRuleCatchesNot "DL3034" "RUN zypper install --non-interactive httpd=2.4.24 && zypper clean"+ onBuildRuleCatchesNot "DL3034" "RUN zypper install -y httpd=2.4.24 && zypper clean"+ onBuildRuleCatchesNot "DL3034" "RUN zypper install --no-confirm httpd=2.4.24 && zypper clean"
+ test/Hadolint/Rule/DL3035Spec.hs view
@@ -0,0 +1,17 @@+module Hadolint.Rule.DL3035Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3035" $ do+ it "not ok: zypper dist-upgrade" $ do+ ruleCatches "DL3035" "RUN zypper dist-upgrade"+ ruleCatches "DL3035" "RUN zypper dup"+ onBuildRuleCatches "DL3035" "RUN zypper dist-upgrade"+ onBuildRuleCatches "DL3035" "RUN zypper dup"
+ test/Hadolint/Rule/DL3036Spec.hs view
@@ -0,0 +1,20 @@+module Hadolint.Rule.DL3036Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3036 - `zypper clean` missing after zypper use." $ do+ it "not ok without zypper clean" $ do+ ruleCatches "DL3036" "RUN zypper install -y mariadb=10.4"+ onBuildRuleCatches "DL3036" "RUN zypper install -y mariadb=10.4"+ it "ok with zypper clean" $ do+ ruleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper clean"+ ruleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper cc"+ onBuildRuleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper clean"+ onBuildRuleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper cc"
+ test/Hadolint/Rule/DL3037Spec.hs view
@@ -0,0 +1,30 @@+module Hadolint.Rule.DL3037Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3037 - Specify version with `zypper install -y <package>[=]<version>`." $ do+ -- NOTE: In Haskell strings, '\' has to be escaped. And in shell commands, '>'+ -- and '<' have to be escaped. Hence the double escaping.+ it "not ok without zypper version pinning" $ do+ ruleCatches "DL3037" "RUN zypper install -y tomcat && zypper clean"+ onBuildRuleCatches "DL3037" "RUN zypper install -y tomcat && zypper clean"+ it "ok with different variants of zypper version pinning" $ do+ ruleCatchesNot "DL3037" "RUN zypper install -y tomcat=9.0.39 && zypper clean"+ ruleCatchesNot "DL3037" "RUN zypper install -y tomcat\\>=9.0 && zypper clean"+ ruleCatchesNot "DL3037" "RUN zypper install -y tomcat\\>9.0 && zypper clean"+ ruleCatchesNot "DL3037" "RUN zypper install -y tomcat\\<=9.0 && zypper clean"+ ruleCatchesNot "DL3037" "RUN zypper install -y tomcat\\<9.0 && zypper clean"+ ruleCatchesNot "DL3037" "RUN zypper install -y tomcat-9.0.39-1.rpm && zypper clean"+ onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat=9.0.39 && zypper clean"+ onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat\\>=9.0 && zypper clean"+ onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat\\>9.0 && zypper clean"+ onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat\\<=9.0 && zypper clean"+ onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat\\<9.0 && zypper clean"+ onBuildRuleCatchesNot "DL3037" "RUN zypper install -y tomcat-9.0.39-1.rpm && zypper clean"
+ test/Hadolint/Rule/DL3038Spec.hs view
@@ -0,0 +1,20 @@+module Hadolint.Rule.DL3038Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3038 - Use the `-y` switch to avoid manual input `dnf install -y <package>`" $ do+ it "not ok without dnf non-interactive flag" $ do+ ruleCatches "DL3038" "RUN dnf install httpd-2.4.24 && dnf clean all"+ onBuildRuleCatches "DL3038" "RUN dnf install httpd-2.4.24 && dnf clean all"+ it "ok with dnf non-interactive flag" $ do+ ruleCatchesNot "DL3038" "RUN dnf install -y httpd-2.4.24 && dnf clean all"+ ruleCatchesNot "DL3038" "RUN notdnf install httpd"+ onBuildRuleCatchesNot "DL3038" "RUN dnf install -y httpd-2.4.24 && dnf clean all"+ onBuildRuleCatchesNot "DL3038" "RUN notdnf install httpd"
+ test/Hadolint/Rule/DL3040Spec.hs view
@@ -0,0 +1,20 @@+module Hadolint.Rule.DL3040Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3040 - `dnf clean all` missing after dnf command." $ do+ it "no ok without dnf clean all" $ do+ ruleCatches "DL3040" "RUN dnf install -y mariadb-10.4"+ onBuildRuleCatches "DL3040" "RUN dnf install -y mariadb-10.4"+ it "ok with dnf clean all" $ do+ ruleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && dnf clean all"+ ruleCatchesNot "DL3040" "RUN notdnf install mariadb"+ onBuildRuleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && dnf clean all"+ onBuildRuleCatchesNot "DL3040" "RUN notdnf install mariadb"
+ test/Hadolint/Rule/DL3041Spec.hs view
@@ -0,0 +1,28 @@+module Hadolint.Rule.DL3041Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3041 - Specify version with `dnf install -y <package>-<version>`" $ do+ it "not ok without dnf version pinning" $ do+ ruleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"+ onBuildRuleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"+ it "ok with dnf version pinning" $ do+ ruleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"+ ruleCatchesNot "DL3041" "RUN notdnf install tomcat"+ onBuildRuleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"+ onBuildRuleCatchesNot "DL3041" "RUN notdnf install tomcat"+ it "not ok without dnf version pinning - moudles" $ do+ ruleCatches "DL3041" "RUN dnf module install -y tomcat && dnf clean all"+ onBuildRuleCatches "DL3041" "RUN dnf module install -y tomcat && dnf clean all"+ it "ok with dnf version pinning - modules" $ do+ ruleCatchesNot "DL3041" "RUN dnf module install -y tomcat:9 && dnf clean all"+ ruleCatchesNot "DL3041" "RUN notdnf module install tomcat"+ onBuildRuleCatchesNot "DL3041" "RUN dnf module install -y tomcat:9 && dnf clean all"+ onBuildRuleCatchesNot "DL3041" "RUN notdnf module install tomcat"
+ test/Hadolint/Rule/DL3042Spec.hs view
@@ -0,0 +1,88 @@+module Hadolint.Rule.DL3042Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3042 - Avoid cache directory with `pip install --no-cache-dir <package>`." $ do+ it "pip2 --no-cache-dir not used" $ do+ ruleCatches "DL3042" "RUN pip2 install MySQL_python"+ onBuildRuleCatches "DL3042" "RUN pip2 install MySQL_python"+ it "pip3 --no-cache-dir not used" $ do+ ruleCatches "DL3042" "RUN pip3 install MySQL_python"+ onBuildRuleCatches "DL3042" "RUN pip3 install MySQL_python"+ it "pip --no-cache-dir not used" $ do+ ruleCatches "DL3042" "RUN pip install MySQL_python"+ onBuildRuleCatches "DL3042" "RUN pip install MySQL_python"+ it "pip2 --no-cache-dir used" $ do+ ruleCatchesNot "DL3042" "RUN pip2 install MySQL_python --no-cache-dir"+ onBuildRuleCatchesNot "DL3042" "RUN pip2 install MySQL_python --no-cache-dir"+ it "pip3 --no-cache-dir used" $ do+ ruleCatchesNot "DL3042" "RUN pip3 install --no-cache-dir MySQL_python"+ onBuildRuleCatchesNot "DL3042" "RUN pip3 install --no-cache-dir MySQL_python"+ it "pip --no-cache-dir used" $ do+ ruleCatchesNot "DL3042" "RUN pip install MySQL_python --no-cache-dir"+ onBuildRuleCatchesNot "DL3042" "RUN pip install MySQL_python --no-cache-dir"+ it "don't match on pipx" $ do+ ruleCatchesNot "DL3042" "RUN pipx install software"+ onBuildRuleCatchesNot "DL3042" "Run pipx install software"+ it "don't match on pipenv" $ do+ ruleCatchesNot "DL3042" "RUN pipenv install library"+ onBuildRuleCatchesNot "DL3042" "RUN pipenv install library"++ -- ENV won't work with the onBuildRuleCatches[Not] wrapper:+ it "respect ENV PIP_NO_CACHE_DIR with truthy values" $ do+ ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=1\nRUN pip install MySQL_python"+ ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=on\nRUN pip install MySQL_python"+ ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=yes\nRUN pip install MySQL_python"+ ruleCatchesNot "DL3042" "ENV PIP_NO_CACHE_DIR=true\nRUN pip install MySQL_python"+ it "respect RUN PIP_NO_CACHE_DIR=... with truthy values" $ do+ ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=1 pip install MySQL_python"+ ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=on pip install MySQL_python"+ ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=yes pip install MySQL_python"+ ruleCatchesNot "DL3042" "RUN PIP_NO_CACHE_DIR=true pip install MySQL_python"+ it "respect RUN export PIP_NO_CACHE_DIR=... with truthy values" $ do+ ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=1 && pip install MySQL_python"+ ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=on && pip install MySQL_python"+ ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=yes && pip install MySQL_python"+ ruleCatchesNot "DL3042" "RUN export PIP_NO_CACHE_DIR=true && pip install MySQL_python"+ it "respect ENV PIP_NO_CACHE_DIR with falsy values" $ do+ ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=0\nRUN pip install MySQL_python"+ ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=off\nRUN pip install MySQL_python"+ ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=no\nRUN pip install MySQL_python"+ ruleCatches "DL3042" "ENV PIP_NO_CACHE_DIR=false\nRUN pip install MySQL_python"+ it "respect RUN PIP_NO_CACHE_DIR=... with falsy values" $ do+ ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=0 pip install MySQL_python"+ ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=off pip install MySQL_python"+ ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=no pip install MySQL_python"+ ruleCatches "DL3042" "RUN PIP_NO_CACHE_DIR=false pip install MySQL_python"+ it "respect RUN export PIP_NO_CACHE_DIR=... with falsy values" $ do+ ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=0 && pip install MySQL_python"+ ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=off && pip install MySQL_python"+ ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=no && pip install MySQL_python"+ ruleCatches "DL3042" "RUN export PIP_NO_CACHE_DIR=false && pip install MySQL_python"++ it "don't trigger if PIP_NO_CACHE_DIR is inherited" $+ let dockerFile = Text.unlines+ [ "FROM debian:buster as base",+ "ENV PIP_NO_CACHE_DIR=1",+ "FROM base",+ "RUN pip install six"+ ]+ in do+ ruleCatchesNot "DL3042" dockerFile+ it "trigger if PIP_NO_CACHE_DIR is not inherited" $+ let dockerFile = Text.unlines+ [ "FROM debian:buster as base",+ "ENV PIP_NO_CACHE_DIR=1",+ "FROM debian:buster",+ "RUN pip install six"+ ]+ in do+ ruleCatches "DL3042" dockerFile
+ test/Hadolint/Rule/DL3043Spec.hs view
@@ -0,0 +1,45 @@+module Hadolint.Rule.DL3043Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3043 - `ONBUILD`, `FROM` or `MAINTAINER` triggered from within `ONBUILD` instruction." $ do+ it "error when using `ONBUILD` within `ONBUILD`" $+ let dockerFile =+ [ "ONBUILD ONBUILD RUN anything"+ ]+ in ruleCatches "DL3043" $ Text.unlines dockerFile+ it "error when using `FROM` within `ONBUILD`" $+ let dockerFile =+ [ "ONBUILD FROM debian:buster"+ ]+ in ruleCatches "DL3043" $ Text.unlines dockerFile+ it "error when using `MAINTAINER` within `ONBUILD`" $+ let dockerFile =+ [ "ONBUILD MAINTAINER \"BoJack Horseman\""+ ]+ in ruleCatches "DL3043" $ Text.unlines dockerFile+ it "ok with `ADD`" $ ruleCatchesNot "DL3043" "ONBUILD ADD anything anywhere"+ it "ok with `USER`" $ ruleCatchesNot "DL3043" "ONBUILD USER anything"+ it "ok with `LABEL`" $ ruleCatchesNot "DL3043" "ONBUILD LABEL bla=\"blubb\""+ it "ok with `STOPSIGNAL`" $ ruleCatchesNot "DL3043" "ONBUILD STOPSIGNAL anything"+ it "ok with `COPY`" $ ruleCatchesNot "DL3043" "ONBUILD COPY anything anywhere"+ it "ok with `RUN`" $ ruleCatchesNot "DL3043" "ONBUILD RUN anything"+ it "ok with `CMD`" $ ruleCatchesNot "DL3043" "ONBUILD CMD anything"+ it "ok with `SHELL`" $ ruleCatchesNot "DL3043" "ONBUILD SHELL anything"+ it "ok with `WORKDIR`" $ ruleCatchesNot "DL3043" "ONBUILD WORKDIR anything"+ it "ok with `EXPOSE`" $ ruleCatchesNot "DL3043" "ONBUILD EXPOSE 69"+ it "ok with `VOLUME`" $ ruleCatchesNot "DL3043" "ONBUILD VOLUME anything"+ it "ok with `ENTRYPOINT`" $ ruleCatchesNot "DL3043" "ONBUILD ENTRYPOINT anything"+ it "ok with `ENV`" $ ruleCatchesNot "DL3043" "ONBUILD ENV MYVAR=\"bla\""+ it "ok with `ARG`" $ ruleCatchesNot "DL3043" "ONBUILD ARG anything"+ it "ok with `HEALTHCHECK`" $ ruleCatchesNot "DL3043" "ONBUILD HEALTHCHECK NONE"+ it "ok with `FROM` outside of `ONBUILD`" $ ruleCatchesNot "DL3043" "FROM debian:buster"+ it "ok with `MAINTAINER` outside of `ONBUILD`" $ ruleCatchesNot "DL3043" "MAINTAINER \"Some Guy\""
+ test/Hadolint/Rule/DL3044Spec.hs view
@@ -0,0 +1,44 @@+module Hadolint.Rule.DL3044Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3044 - Do not refer to an environment variable within the same `ENV` statement where it is defined." $ do+ it "ok with normal ENV" $+ ruleCatchesNot "DL3044" "ENV BLA=\"blubb\"\nENV BLUBB=\"${BLA}/blubb\""+ it "ok with partial match 1" $+ ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"${FOOBLA}/blubb\""+ it "ok with partial match 2" $+ ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"${BLAFOO}/blubb\""+ it "ok with partial match 3" $+ ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$FOOBLA/blubb\""+ it "ok with partial match 4" $+ ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$BLAFOO/blubb\""+ it "fail with partial match 5" $+ ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$BLA/$BLAFOO/blubb\""+ it "ok with parial match 6" $+ ruleCatchesNot "DL3044" "ENV BLA=\"blubb\" BLUBB=\"BLA/$BLAFOO/BLA\""+ it "ok when previously defined in `ARG`" $+ ruleCatchesNot "DL3044" "ARG BLA\nENV BLA=${BLA}"+ it "ok when previously defined in `ENV`" $+ ruleCatchesNot "DL3044" "ENV BLA blubb\nENV BLA=${BLA}"+ it "ok with referencing a variable on its own right hand side" $+ ruleCatchesNot "DL3044" "ENV PATH=/bla:${PATH}"+ it "ok with referencing a variable on its own right side twice in different `ENV`s" $+ ruleCatchesNot "DL3044" "ENV PATH=/bla:${PATH}\nENV PATH=/blubb:${PATH}"+ it "fail when referencing a variable on its own right side twice within the same `ENV`" $+ ruleCatches "DL3044" "ENV PATH=/bla:${PATH} PATH=/blubb:${PATH}"+ it "fail with selfreferencing with curly braces ENV" $+ ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"${BLA}/blubb\""+ it "fail with selfreferencing without curly braces ENV" $+ ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$BLA/blubb\""+ it "fail with full match 1" $+ ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"$BLA\""+ it "fail with full match 2" $+ ruleCatches "DL3044" "ENV BLA=\"blubb\" BLUBB=\"${BLA}\""
+ test/Hadolint/Rule/DL3045Spec.hs view
@@ -0,0 +1,158 @@+module Hadolint.Rule.DL3045Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3045 - `COPY` without `WORKDIR` set" $ do+ it "ok: `COPY` with absolute destination and no `WORKDIR` set" $ do+ ruleCatchesNot "DL3045" "COPY bla.sh /usr/local/bin/blubb.sh"+ onBuildRuleCatchesNot "DL3045" "COPY bla.sh /usr/local/bin/blubb.sh"++ it "ok: `COPY` with absolute destination and no `WORKDIR` set with quotes" $ do+ ruleCatchesNot "DL3045" "COPY bla.sh \"/usr/local/bin/blubb.s\""+ onBuildRuleCatchesNot "DL3045" "COPY bla.sh \"/usr/local/bin/blubb.sh\""++ it "ok: `COPY` with absolute destination and no `WORKDIR` set - windows" $ do+ ruleCatchesNot "DL3045" "COPY bla.sh c:\\system32\\blubb.sh"+ onBuildRuleCatchesNot "DL3045" "COPY bla.sh d:\\mypath\\blubb.sh"++ it "ok: `COPY` with absolute destination and no `WORKDIR` set - windows with quotes" $ do+ ruleCatchesNot "DL3045" "COPY bla.sh \"c:\\system32\\blubb.sh\""+ onBuildRuleCatchesNot "DL3045" "COPY bla.sh \"d:\\mypath\\blubb.sh\""++ it "ok: `COPY` with absolute destination and no `WORKDIR` set - windows with alternative paths" $ do+ ruleCatchesNot "DL3045" "COPY bla.sh c:/system32/blubb.sh"+ onBuildRuleCatchesNot "DL3045" "COPY bla.sh d:/mypath/blubb.sh"++ it "ok: `COPY` with relative destination and `WORKDIR` set" $ do+ ruleCatchesNot "DL3045" "FROM scratch\nWORKDIR /usr\nCOPY bla.sh blubb.sh"+ onBuildRuleCatchesNot "FROM scratch\nDL3045" "WORKDIR /usr\nCOPY bla.sh blubb.sh"++ it "ok: `COPY` with relative destination and `WORKDIR` set - windows" $ do+ ruleCatchesNot "DL3045" "FROM scratch\nWORKDIR c:\\system32\nCOPY bla.sh blubb.sh"+ onBuildRuleCatchesNot "DL3045" "FROM scratch\nWORKDIR c:\\system32\nCOPY bla.sh blubb.sh"++ it "ok: `COPY` with destination being an environment variable 1" $ do+ ruleCatchesNot "DL3045" "COPY src.sh ${SRC_BASE_ENV}"+ onBuildRuleCatchesNot "DL3045" "COPY src.sh ${SRC_BASE_ENV}"++ it "ok: `COPY` with destination being an environment variable 2" $ do+ ruleCatchesNot "DL3045" "COPY src.sh $SRC_BASE_ENV"+ onBuildRuleCatchesNot "DL3045" "COPY src.sh $SRC_BASE_ENV"++ it "ok: `COPY` with destination being an environment variable 3" $ do+ ruleCatchesNot "DL3045" "COPY src.sh \"${SRC_BASE_ENV}\""+ onBuildRuleCatchesNot "DL3045" "COPY src.sh \"${SRC_BASE_ENV}\""++ it "ok: `COPY` with destination being an environment variable 4" $ do+ ruleCatchesNot "DL3045" "COPY src.sh \"$SRC_BASE_ENV\""+ onBuildRuleCatchesNot "DL3045" "COPY src.sh \"$SRC_BASE_ENV\""++ it "not ok: `COPY` with relative destination and no `WORKDIR` set" $ do+ ruleCatches "DL3045" "COPY bla.sh blubb.sh"+ onBuildRuleCatches "DL3045" "COPY bla.sh blubb.sh"++ it "not ok: `COPY` with relative destination and no `WORKDIR` set with quotes" $ do+ ruleCatches "DL3045" "COPY bla.sh \"blubb.sh\""+ onBuildRuleCatches "DL3045" "COPY bla.sh \"blubb.sh\""++ it "not ok: `COPY` to relative destination if `WORKDIR` is set in a previous stage but not inherited" $+ let dockerFile =+ Text.unlines+ [ "FROM debian:buster as stage1",+ "WORKDIR /usr",+ "FROM debian:buster",+ "COPY foo bar"+ ]+ in do+ ruleCatches "DL3045" dockerFile+ onBuildRuleCatches "DL3045" dockerFile++ it "not ok: `COPY` to relative destination if `WORKDIR` is set in a previous stage but not inherited - windows" $+ let dockerFile =+ Text.unlines+ [ "FROM microsoft/windowsservercore as stage1",+ "WORKDIR c:\\system32",+ "FROM microsoft/windowsservercore",+ "COPY foo bar"+ ]+ in do+ ruleCatches "DL3045" dockerFile+ onBuildRuleCatches "DL3045" dockerFile++ it "ok: `COPY` to relative destination if `WORKDIR` has been set in base image" $+ let dockerFile =+ Text.unlines+ [ "FROM debian:buster as base",+ "WORKDIR /usr",+ "FROM debian:buster as stage-inbetween",+ "RUN foo",+ "FROM base",+ "COPY foo bar"+ ]+ in do+ ruleCatchesNot "DL3045" dockerFile+ onBuildRuleCatchesNot "DL3045" dockerFile++ it "ok: `COPY` to relative destination if `WORKDIR` has been set in base image - windows" $+ let dockerFile =+ Text.unlines+ [ "FROM microsoft/windowsservercore as base",+ "WORKDIR c:\\system32",+ "FROM microsoft/windowsservercore as stage-inbetween",+ "RUN foo",+ "FROM base",+ "COPY foo bar"+ ]+ in do+ ruleCatchesNot "DL3045" dockerFile+ onBuildRuleCatchesNot "DL3045" dockerFile++ it "ok: `COPY` to relative destination if `WORKDIR` has been set in previous stage, deep case" $+ let dockerFile =+ Text.unlines+ [ "FROM debian:buster as base1",+ "WORKDIR /usr",+ "FROM base1 as base2",+ "RUN foo",+ "FROM base2",+ "COPY foo bar"+ ]+ in do+ ruleCatchesNot "DL3045" dockerFile+ onBuildRuleCatchesNot "DL3045" dockerFile++ it "ok: `COPY` to relative destination if `WORKDIR` has been set in previous stage, deep case - windows" $+ let dockerFile =+ Text.unlines+ [ "FROM microsoft/windowsservercore as base1",+ "WORKDIR c:\\system32",+ "FROM base1 as base2",+ "RUN foo",+ "FROM base2",+ "COPY foo bar"+ ]+ in do+ ruleCatchesNot "DL3045" dockerFile+ onBuildRuleCatchesNot "DL3045" dockerFile++ it "ok: `COPY` to relative destination if `WORKDIR` has been set, both within an `ONBUILD` context" $+ let dockerFile =+ Text.unlines+ [ "FROM debian:buster",+ "ONBUILD WORKDIR /usr/local/lib",+ "ONBUILD COPY foo bar"+ ]+ in do+ ruleCatchesNot "DL3045" dockerFile+ onBuildRuleCatchesNot "DL3045" dockerFile++ it "regression: don't crash with single character paths" $ do+ ruleCatches "DL3045" "COPY a b"+ onBuildRuleCatches "DL3045" "COPY a b"
+ test/Hadolint/Rule/DL3046Spec.hs view
@@ -0,0 +1,17 @@+module Hadolint.Rule.DL3046Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3046 - `useradd` without flag `-l` and high UID will result in excessively large Image." $ do+ it "ok with `useradd` alone" $ ruleCatchesNot "DL3046" "RUN useradd luser"+ it "ok with `useradd` short uid" $ ruleCatchesNot "DL3046" "RUN useradd -u 12345 luser"+ it "ok with `useradd` long uid and flag `-l`" $ ruleCatchesNot "DL3046" "RUN useradd -l -u 123456 luser"+ it "ok with `useradd` and just flag `-l`" $ ruleCatchesNot "DL3046" "RUN useradd -l luser"+ it "warn when `useradd` and long uid without flag `-l`" $ ruleCatches "DL3046" "RUN useradd -u 123456 luser"
+ test/Hadolint/Rule/DL3047Spec.hs view
@@ -0,0 +1,73 @@+module Hadolint.Rule.DL3047Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3047 - `wget` without flag `--progress` will result in excessively bloated build logs when downloading larger files." $ do+ it "warns when using wget without --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget my.xyz"+ ]+ in ruleCatches "DL3047" $ Text.unlines dockerFile+ it "does not warn when running with --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget --progress=dot:giga my.xyz"+ ]+ in ruleCatchesNot "DL3047" $ Text.unlines dockerFile+ it "does not warn when running with -q (quiet short option) and without --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget -q my.xyz"+ ]+ in ruleCatchesNot "DL3047" $ Text.unlines dockerFile+ it "does not warn when running with --quiet (quiet long option) and without --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget --quiet my.xyz"+ ]+ in ruleCatchesNot "DL3047" $ Text.unlines dockerFile+ it "does not warn when running with -nv (no-verbose short option) and without --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget -nv my.xyz"+ ]+ in ruleCatchesNot "DL3047" $ Text.unlines dockerFile+ it "does not warn when running with --no-verbose (no-verbose long option) and without --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget --no-verbose my.xyz"+ ]+ in ruleCatchesNot "DL3047" $ Text.unlines dockerFile+ it "does not warn when running with --output-file (output-file long option) and without --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget --output-file=/tmp/wget.log my.xyz"+ ]+ in ruleCatchesNot "DL3047" $ Text.unlines dockerFile+ it "does not warn when running with -o (output-file long option) and without --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget -o /tmp/wget.log my.xyz"+ ]+ in ruleCatchesNot "DL3047" $ Text.unlines dockerFile+ it "does not warn when running with --append-output (append-output long option) and without --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget --append-output=/tmp/wget.log my.xyz"+ ]+ in ruleCatchesNot "DL3047" $ Text.unlines dockerFile+ it "does not warn when running with -a (append-output long option) and without --progress option" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget -a /tmp/wget.log my.xyz"+ ]+ in ruleCatchesNot "DL3047" $ Text.unlines dockerFile
+ test/Hadolint/Rule/DL3048Spec.hs view
@@ -0,0 +1,39 @@+module Hadolint.Rule.DL3048Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3048 - Invalid Label Key Rule" $ do+ it "not ok with reserved namespace" $ do+ ruleCatches "DL3048" "LABEL com.docker.label=\"foo\""+ ruleCatches "DL3048" "LABEL io.docker.label=\"foo\""+ ruleCatches "DL3048" "LABEL org.dockerproject.label=\"foo\""+ onBuildRuleCatches "DL3048" "LABEL com.docker.label=\"foo\""+ onBuildRuleCatches "DL3048" "LABEL io.docker.label=\"foo\""+ onBuildRuleCatches "DL3048" "LABEL org.dockerproject.label=\"foo\""+ it "not ok with invalid character" $ do+ ruleCatches "DL3048" "LABEL invalid$character=\"foo\""+ onBuildRuleCatches "DL3048" "LABEL invalid$character=\"foo\""+ it "not ok with invalid start and end characters" $ do+ ruleCatches "DL3048" "LABEL .invalid =\"foo\""+ ruleCatches "DL3048" "LABEL -invalid =\"foo\""+ ruleCatches "DL3048" "LABEL 1invalid =\"foo\""+ onBuildRuleCatches "DL3048" "LABEL .invalid=\"foo\""+ onBuildRuleCatches "DL3048" "LABEL -invalid=\"foo\""+ onBuildRuleCatches "DL3048" "LABEL 1invalid=\"foo\""+ it "not ok with consecutive dividers" $ do+ ruleCatches "DL3048" "LABEL invalid..character=\"foo\""+ ruleCatches "DL3048" "LABEL invalid--character=\"foo\""+ onBuildRuleCatches "DL3048" "LABEL invalid..character=\"foo\""+ onBuildRuleCatches "DL3048" "LABEL invalid--character=\"foo\""+ it "ok with valid labels" $ do+ ruleCatchesNot "DL3048" "LABEL org.valid-key.label3=\"foo\""+ ruleCatchesNot "DL3048" "LABEL validlabel=\"foo\""+ onBuildRuleCatchesNot "DL3048" "LABEL org.valid-key.label3=\"foo\""+ onBuildRuleCatchesNot "DL3048" "LABEL validlabel=\"foo\""
+ test/Hadolint/Rule/DL3049Spec.hs view
@@ -0,0 +1,82 @@+module Hadolint.Rule.DL3049Spec (spec) where++import Data.Default+import Hadolint (Configuration (..))+import Data.Text as Text+import qualified Data.Map as Map+import qualified Hadolint.Rule as Rule+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def { labelSchema = Map.fromList [("foo", Rule.RawText)] }++ describe "DL3049 - Missing label rule spec" $ do+ -- single stage spec+ it "not ok: single stage, no label" $ do+ ruleCatches "DL3049" "FROM baseimage"+ onBuildRuleCatches "DL3049" "FROM baseimage"+ it "not ok: single stage, wrong label" $ do+ ruleCatches "DL3049" "FROM baseimage\nLABEL bar=\"baz\""+ onBuildRuleCatches "DL3049" "FROM baseimage\nLABEL bar=\"baz\""+ it "ok: single stage, label present" $ do+ ruleCatchesNot "DL3049" "FROM baseimage\nLABEL foo=\"bar\""+ onBuildRuleCatchesNot "DL3049" "FROM baseimage\nLABEL foo=\"bar\""+ -- multi stage spec+ it "warn twice: two stages, no labels" $+ let dockerFile =+ [ "FROM stage1",+ "FROM stage2"+ ]+ in assertChecks+ (Text.unlines dockerFile)+ (failsWith 2 "DL3049")+ it "warn twice: two stages, wrong labels only" $+ let dockerFile =+ [ "FROM stage1",+ "LABEL bar=\"baz\"",+ "FROM stage2",+ "LABEL buzz=\"fuzz\""+ ]+ in assertChecks+ (Text.unlines dockerFile)+ (failsWith 2 "DL3049")+ it "warn once: two stages, label present in second only" $+ let dockerFile =+ [ "FROM baseimage",+ "FROM newimage",+ "LABEL foo=\"bar\""+ ]+ in assertChecks+ (Text.unlines dockerFile)+ (failsWith 1 "DL3049")+ it "warn once: two stages, no inheritance, wrong label in one" $+ let dockerFile =+ [ "FROM baseimage",+ "LABEL baz=\"bar\"",+ "FROM newimage",+ "LABEL foo=\"bar\""+ ]+ in assertChecks+ (Text.unlines dockerFile)+ (failsWith 1 "DL3049")+ it "warn once: two stages, inheritance, label only defined in second stage" $+ let dockerFile =+ [ "FROM baseimage as base",+ "FROM base",+ "LABEL foo=\"bar\""+ ]+ in assertChecks+ (Text.unlines dockerFile)+ (failsWith 1 "DL3049")+ it "don't warn: two stages, inheritance" $+ let dockerFile =+ [ "FROM baseimage as base",+ "LABEL foo=\"bar\"",+ "FROM base"+ ]+ in assertChecks+ (Text.unlines dockerFile)+ (failsWith 0 "DL3049")
+ test/Hadolint/Rule/DL3050Spec.hs view
@@ -0,0 +1,31 @@+module Hadolint.Rule.DL3050Spec (spec) where++import Data.Default+import Hadolint (Configuration (..))+import qualified Data.Map as Map+import qualified Hadolint.Rule as Rule+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config =+ def+ { labelSchema = Map.fromList [("required", Rule.RawText)],+ strictLabels = True+ }++ describe "DL3050 - Superfluous label(s) present." $ do+ it "ok with no label" $ do+ ruleCatchesNot "DL3050" ""+ onBuildRuleCatchesNot "DL3050" ""+ it "ok with required label" $ do+ ruleCatchesNot "DL3050" "LABEL required=\"foo\""+ onBuildRuleCatchesNot "DL3050" "LABEL required=\"bar\""+ it "not ok with just other label" $ do+ ruleCatches "DL3050" "LABEL other=\"bar\""+ onBuildRuleCatches "DL3050" "LABEL other=\"bar\""+ it "not ok with other label and required label" $ do+ ruleCatches "DL3050" "LABEL required=\"foo\" other=\"bar\""+ onBuildRuleCatches "DL3050" "LABEL required=\"foo\" other=\"bar\""
+ test/Hadolint/Rule/DL3051Spec.hs view
@@ -0,0 +1,24 @@+module Hadolint.Rule.DL3051Spec (spec) where++import Data.Default+import Hadolint (Configuration (..))+import qualified Data.Map as Map+import qualified Hadolint.Rule as Rule+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config =+ def { labelSchema = Map.fromList [("emptylabel", Rule.RawText)] }+ describe "DL3051 - Label `<label>` is empty." $ do+ it "not ok with label empty" $ do+ ruleCatches "DL3051" "LABEL emptylabel=\"\""+ onBuildRuleCatches "DL3051" "LABEL emptylabel=\"\""+ it "ok with label not empty" $ do+ ruleCatchesNot "DL3051" "LABEL emptylabel=\"foo\""+ onBuildRuleCatchesNot "DL3051" "LABEL emptylabel=\"bar\""+ it "ok with other label empty" $ do+ ruleCatchesNot "DL3051" "LABEL other=\"\""+ onBuildRuleCatchesNot "DL3051" "LABEL other=\"\""
+ test/Hadolint/Rule/DL3052Spec.hs view
@@ -0,0 +1,23 @@+module Hadolint.Rule.DL3052Spec (spec) where++import Data.Default+import Hadolint (Configuration (..), LabelType (..))+import qualified Data.Map as Map+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def { labelSchema = Map.fromList [("urllabel", Url)] }++ describe "DL3052 - Label `<label>` is not a valid URL." $ do+ it "not ok with label not containing URL" $ do+ ruleCatches "DL3052" "LABEL urllabel=\"not-url\""+ onBuildRuleCatches "DL3052" "LABEL urllabel=\"not-url\""+ it "ok with label containing URL" $ do+ ruleCatchesNot "DL3052" "LABEL urllabel=\"http://example.com\""+ onBuildRuleCatchesNot "DL3052" "LABEL urllabel=\"http://example.com\""+ it "ok with other label not containing URL" $ do+ ruleCatchesNot "DL3052" "LABEL other=\"foo\""+ onBuildRuleCatchesNot "DL3052" "LABEL other=\"bar\""
+ test/Hadolint/Rule/DL3053Spec.hs view
@@ -0,0 +1,24 @@+module Hadolint.Rule.DL3053Spec (spec) where++import Data.Default+import Hadolint (Configuration (..))+import qualified Data.Map as Map+import qualified Hadolint.Rule as Rule+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def { labelSchema = Map.fromList [("datelabel", Rule.Rfc3339)] }++ describe "DL3053 - Label `<label>` is not a valid time format - must be conform to RFC3339." $ do+ it "not ok with label not containing RFC3339 date" $ do+ ruleCatches "DL3053" "LABEL datelabel=\"not-date\""+ onBuildRuleCatches "DL3053" "LABEL datelabel=\"not-date\""+ it "ok with label containing RFC3339 date" $ do+ ruleCatchesNot "DL3053" "LABEL datelabel=\"2021-03-10T10:26:33.564595127+01:00\""+ onBuildRuleCatchesNot "DL3053" "LABEL datelabel=\"2021-03-10T10:26:33.564595127+01:00\""+ it "ok with other label not containing RFC3339 date" $ do+ ruleCatchesNot "DL3053" "LABEL other=\"doo\""+ onBuildRuleCatchesNot "DL3053" "LABEL other=\"bar\""
+ test/Hadolint/Rule/DL3054Spec.hs view
@@ -0,0 +1,24 @@+module Hadolint.Rule.DL3054Spec (spec) where++import Data.Default+import Hadolint (Configuration (..))+import qualified Data.Map as Map+import qualified Hadolint.Rule as Rule+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def { labelSchema = Map.fromList [("spdxlabel", Rule.Spdx)] }++ describe "DL3054 - Label `<label>` is not a valid SPDX license identifier." $ do+ it "not ok with label not containing SPDX identifier" $ do+ ruleCatches "DL3054" "LABEL spdxlabel=\"not-spdx\""+ onBuildRuleCatches "DL3054" "LABEL spdxlabel=\"not-spdx\""+ it "ok with label containing SPDX identifier" $ do+ ruleCatchesNot "DL3054" "LABEL spdxlabel=\"BSD-3-Clause\""+ onBuildRuleCatchesNot "DL3054" "LABEL spdxlabel=\"MIT\""+ it "ok with other label not containing SPDX identifier" $ do+ ruleCatchesNot "DL3054" "LABEL other=\"fooo\""+ onBuildRuleCatchesNot "DL3054" "LABEL other=\"bar\""
+ test/Hadolint/Rule/DL3055Spec.hs view
@@ -0,0 +1,27 @@+module Hadolint.Rule.DL3055Spec (spec) where++import Data.Default+import Hadolint (Configuration (..))+import qualified Data.Map as Map+import qualified Hadolint.Rule as Rule+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def { labelSchema = Map.fromList [("githash", Rule.GitHash)] }++ describe "DL3055 - Label `<label>` is not a valid git hash." $ do+ it "not ok with label not containing git hash" $ do+ ruleCatches "DL3055" "LABEL githash=\"not-git-hash\""+ onBuildRuleCatches "DL3055" "LABEL githash=\"not-git-hash\""+ it "ok with label containing short git hash" $ do+ ruleCatchesNot "DL3055" "LABEL githash=\"2dbfae9\""+ onBuildRuleCatchesNot "DL3055" "LABEL githash=\"2dbfae9\""+ it "ok with label containing long git hash" $ do+ ruleCatchesNot "DL3055" "LABEL githash=\"43c572f1272b6b3171dd1db9e41b7027128ce080\""+ onBuildRuleCatchesNot "DL3055" "LABEL githash=\"43c572f1272b6b3171dd1db9e41b7027128ce080\""+ it "ok with other label not containing git hash" $ do+ ruleCatchesNot "DL3055" "LABEL other=\"foo\""+ onBuildRuleCatchesNot "DL3055" "LABEL other=\"bar\""
+ test/Hadolint/Rule/DL3056Spec.hs view
@@ -0,0 +1,24 @@+module Hadolint.Rule.DL3056Spec (spec) where++import Data.Default+import Hadolint (Configuration (..))+import qualified Data.Map as Map+import qualified Hadolint.Rule as Rule+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def { labelSchema = Map.fromList [("semver", Rule.SemVer)] }++ describe "DL3056 - Label `<label>` does not conform to semantic versioning." $ do+ it "not ok with label not containing semantic version" $ do+ ruleCatches "DL3056" "LABEL semver=\"not-sem-ver\""+ onBuildRuleCatches "DL3056" "LABEL semver=\"not-sem-ver\""+ it "ok with label containing semantic version" $ do+ ruleCatchesNot "DL3056" "LABEL semver=\"1.0.0\""+ onBuildRuleCatchesNot "DL3056" "LABEL semver=\"2.0.1-rc1\""+ it "ok with other label not containing semantic version" $ do+ ruleCatchesNot "DL3056" "LABEL other=\"foo\""+ onBuildRuleCatchesNot "DL3056" "LABEL other=\"bar\""
+ test/Hadolint/Rule/DL3057Spec.hs view
@@ -0,0 +1,20 @@+module Hadolint.Rule.DL3057Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3057 - `HEALTHCHECK instruction missing" $ do+ it "warn with no HEALTHCHECK instructions" $+ ruleCatches "DL3057" "FROM scratch"+ it "ok with one HEALTHCHECK instruction" $+ ruleCatchesNot "DL3057" "FROM scratch\nHEALTHCHECK CMD /bin/bla"+ it "ok with inheriting HEALTHCHECK instruction" $+ ruleCatchesNot "DL3057" "FROM scratch AS base\nHEALTHCHECK CMD /bin/bla\nFROM base"+ it "warn when not inheriting with no HEALTHCHECK instruction" $+ ruleCatches "DL3057" "FROM scratch AS base\nHEALTHCHECK CMD /bin/bla\nFROM scratch"
+ test/Hadolint/Rule/DL3058Spec.hs view
@@ -0,0 +1,24 @@+module Hadolint.Rule.DL3058Spec (spec) where++import Data.Default+import Hadolint (Configuration (..))+import qualified Data.Map as Map+import qualified Hadolint.Rule as Rule+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def { labelSchema = Map.fromList [("maintainer", Rule.Email)] }++ describe "DL3058 - Label `<label>` is not a valid email format - must be conform to RFC5322." $ do+ it "not ok with label not containing valid email" $ do+ ruleCatches "DL3058" "LABEL maintainer=\"not-email\""+ onBuildRuleCatches "DL3058" "LABEL maintainer=\"not-email\""+ it "ok with label containing valid email" $ do+ ruleCatchesNot "DL3058" "LABEL maintainer=\"abcd@google.com\""+ onBuildRuleCatchesNot "DL3058" "LABEL maintainer=\"abcd@google.com\""+ it "ok with other label not containing valid email" $ do+ ruleCatchesNot "DL3058" "LABEL other=\"doo\""+ onBuildRuleCatchesNot "DL3058" "LABEL other=\"bar\""
+ test/Hadolint/Rule/DL3059Spec.hs view
@@ -0,0 +1,73 @@+module Hadolint.Rule.DL3059Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3059 - Multiple consecutive `RUN` instructions" $ do+ it "ok with no `RUN` at all" $ do+ ruleCatchesNot "DL3059" "FROM debian:10"+ it "ok with one `RUN`" $ do+ ruleCatchesNot "DL3059" "RUN /foo.sh"+ it "ok with two not consecutive `RUN`" $ do+ ruleCatchesNot "DL3059" "RUN /foo.sh\nWORKDIR /\nRUN /bar.sh"+ it "not ok with two consecutive `RUN`s" $ do+ ruleCatches "DL3059" "RUN /foo.sh\nRUN /bar.sh"+ it "not ok with two `RUN`s separated by a comment" $ do+ ruleCatches "DL3059" "RUN /foo.sh\n# a comment\nRUN /bar.sh"+ it "not ok with two `RUN`s separated by two comment" $ do+ ruleCatches "DL3059" "RUN /foo.sh\n# a comment\n# another comment\nRUN\+ \ /bar.sh"+ it "ok with one `RUN` after a comment" $ do+ ruleCatchesNot "DL3059" "# a comment\nRUN /foo.sh"+ it "ok with two consecutive `RUN`s when flags are different 1" $ do+ ruleCatchesNot "DL3059" "RUN --mount=type=secret,id=foo /foo.sh\nRUN\+ \ /bar.sh"+ it "ok with two consecutive `RUN`s when flags are different 2" $ do+ let dfile = [ "RUN --mount=type=secret,id=foo /foo.sh",+ "RUN --mount=type=secret,id=bar /bar.sh"+ ]+ in ruleCatchesNot "DL3059" $ Text.unlines dfile+ it "not ok with two consecutive `RUN`s when flags are equal" $ do+ let dfile = [ "RUN --mount=type=secret,id=foo /foo.sh",+ "RUN --mount=type=secret,id=foo /bar.sh"+ ]+ in ruleCatches "DL3059" $ Text.unlines dfile++ it "ok with two consecutive `RUN`s when commands are chained 1" $ do+ let dfile = [ "RUN foo && bar",+ "RUN foobar"+ ]+ in ruleCatchesNot "DL3059" $ Text.unlines dfile+ it "ok with two consecutive `RUN`s when commands are chained 2" $ do+ let dfile = [ "RUN foo; bar",+ "RUN foobar"+ ]+ in ruleCatchesNot "DL3059" $ Text.unlines dfile+ it "ok with two consecutive `RUN`s when commands are chained 3" $ do+ let dfile = [ "RUN foobar",+ "RUN foo && bar"+ ]+ in ruleCatchesNot "DL3059" $ Text.unlines dfile+ it "ok with two consecutive `RUN`s when commands are chained 4" $ do+ let dfile = [ "RUN foobar",+ "RUN foo; bar"+ ]+ in ruleCatchesNot "DL3059" $ Text.unlines dfile+ it "ok with two consecutive `RUN`s when commands are chained 5" $ do+ let dfile = [ "RUN foo && bar",+ "RUN foo; bar"+ ]+ in ruleCatchesNot "DL3059" $ Text.unlines dfile+ it "not ok when more than one `RUN` has just one command in a row" $ do+ let dfile = [ "RUN foo && bar",+ "RUN foo",+ "RUN bar"+ ]+ in ruleCatches "DL3059" $ Text.unlines dfile
+ test/Hadolint/Rule/DL3060Spec.hs view
@@ -0,0 +1,70 @@+module Hadolint.Rule.DL3060Spec (spec) where++import Data.Default+import qualified Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL3060 - `yarn cache clean` missing after `yarn install`" $ do+ it "ok with non-yarn commands" $ do+ ruleCatchesNot "DL3060" "RUN foo"+ onBuildRuleCatchesNot "DL3060" "RUN foo"+ it "not ok with no cache clean" $ do+ ruleCatches "DL3060" "RUN yarn install foo"+ onBuildRuleCatches "DL3060" "RUN yarn install foo"+ it "ok with cache clean" $ do+ ruleCatchesNot "DL3060" "RUN yarn install bar && yarn cache clean"+ onBuildRuleCatchesNot "DL3060" "RUN yarn install bar && yarn cache clean"++ it "not ok when yarn install is in last stage w/o yarn clean" $+ let dockerFile =+ Text.unlines+ [ "FROM node:lts-alpine as foo",+ "RUN hey!",+ "FROM scratch",+ "RUN yarn install"+ ]+ in do+ ruleCatches "DL3060" dockerFile+ onBuildRuleCatches "DL3060" dockerFile++ it "not ok when inheriting from stage with yarn install w/o yarn clean" $+ let dockerFile =+ Text.unlines+ [ "FROM node:lts-alpine as foo",+ "RUN yarn install",+ "FROM foo",+ "RUN hey!"+ ]+ in do+ ruleCatches "DL3060" dockerFile+ onBuildRuleCatches "DL3060" dockerFile++ it "ok when inheriting from stage with yarn cache clear" $+ let dockerFile =+ Text.unlines+ [ "FROM node:lts-alpine as foo",+ "RUN yarn install && yarn cache clean",+ "FROM foo",+ "RUN hey!"+ ]+ in do+ ruleCatchesNot "DL3060" dockerFile+ onBuildRuleCatchesNot "DL3060" dockerFile++ it "ok when omitting yarn cache clean in stage that is not reused later" $+ let dockerFile =+ Text.unlines+ [ "FROM node:lts-alpine as foo",+ "RUN yarn install foo",+ "FROM scratch",+ "RUN hey!"+ ]+ in do+ ruleCatchesNot "DL3060" dockerFile+ onBuildRuleCatches "DL3060" dockerFile
+ test/Hadolint/Rule/DL4000Spec.hs view
@@ -0,0 +1,16 @@+module Hadolint.Rule.DL4000Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def+ describe "DL4000 - MAINTAINER is deprecated." $ do+ it "has deprecated maintainer" $+ ruleCatches "DL4000" "FROM busybox\nMAINTAINER hudu@mail.com"+ it "has maintainer" $ ruleCatches "DL4000" "FROM debian\nMAINTAINER Lukas"+ it "has maintainer first" $ ruleCatches "DL4000" "MAINTAINER Lukas\nFROM DEBIAN"+ it "has no maintainer" $ ruleCatchesNot "DL4000" "FROM debian"
+ test/Hadolint/Rule/DL4001Spec.hs view
@@ -0,0 +1,71 @@+module Hadolint.Rule.DL4001Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL4001 - Either use Wget or Curl but not both." $ do+ it "warns when using both wget and curl" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget my.xyz",+ "RUN curl localhost"+ ]+ in ruleCatches "DL4001" $ Text.unlines dockerFile+ it "warns when using both wget and curl in same instruction" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget my.xyz && curl localhost"+ ]+ in ruleCatches "DL4001" $ Text.unlines dockerFile+ it "does not warn when using only wget" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget my.xyz"+ ]+ in ruleCatchesNot "DL4001" $ Text.unlines dockerFile+ it "does not warn when using both curl and wget in different stages" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget my.xyz",+ "FROM scratch",+ "RUN curl localhost"+ ]+ in ruleCatchesNot "DL4001" $ Text.unlines dockerFile+ it "does not warns when using both, on a single stage" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget my.xyz",+ "RUN curl localhost",+ "FROM scratch",+ "RUN curl localhost"+ ]+ in ruleCatches "DL4001" $ Text.unlines dockerFile+ it "only warns on the relevant RUN instruction" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget my.xyz",+ "RUN curl my.xyz",+ "RUN echo hello"+ ]+ in assertChecks+ (Text.unlines dockerFile)+ (failsWith 1 "DL4001")++ it "only warns on many relevant RUN instructions" $+ let dockerFile =+ [ "FROM node as foo",+ "RUN wget my.xyz",+ "RUN curl my.xyz",+ "RUN echo hello",+ "RUN wget foo.com"+ ]+ in assertChecks+ (Text.unlines dockerFile)+ (failsWith 2 "DL4001")
+ test/Hadolint/Rule/DL4003Spec.hs view
@@ -0,0 +1,44 @@+module Hadolint.Rule.DL4003Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL4003" $ do+ it "many cmds" $+ let dockerFile =+ [ "FROM debian",+ "CMD bash",+ "RUN foo",+ "CMD another"+ ]+ in ruleCatches "DL4003" $ Text.unlines dockerFile++ it "single cmds, different stages" $+ let dockerFile =+ [ "FROM debian as distro1",+ "CMD bash",+ "RUN foo",+ "FROM debian as distro2",+ "CMD another"+ ]+ in ruleCatchesNot "DL4003" $ Text.unlines dockerFile++ it "many cmds, different stages" $+ let dockerFile =+ [ "FROM debian as distro1",+ "CMD bash",+ "RUN foo",+ "CMD another",+ "FROM debian as distro2",+ "CMD another"+ ]+ in ruleCatches "DL4003" $ Text.unlines dockerFile++ it "single cmd" $ ruleCatchesNot "DL4003" "CMD /bin/true"
+ test/Hadolint/Rule/DL4004Spec.hs view
@@ -0,0 +1,47 @@+module Hadolint.Rule.DL4004Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL4004" $ do+ it "no cmd" $ ruleCatchesNot "DL4004" "FROM busybox"++ it "many entrypoints" $+ let dockerFile =+ [ "FROM debian",+ "ENTRYPOINT bash",+ "RUN foo",+ "ENTRYPOINT another"+ ]+ in ruleCatches "DL4004" $ Text.unlines dockerFile++ it "single entrypoint, different stages" $+ let dockerFile =+ [ "FROM debian as distro1",+ "ENTRYPOINT bash",+ "RUN foo",+ "FROM debian as distro2",+ "ENTRYPOINT another"+ ]+ in ruleCatchesNot "DL4004" $ Text.unlines dockerFile++ it "many entrypoints, different stages" $+ let dockerFile =+ [ "FROM debian as distro1",+ "ENTRYPOINT bash",+ "RUN foo",+ "ENTRYPOINT another",+ "FROM debian as distro2",+ "ENTRYPOINT another"+ ]+ in ruleCatches "DL4004" $ Text.unlines dockerFile++ it "single entry" $ ruleCatchesNot "DL4004" "ENTRYPOINT /bin/true"+ it "no entry" $ ruleCatchesNot "DL4004" "FROM busybox"
+ test/Hadolint/Rule/DL4005Spec.hs view
@@ -0,0 +1,21 @@+module Hadolint.Rule.DL4005Spec (spec) where++import Data.Default+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL4005 - Use `SHELL` to change the default shell." $ do+ it "RUN ln" $ do+ ruleCatches "DL4005" "RUN ln -sfv /bin/bash /bin/sh"+ onBuildRuleCatches "DL4005" "RUN ln -sfv /bin/bash /bin/sh"+ it "RUN ln with unrelated symlinks" $ do+ ruleCatchesNot "DL4005" "RUN ln -sf /bin/true /sbin/initctl"+ onBuildRuleCatchesNot "DL4005" "RUN ln -sf /bin/true /sbin/initctl"+ it "RUN ln with multiple acceptable commands" $ do+ ruleCatchesNot "DL4005" "RUN ln -s foo bar && unrelated && something_with /bin/sh"+ onBuildRuleCatchesNot "DL4005" "RUN ln -s foo bar && unrelated && something_with /bin/sh"
+ test/Hadolint/Rule/DL4006Spec.hs view
@@ -0,0 +1,106 @@+module Hadolint.Rule.DL4006Spec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "DL4006 - Set the `SHELL` option `-o pipefail` before RUN with a pipe in it" $ do+ it "warn on missing pipefail" $+ let dockerFile =+ [ "FROM scratch",+ "RUN wget -O - https://some.site | wc -l > /number"+ ]+ in ruleCatches "DL4006" $ Text.unlines dockerFile+ it "don't warn on commands with no pipes" $+ let dockerFile =+ [ "FROM scratch as build",+ "RUN wget -O - https://some.site && wc -l file > /number"+ ]+ in ruleCatchesNot "DL4006" $ Text.unlines dockerFile+ it "don't warn on commands with pipes and the pipefail option" $+ let dockerFile =+ [ "FROM scratch as build",+ "SHELL [\"/bin/bash\", \"-eo\", \"pipefail\", \"-c\"]",+ "RUN wget -O - https://some.site | wc -l file > /number"+ ]+ in ruleCatchesNot "DL4006" $ Text.unlines dockerFile+ it "don't warn on commands with pipes and the pipefail option 2" $+ let dockerFile =+ [ "FROM scratch as build",+ "SHELL [\"/bin/bash\", \"-e\", \"-o\", \"pipefail\", \"-c\"]",+ "RUN wget -O - https://some.site | wc -l file > /number"+ ]+ in ruleCatchesNot "DL4006" $ Text.unlines dockerFile+ it "don't warn on commands with pipes and the pipefail option 3" $+ let dockerFile =+ [ "FROM scratch as build",+ "SHELL [\"/bin/bash\", \"-o\", \"errexit\", \"-o\", \"pipefail\", \"-c\"]",+ "RUN wget -O - https://some.site | wc -l file > /number"+ ]+ in ruleCatchesNot "DL4006" $ Text.unlines dockerFile+ it "don't warn on commands with pipes and the pipefail zsh" $+ let dockerFile =+ [ "FROM scratch as build",+ "SHELL [\"/bin/zsh\", \"-o\", \"pipefail\", \"-c\"]",+ "RUN wget -O - https://some.site | wc -l file > /number"+ ]+ in ruleCatchesNot "DL4006" $ Text.unlines dockerFile+ it "don't warn on powershell" $+ let dockerFile =+ [ "FROM scratch as build",+ "SHELL [\"pwsh\", \"-c\"]",+ "RUN Get-Variable PSVersionTable | Select-Object -ExpandProperty Value"+ ]+ in ruleCatchesNot "DL4006" $ Text.unlines dockerFile+ it "warns when using plain sh" $+ let dockerFile =+ [ "FROM scratch as build",+ "SHELL [\"/bin/sh\", \"-o\", \"pipefail\", \"-c\"]",+ "RUN wget -O - https://some.site | wc -l file > /number"+ ]+ in ruleCatches "DL4006" $ Text.unlines dockerFile+ it "warn on missing pipefail in the next image" $+ let dockerFile =+ [ "FROM scratch as build",+ "SHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]",+ "RUN wget -O - https://some.site | wc -l file > /number",+ "FROM scratch as build2",+ "RUN wget -O - https://some.site | wc -l file > /number"+ ]+ in ruleCatches "DL4006" $ Text.unlines dockerFile+ it "warn on missing pipefail if next SHELL is not using it" $+ let dockerFile =+ [ "FROM scratch as build",+ "SHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]",+ "RUN wget -O - https://some.site | wc -l file > /number",+ "SHELL [\"/bin/sh\", \"-c\"]",+ "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/Hadolint/Rule/ShellcheckSpec.hs view
@@ -0,0 +1,171 @@+module Hadolint.Rule.ShellcheckSpec (spec) where++import Data.Default+import Data.Text as Text+import Helpers+import Test.Hspec+++spec :: SpecWith ()+spec = do+ let ?config = def++ describe "Shellcheck" $ do+ it "runs shellchek on RUN instructions" $ do+ assertChecks "RUN echo $MISSING_QUOTES" failsShellcheck+ assertOnBuildChecks "RUN echo $MISSING_QUOTES" failsShellcheck+ it "not warns on valid scripts" $ do+ assertChecks "RUN echo foo" passesShellcheck+ assertOnBuildChecks "RUN echo foo" passesShellcheck++ it "Does not complain on default env vars" $+ let dockerFile =+ Text.unlines+ [ "RUN echo \"$HTTP_PROXY\"",+ "RUN echo \"$http_proxy\"",+ "RUN echo \"$HTTPS_PROXY\"",+ "RUN echo \"$https_proxy\"",+ "RUN echo \"$FTP_PROXY\"",+ "RUN echo \"$ftp_proxy\"",+ "RUN echo \"$NO_PROXY\"",+ "RUN echo \"$no_proxy\""+ ]+ in do+ assertChecks dockerFile passesShellcheck+ assertOnBuildChecks dockerFile passesShellcheck++ it "Complain on missing env vars" $+ let dockerFile =+ Text.unlines+ [ "RUN echo \"$RTTP_PROXY\""+ ]+ in do+ assertChecks dockerFile failsShellcheck+ assertOnBuildChecks dockerFile failsShellcheck++ it "Is aware of ARGS and ENV" $+ let dockerFile =+ Text.unlines+ [ "ARG foo=bar",+ "ARG another_foo",+ "ENV bar=10 baz=20",+ "RUN echo \"$foo\"",+ "RUN echo \"$another_foo\"",+ "RUN echo \"$bar\"",+ "RUN echo \"$baz\""+ ]+ in do+ assertChecks dockerFile passesShellcheck+ assertOnBuildChecks dockerFile failsShellcheck++ it "Resets env vars after a FROM" $+ let dockerFile =+ Text.unlines+ [ "ARG foo=bar",+ "ARG another_foo",+ "ENV bar=10 baz=20",+ "FROM debian",+ "RUN echo \"$foo\""+ ]+ in do+ assertChecks dockerFile failsShellcheck+ assertOnBuildChecks dockerFile failsShellcheck++ it "Defaults the shell to sh" $+ let dockerFile =+ Text.unlines+ [ "RUN echo $RANDOM"+ ]+ in do+ assertChecks dockerFile failsShellcheck+ assertOnBuildChecks dockerFile failsShellcheck++ it "Can change the shell check to bash" $+ let dockerFile =+ Text.unlines+ [ "SHELL [\"/bin/bash\", \"-eo\", \"pipefail\", \"-c\"]",+ "RUN echo $RANDOM"+ ]+ in do+ assertChecks dockerFile passesShellcheck+ -- This is debatable, as it should actaully pass, but detecting it correctly+ -- is quite difficult+ assertOnBuildChecks dockerFile failsShellcheck+ it "Resets the SHELL to sh after a FROM" $+ let dockerFile =+ Text.unlines+ [ "SHELL [\"/bin/bash\", \"-eo\", \"pipefail\", \"-c\"]",+ "FROM debian",+ "RUN echo $RANDOM"+ ]+ in do+ assertChecks dockerFile failsShellcheck+ assertOnBuildChecks dockerFile failsShellcheck++ it "Does not complain on ash shell" $+ let dockerFile =+ Text.unlines+ [ "SHELL [\"/bin/ash\", \"-o\", \"pipefail\", \"-c\"]",+ "RUN echo hello"+ ]+ in do+ assertChecks dockerFile passesShellcheck+ assertOnBuildChecks dockerFile passesShellcheck++ 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+ assertChecks dockerFile passesShellcheck+ assertOnBuildChecks dockerFile passesShellcheck++ it "Does not complain on non-posix shells, powershell - absolute path" $+ let dockerFile =+ Text.unlines+ [ "SHELL [\"C:\\\\Windows\\\\System32\\\\WindowsPowerShell\\\\\+ \v1.0\\\\powershell.exe\", \"-noprofile\", \"-noninteractive\"\+ \, \"-command\"]",+ "RUN Get-Variable PSVersionTable | Select-Object \+ \-ExpandProperty Value"+ ]+ in do+ assertChecks dockerFile passesShellcheck+ assertOnBuildChecks dockerFile passesShellcheck++ it "Respects shell pragma" $+ let dockerFile =+ Text.unlines+ [ "FROM mcr.microsoft.com/foo/bar/windows:10",+ "# hadolint shell = powershell.exe",+ "RUN Get-Variable PSVersionTable | Select-Object \+ \-ExpandProperty Value"+ ]+ in do+ assertChecks dockerFile passesShellcheck+ assertOnBuildChecks dockerFile passesShellcheck+ it "Respects global shell pragma" $+ let dockerFile =+ Text.unlines+ [ "# hadolint shell=powershell.exe",+ "FROM mcr.microsoft.com/foo/bar/windows:10",+ "RUN Get-Variable PSVersionTable | Select-Object \+ \-ExpandProperty Value",+ "FROM mcr.microsoft.com/foo/bar/windows:10",+ "RUN Get-Variable PSVersionTable | Select-Object \+ \-ExpandProperty Value"+ ]+ in do+ assertChecks dockerFile passesShellcheck+ assertOnBuildChecks dockerFile passesShellcheck
+ test/Hadolint/ShellSpec.hs view
@@ -0,0 +1,53 @@+module Hadolint.ShellSpec (spec) where++import Hadolint.Shell+import Test.Hspec+++spec :: SpecWith ()+spec =+ describe "Shell unit tests" $ do+ --+ it "getFlagArgs" $ do+ let cmd = Command "useradd"+ [ CmdPart "12345" 2,+ CmdPart "67890" 4,+ CmdPart "luser" 5 ]+ [ CmdPart "u" 1,+ CmdPart "u" 3 ]+ in getFlagArg "u" cmd `shouldBe` [ "12345",+ "67890" ]+ let cmd = Command "useradd"+ [ CmdPart "12345" 2,+ CmdPart "luser" 3 ]+ [ CmdPart "u" 1 ]+ in getFlagArg "u" cmd `shouldBe` [ "12345" ]++ let cmd = Command "useradd"+ [ CmdPart "12345" 2,+ CmdPart "luser" 3 ]+ [ CmdPart "u" 1 ]+ in getFlagArg "f" cmd `shouldBe` []+ --+ it "hasFlag" $ do+ let cmd = Command "useradd"+ [ CmdPart "luser" 1 ]+ [ CmdPart "l" 0 ]+ in hasFlag "l" cmd `shouldBe` True+ let cmd = Command "useradd"+ [ CmdPart "luser" 1 ]+ [ CmdPart "l" 0 ]+ in hasFlag "f" cmd `shouldBe` False+ --+ it "dropFlagArg" $ do+ let cmd = Command "npm"+ [ CmdPart "verbose" 2,+ CmdPart "install" 3,+ CmdPart "bla@1.0.0" 4]+ [ CmdPart "loglevel" 1]+ let res = Command "npm"+ [ CmdPart "install" 3,+ CmdPart "bla@1.0.0" 4]+ [ CmdPart "loglevel" 1]+ in do+ dropFlagArg ["loglevel"] cmd `shouldBe` res
test/Helpers.hs view
@@ -1,30 +1,34 @@ module Helpers where -import qualified Control.Foldl as Foldl import Control.Monad (unless, when)-import qualified Data.Sequence as Seq-import qualified Data.Text as Text+import Hadolint (Configuration (..), OutputFormat (..), printResults)+import Hadolint.Formatter.Format (Result (..)) import Hadolint.Formatter.TTY (formatCheck)-import qualified Hadolint.Process import Hadolint.Rule (CheckFailure (..), Failures, RuleCode (..)) import Language.Docker.Parser import Language.Docker.Syntax+import System.IO.Silently import Test.HUnit hiding (Label) import Test.Hspec+import qualified Control.Foldl as Foldl+import qualified Data.List.NonEmpty as NonEmpty+import qualified Data.Sequence as Seq+import qualified Data.Text as Text+import qualified Hadolint.Process assertChecks ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ (HasCallStack, ?config :: Configuration) => Text.Text -> (Failures -> IO a) -> IO a assertChecks dockerfile makeAssertions = case parseText (dockerfile <> "\n") of Left err -> assertFailure $ show err- Right dockerFile -> makeAssertions $ Hadolint.Process.run ?rulesConfig dockerFile+ Right dockerFile -> makeAssertions $ Hadolint.Process.run ?config dockerFile assertOnBuildChecks ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ (HasCallStack, ?config :: Configuration) => Text.Text -> (Failures -> IO a) -> IO a@@ -33,7 +37,7 @@ Left err -> assertFailure $ show err Right dockerFile -> checkOnBuild dockerFile where- checkOnBuild dockerFile = makeAssertions $ Hadolint.Process.run ?rulesConfig (fmap wrapInOnBuild dockerFile)+ checkOnBuild dockerFile = makeAssertions $ Hadolint.Process.run ?config (fmap wrapInOnBuild dockerFile) wrapInOnBuild (InstructionPos (Run args) so li) = InstructionPos (OnBuild (Run args)) so li wrapInOnBuild i = i @@ -44,7 +48,7 @@ -- Assert a failed check exists for rule ruleCatches ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ (HasCallStack, ?config :: Configuration) => RuleCode -> Text.Text -> Assertion@@ -55,7 +59,7 @@ assertBool "Incorrect line number for result" $ not $ hasInvalidLines checks onBuildRuleCatches ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ (HasCallStack, ?config :: Configuration) => RuleCode -> Text.Text -> Assertion@@ -66,7 +70,7 @@ assertBool "Incorrect line number for result" $ not $ hasInvalidLines checks ruleCatchesNot ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ (HasCallStack, ?config :: Configuration) => RuleCode -> Text.Text -> Assertion@@ -75,7 +79,7 @@ f = failsWith 0 ruleCode onBuildRuleCatchesNot ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ (HasCallStack, ?config :: Configuration) => RuleCode -> Text.Text -> Assertion@@ -118,3 +122,17 @@ <> (Text.unpack . formatChecksNoColor $ matched) where matched = Seq.filter (\CheckFailure {code = RuleCode rc} -> "SC" `Text.isPrefixOf` rc) checks+++assertFormatter ::+ (HasCallStack, ?noColor :: Bool) =>+ OutputFormat ->+ [CheckFailure] ->+ String ->+ Assertion+assertFormatter formatter failures expectation = do+ let results =+ NonEmpty.fromList [Result "<string>" mempty (Seq.fromList failures)]+ (cap, _) <- capture+ (printResults formatter ?noColor (Just "<string>") results)+ cap `shouldBe` expectation
+ test/RegressionSpec.hs view
@@ -0,0 +1,32 @@+module RegressionSpec (spec) where++import Data.Default+import qualified Data.Text as Text+import Helpers+import Test.HUnit hiding (Label)+import Test.Hspec++spec :: SpecWith ()+spec = do+ let ?config = def -- default implicit parameter running the checkers+ describe "Regression Tests" $ do+ it "Comments with backslashes at the end are just comments" $+ let dockerFile =+ [ "FROM alpine:3.6",+ "# The following comment makes hadolint still complain about DL4006",+ "# \\",+ "# should solve DL4006",+ "SHELL [\"/bin/sh\", \"-o\", \"pipefail\", \"-c\"]",+ "# RUN with pipe. causes DL4006, but should be fixed by above SHELL",+ "RUN echo \"kaka\" | sed 's/a/o/g' >> /root/afile"+ ]+ in ruleCatches "DL4006" $ Text.unlines dockerFile+ it "`ARG` can correctly unset variables" $+ let dockerFile =+ [ "ARG A_WITHOUT_EQ",+ "ARG A_WITH_EQ=",+ "RUN echo bla"+ ]+ in assertChecks+ (Text.unlines dockerFile)+ (assertBool "No Warnings or Errors should be triggered" . null)
− test/ShellSpec.hs
@@ -1,52 +0,0 @@-module ShellSpec where--import Hadolint.Shell-import Test.Hspec--tests :: SpecWith ()-tests =- describe "Shell unit tests" $ do- --- it "getFlagArgs" $ do- let cmd = Command "useradd"- [ CmdPart "12345" 2,- CmdPart "67890" 4,- CmdPart "luser" 5 ]- [ CmdPart "u" 1,- CmdPart "u" 3 ]- in getFlagArg "u" cmd `shouldBe` [ "12345",- "67890" ]- let cmd = Command "useradd"- [ CmdPart "12345" 2,- CmdPart "luser" 3 ]- [ CmdPart "u" 1 ]- in getFlagArg "u" cmd `shouldBe` [ "12345" ]-- let cmd = Command "useradd"- [ CmdPart "12345" 2,- CmdPart "luser" 3 ]- [ CmdPart "u" 1 ]- in getFlagArg "f" cmd `shouldBe` []- --- it "hasFlag" $ do- let cmd = Command "useradd"- [ CmdPart "luser" 1 ]- [ CmdPart "l" 0 ]- in hasFlag "l" cmd `shouldBe` True- let cmd = Command "useradd"- [ CmdPart "luser" 1 ]- [ CmdPart "l" 0 ]- in hasFlag "f" cmd `shouldBe` False- --- it "dropFlagArg" $ do- let cmd = Command "npm"- [ CmdPart "verbose" 2,- CmdPart "install" 3,- CmdPart "bla@1.0.0" 4]- [ CmdPart "loglevel" 1]- let res = Command "npm"- [ CmdPart "install" 3,- CmdPart "bla@1.0.0" 4]- [ CmdPart "loglevel" 1]- in do- dropFlagArg ["loglevel"] cmd `shouldBe` res
− test/Shellcheck.hs
@@ -1,130 +0,0 @@-module Shellcheck (tests) where--import Data.Text as Text-import Helpers-import Test.Hspec---tests :: SpecWith ()-tests = do- let ?rulesConfig = mempty- describe "Shellcheck" $ do- it "runs shellchek on RUN instructions" $ do- assertChecks "RUN echo $MISSING_QUOTES" failsShellcheck- assertOnBuildChecks "RUN echo $MISSING_QUOTES" failsShellcheck- it "not warns on valid scripts" $ do- assertChecks "RUN echo foo" passesShellcheck- assertOnBuildChecks "RUN echo foo" passesShellcheck-- it "Does not complain on default env vars" $- let dockerFile =- Text.unlines- [ "RUN echo \"$HTTP_PROXY\"",- "RUN echo \"$http_proxy\"",- "RUN echo \"$HTTPS_PROXY\"",- "RUN echo \"$https_proxy\"",- "RUN echo \"$FTP_PROXY\"",- "RUN echo \"$ftp_proxy\"",- "RUN echo \"$NO_PROXY\"",- "RUN echo \"$no_proxy\""- ]- in do- assertChecks dockerFile passesShellcheck- assertOnBuildChecks dockerFile passesShellcheck-- it "Complain on missing env vars" $- let dockerFile =- Text.unlines- [ "RUN echo \"$RTTP_PROXY\""- ]- in do- assertChecks dockerFile failsShellcheck- assertOnBuildChecks dockerFile failsShellcheck-- it "Is aware of ARGS and ENV" $- let dockerFile =- Text.unlines- [ "ARG foo=bar",- "ARG another_foo",- "ENV bar=10 baz=20",- "RUN echo \"$foo\"",- "RUN echo \"$another_foo\"",- "RUN echo \"$bar\"",- "RUN echo \"$baz\""- ]- in do- assertChecks dockerFile passesShellcheck- assertOnBuildChecks dockerFile failsShellcheck-- it "Resets env vars after a FROM" $- let dockerFile =- Text.unlines- [ "ARG foo=bar",- "ARG another_foo",- "ENV bar=10 baz=20",- "FROM debian",- "RUN echo \"$foo\""- ]- in do- assertChecks dockerFile failsShellcheck- assertOnBuildChecks dockerFile failsShellcheck-- it "Defaults the shell to sh" $- let dockerFile =- Text.unlines- [ "RUN echo $RANDOM"- ]- in do- assertChecks dockerFile failsShellcheck- assertOnBuildChecks dockerFile failsShellcheck-- it "Can change the shell check to bash" $- let dockerFile =- Text.unlines- [ "SHELL [\"/bin/bash\", \"-eo\", \"pipefail\", \"-c\"]",- "RUN echo $RANDOM"- ]- in do- assertChecks dockerFile passesShellcheck- -- This is debatable, as it should actaully pass, but detecting it correctly- -- is quite difficult- assertOnBuildChecks dockerFile failsShellcheck- it "Resets the SHELL to sh after a FROM" $- let dockerFile =- Text.unlines- [ "SHELL [\"/bin/bash\", \"-eo\", \"pipefail\", \"-c\"]",- "FROM debian",- "RUN echo $RANDOM"- ]- in do- assertChecks dockerFile failsShellcheck- assertOnBuildChecks dockerFile failsShellcheck-- it "Does not complain on ash shell" $- let dockerFile =- Text.unlines- [ "SHELL [\"/bin/ash\", \"-o\", \"pipefail\", \"-c\"]",- "RUN echo hello"- ]- in do- assertChecks dockerFile passesShellcheck- assertOnBuildChecks dockerFile passesShellcheck-- 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- assertChecks dockerFile passesShellcheck- assertOnBuildChecks dockerFile passesShellcheck
test/Spec.hs view
@@ -1,233 +1,1 @@-import qualified ConfigSpec-import qualified Data.Text as Text-import qualified DL3000-import qualified DL3001-import qualified DL3002-import qualified DL3003-import qualified DL3004-import qualified DL3005-import qualified DL3006-import qualified DL3007-import qualified DL3008-import qualified DL3009-import qualified DL3010-import qualified DL3011-import qualified DL3012-import qualified DL3013-import qualified DL3014-import qualified DL3015-import qualified DL3016-import qualified DL3018-import qualified DL3019-import qualified DL3020-import qualified DL3021-import qualified DL3022-import qualified DL3023-import qualified DL3024-import qualified DL3025-import qualified DL3026-import qualified DL3027-import qualified DL3028-import qualified DL3029-import qualified DL3030-import qualified DL3032-import qualified DL3033-import qualified DL3034-import qualified DL3035-import qualified DL3036-import qualified DL3037-import qualified DL3038-import qualified DL3040-import qualified DL3041-import qualified DL3042-import qualified DL3043-import qualified DL3044-import qualified DL3045-import qualified DL3046-import qualified DL3047-import qualified DL3048-import qualified DL3049-import qualified DL3050-import qualified DL3051-import qualified DL3052-import qualified DL3053-import qualified DL3054-import qualified DL3055-import qualified DL3056-import qualified DL3057-import qualified DL3058-import qualified DL3059-import qualified DL3060-import qualified DL4001-import qualified DL4003-import qualified DL4004-import qualified DL4005-import qualified DL4006-import Hadolint.Formatter.Format (errorMessage)-import Hadolint.Formatter.TTY (formatError)-import Helpers-import Language.Docker.Parser-import qualified Shellcheck-import qualified ShellSpec-import Test.HUnit hiding (Label)-import Test.Hspec--main :: IO ()-main =- hspec $ do- let ?rulesConfig = mempty -- default implicit parameter running the checkers- describe "format error" $- it "display error after line pos" $ do- let ast = parseText "FOM debian:jessie"- expectedMsg =- "<string>:1:1 unexpected 'F' expecting '#', ADD, ARG, CMD, COPY, ENTRYPOINT, "- <> "ENV, EXPOSE, FROM, HEALTHCHECK, LABEL, MAINTAINER, ONBUILD, RUN, SHELL, STOPSIGNAL, "- <> "USER, VOLUME, WORKDIR, a pragma, end of input, or whitespaces "- case ast of- Left err -> assertEqual "Unexpected error msg" expectedMsg (formatError err)- Right _ -> assertFailure "AST should fail parsing"- describe "errorMessage" $- it "display just the error message" $ do- let ast = parseText "RUNNN"- expectedMsg = "missing whitespace"- case ast of- Left err -> assertEqual "Unexpected error msg" expectedMsg (errorMessage err)- Right _ -> assertFailure "AST should fail parsing"- --- describe "Rules can be ignored with inline comments" $ do- it "ignores single rule" $- let dockerFile =- [ "FROM ubuntu",- "# hadolint ignore=DL3002",- "USER root"- ]- in ruleCatchesNot "DL3002" $ Text.unlines dockerFile- it "ignores only the given rule" $- let dockerFile =- [ "FROM scratch",- "# hadolint ignore=DL3001",- "USER root"- ]- in ruleCatches "DL3002" $ Text.unlines dockerFile- it "ignores only the given rule, when multiple passed" $- let dockerFile =- [ "FROM scratch",- "# hadolint ignore=DL3001,DL3002",- "USER root"- ]- in ruleCatchesNot "DL3002" $ Text.unlines dockerFile- it "ignores the rule only if directly above the instruction" $- let dockerFile =- [ "# hadolint ignore=DL3001,DL3002",- "FROM ubuntu",- "USER root"- ]- in ruleCatches "DL3002" $ Text.unlines dockerFile- it "won't ignore the rule if passed invalid rule names" $- let dockerFile =- [ "FROM scratch",- "# hadolint ignore=crazy,DL3002",- "USER root"- ]- in ruleCatches "DL3002" $ Text.unlines dockerFile- it "ignores multiple rules correctly, even with some extra whitespace" $- let dockerFile =- [ "FROM node as foo",- "# hadolint ignore=DL3023, DL3021",- "COPY --from=foo bar baz ."- ]- in do- ruleCatchesNot "DL3023" $ Text.unlines dockerFile- ruleCatchesNot "DL3021" $ Text.unlines dockerFile- --- describe "Regression Tests" $ do- it "Comments with backslashes at the end are just comments" $- let dockerFile =- [ "FROM alpine:3.6",- "# The following comment makes hadolint still complain about DL4006",- "# \\",- "# should solve DL4006",- "SHELL [\"/bin/sh\", \"-o\", \"pipefail\", \"-c\"]",- "# RUN with pipe. causes DL4006, but should be fixed by above SHELL",- "RUN echo \"kaka\" | sed 's/a/o/g' >> /root/afile"- ]- in ruleCatches "DL4006" $ Text.unlines dockerFile- it "`ARG` can correctly unset variables" $- let dockerFile =- [ "ARG A_WITHOUT_EQ",- "ARG A_WITH_EQ=",- "RUN echo bla"- ]- in assertChecks- (Text.unlines dockerFile)- (assertBool "No Warnings or Errors should be triggered" . null)-- -- Run tests for the Config module- ConfigSpec.tests- -- Run tests for the Shell module- ShellSpec.tests- -- Run rule tests- Shellcheck.tests- DL3000.tests- DL3001.tests- DL3002.tests- DL3003.tests- DL3004.tests- DL3005.tests- DL3006.tests- DL3007.tests- DL3008.tests- DL3009.tests- DL3010.tests- DL3011.tests- DL3012.tests- DL3013.tests- DL3014.tests- DL3015.tests- DL3016.tests- DL3018.tests- DL3019.tests- DL3020.tests- DL3021.tests- DL3022.tests- DL3023.tests- DL3024.tests- DL3025.tests- DL3026.tests- DL3027.tests- DL3028.tests- DL3029.tests- DL3030.tests- DL3032.tests- DL3033.tests- DL3034.tests- DL3035.tests- DL3036.tests- DL3037.tests- DL3038.tests- DL3040.tests- DL3041.tests- DL3042.tests- DL3043.tests- DL3044.tests- DL3045.tests- DL3046.tests- DL3047.tests- DL3048.tests- DL3049.tests- DL3050.tests- DL3051.tests- DL3052.tests- DL3053.tests- DL3054.tests- DL3055.tests- DL3056.tests- DL3057.tests- DL3058.tests- DL3059.tests- DL3060.tests- DL4001.tests- DL4003.tests- DL4004.tests- DL4005.tests- DL4006.tests+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}