hadolint 2.0.0 → 2.1.0
raw patch · 80 files changed
+2996/−2073 lines, 80 filesdep +email-validatePVP ok
version bump matches the API change (PVP)
Dependencies added: email-validate
API changes (from Hackage documentation)
+ Hadolint.Rule: Email :: LabelType
+ Hadolint.Rule.DL3058: rule :: LabelSchema -> Rule args
+ Hadolint.Shell: instance GHC.Classes.Eq Hadolint.Shell.CmdPart
+ Hadolint.Shell: instance GHC.Classes.Eq Hadolint.Shell.Command
- Hadolint.Formatter.Json: formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Value
+ Hadolint.Formatter.Json: formatResult :: Result s e -> Seq (JsonFormat s e)
Files
- README.md +1/−0
- hadolint.cabal +72/−3
- src/Hadolint/Formatter/Json.hs +7/−9
- src/Hadolint/Process.hs +2/−0
- src/Hadolint/Rule.hs +2/−0
- src/Hadolint/Rule/DL3000.hs +13/−4
- src/Hadolint/Rule/DL3016.hs +51/−24
- src/Hadolint/Rule/DL3045.hs +7/−1
- src/Hadolint/Rule/DL3058.hs +26/−0
- src/Hadolint/Shell.hs +4/−4
- test/ConfigSpec.hs +0/−2
- test/DL3000.hs +26/−0
- test/DL3001.hs +16/−0
- test/DL3002.hs +86/−0
- test/DL3003.hs +12/−0
- test/DL3004.hs +19/−0
- test/DL3005.hs +16/−0
- test/DL3006.hs +42/−0
- test/DL3007.hs +20/−0
- test/DL3008.hs +61/−0
- test/DL3009.hs +77/−0
- test/DL3010.hs +12/−0
- test/DL3011.hs +12/−0
- test/DL3012.hs +18/−0
- test/DL3013.hs +139/−0
- test/DL3014.hs +31/−0
- test/DL3015.hs +26/−0
- test/DL3016.hs +123/−0
- test/DL3017.hs +13/−0
- test/DL3018.hs +86/−0
- test/DL3019.hs +16/−0
- test/DL3020.hs +18/−0
- test/DL3021.hs +13/−0
- test/DL3022.hs +29/−0
- test/DL3023.hs +26/−0
- test/DL3024.hs +27/−0
- test/DL3025.hs +36/−0
- test/DL3026.hs +56/−0
- test/DL3027.hs +19/−0
- test/DL3028.hs +42/−0
- test/DL3029.hs +12/−0
- test/DL3030.hs +18/−0
- test/DL3031.hs +18/−0
- test/DL3032.hs +18/−0
- test/DL3033.hs +18/−0
- test/DL3034.hs +18/−0
- test/DL3035.hs +20/−0
- test/DL3036.hs +18/−0
- test/DL3037.hs +28/−0
- test/DL3038.hs +18/−0
- test/DL3039.hs +18/−0
- test/DL3040.hs +18/−0
- test/DL3041.hs +18/−0
- test/DL3042.hs +34/−0
- test/DL3043.hs +43/−0
- test/DL3044.hs +42/−0
- test/DL3045.hs +152/−0
- test/DL3046.hs +15/−0
- test/DL3047.hs +71/−0
- test/DL3048.hs +37/−0
- test/DL3049.hs +80/−0
- test/DL3050.hs +25/−0
- test/DL3051.hs +22/−0
- test/DL3052.hs +22/−0
- test/DL3053.hs +22/−0
- test/DL3054.hs +22/−0
- test/DL3055.hs +25/−0
- test/DL3056.hs +22/−0
- test/DL3057.hs +18/−0
- test/DL3058.hs +22/−0
- test/DL4000.hs +15/−0
- test/DL4001.hs +69/−0
- test/DL4003.hs +42/−0
- test/DL4004.hs +45/−0
- test/DL4005.hs +19/−0
- test/DL4006.hs +83/−0
- test/Helpers.hs +120/−0
- test/ShellSpec.hs +39/−28
- test/Shellcheck.hs +121/−0
- test/Spec.hs +227/−1998
README.md view
@@ -292,6 +292,7 @@ | [DL3055](https://github.com/hadolint/hadolint/wiki/DL3055) | Label `<label>` is not a valid git hash. | | [DL3056](https://github.com/hadolint/hadolint/wiki/DL3056) | Label `<label>` does not conform to semantic versioning. | | [DL3057](https://github.com/hadolint/hadolint/wiki/DL3057) | `HEALTHCHECK` instruction missing. |+| [DL3058](https://github.com/hadolint/hadolint/wiki/DL3058) | Label `<label>` is not a valid email format - must be conform to RFC5322. | | [DL4000](https://github.com/hadolint/hadolint/wiki/DL4000) | MAINTAINER is deprecated. | | [DL4001](https://github.com/hadolint/hadolint/wiki/DL4001) | Either use Wget or Curl but not both. | | [DL4003](https://github.com/hadolint/hadolint/wiki/DL4003) | Multiple `CMD` instructions found. |
hadolint.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 6dc5574db9b4edf093ae954fa31aa254d6a3b1a37aa6dc0bf793182d24daf375+-- hash: 3a6af1aecfc32d960fd1df8de865bb60b8682f8877a9b3bb848a3f59c50c063d name: hadolint-version: 2.0.0+version: 2.1.0 synopsis: Dockerfile Linter JavaScript API description: A smarter Dockerfile linter that helps you build best practice Docker images. category: Development@@ -102,6 +102,7 @@ Hadolint.Rule.DL3055 Hadolint.Rule.DL3056 Hadolint.Rule.DL3057+ Hadolint.Rule.DL3058 Hadolint.Rule.DL4000 Hadolint.Rule.DL4001 Hadolint.Rule.DL4003@@ -130,6 +131,7 @@ , cryptonite , deepseq ==1.4.4.* , directory >=1.3.0+ , email-validate , filepath , foldl , ilist@@ -174,11 +176,78 @@ main-is: Spec.hs other-modules: ConfigSpec+ DL3000+ DL3001+ DL3002+ DL3003+ DL3004+ DL3005+ DL3006+ DL3007+ DL3008+ DL3009+ DL3010+ DL3011+ DL3012+ DL3013+ DL3014+ DL3015+ DL3016+ DL3017+ DL3018+ DL3019+ DL3020+ DL3021+ DL3022+ DL3023+ DL3024+ DL3025+ DL3026+ DL3027+ DL3028+ DL3029+ DL3030+ DL3031+ DL3032+ DL3033+ DL3034+ DL3035+ DL3036+ DL3037+ DL3038+ DL3039+ DL3040+ DL3041+ DL3042+ DL3043+ DL3044+ DL3045+ DL3046+ DL3047+ DL3048+ DL3049+ DL3050+ DL3051+ DL3052+ DL3053+ DL3054+ DL3055+ DL3056+ DL3057+ DL3058+ DL4000+ DL4001+ DL4003+ DL4004+ DL4005+ DL4006+ Helpers+ Shellcheck ShellSpec Paths_hadolint hs-source-dirs: test- default-extensions: OverloadedStrings NamedFieldPuns DeriveGeneric DeriveAnyClass RecordWildCards StrictData ScopedTypeVariables PatternSynonyms+ default-extensions: OverloadedStrings NamedFieldPuns DeriveGeneric DeriveAnyClass RecordWildCards StrictData ScopedTypeVariables PatternSynonyms ImplicitParams OverloadedLists ghc-options: -Wall -Wcompat -Wincomplete-record-updates -Wincomplete-uni-patterns -Wredundant-constraints -optP-Wno-nonportable-include-path -flate-dmd-anal build-depends: HUnit >=1.2
src/Hadolint/Formatter/Json.hs view
@@ -4,8 +4,10 @@ ) where +import qualified Control.Foldl as Foldl import Data.Aeson hiding (Result) import qualified Data.ByteString.Lazy.Char8 as B+import Data.Sequence (Seq) import qualified Data.Text as Text import Hadolint.Formatter.Format (Result (..), errorPosition, severityText) import Hadolint.Rule (CheckFailure (..), DLSeverity (..), unRuleCode)@@ -44,17 +46,13 @@ (VisualStream s, TraversableStream s, ShowErrorComponent e, Foldable f) => f (Result s e) -> IO ()-printResults = mapM_ printResult+printResults results = B.putStr . encode $ flattened+ where+ flattened = Foldl.fold (Foldl.premap formatResult Foldl.mconcat) results -formatResult :: (VisualStream s, TraversableStream s, ShowErrorComponent e) => Result s e -> Value-formatResult (Result fileName errors checks) = toJSON allMessages+formatResult :: Result s e -> Seq (JsonFormat s e)+formatResult (Result fileName errors checks) = allMessages where allMessages = errorMessages <> checkMessages errorMessages = fmap JsonParseError errors checkMessages = fmap (JsonCheck fileName) checks--printResult ::- (VisualStream s, TraversableStream s, ShowErrorComponent e) =>- Result s e ->- IO ()-printResult result = B.putStrLn (encode (formatResult result))
src/Hadolint/Process.hs view
@@ -66,6 +66,7 @@ import qualified Hadolint.Rule.DL3055 import qualified Hadolint.Rule.DL3056 import qualified Hadolint.Rule.DL3057+import qualified Hadolint.Rule.DL3058 import qualified Hadolint.Rule.DL4000 import qualified Hadolint.Rule.DL4001 import qualified Hadolint.Rule.DL4003@@ -193,6 +194,7 @@ <> Hadolint.Rule.DL3054.rule labelSchema <> Hadolint.Rule.DL3055.rule labelSchema <> Hadolint.Rule.DL3056.rule labelSchema+ <> Hadolint.Rule.DL3058.rule labelSchema <> Hadolint.Rule.DL3057.rule <> Hadolint.Rule.DL4000.rule <> Hadolint.Rule.DL4001.rule
src/Hadolint/Rule.hs view
@@ -57,6 +57,7 @@ | GitHash | Rfc3339 | SemVer+ | Email deriving (Eq, Read, Show) read :: Text.Text -> Either Text.Text LabelType@@ -65,6 +66,7 @@ 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)
src/Hadolint/Rule/DL3000.hs view
@@ -1,5 +1,6 @@ module Hadolint.Rule.DL3000 (rule) where +import qualified Data.Char as Char import qualified Data.Text as Text import Hadolint.Rule import Language.Docker.Syntax (Instruction (..))@@ -13,10 +14,18 @@ check (Workdir loc) | "$" `Text.isPrefixOf` Text.dropAround dropQuotes loc = True | "/" `Text.isPrefixOf` Text.dropAround dropQuotes loc = True+ | isWindowsAbsolute (Text.dropAround dropQuotes loc) = True | otherwise = False check _ = True- dropQuotes chr- | chr == '\"' = True- | chr == '\'' = True- | otherwise = False {-# INLINEABLE rule #-}++dropQuotes :: Char -> Bool+dropQuotes chr+ | chr == '\"' = True+ | chr == '\'' = True+ | otherwise = False++isWindowsAbsolute :: Text.Text -> Bool+isWindowsAbsolute path+ | Char.isLetter (Text.index path 0) && (':' == Text.index path 1) = True+ | otherwise = False
src/Hadolint/Rule/DL3016.hs view
@@ -26,36 +26,63 @@ check (Run (RunArgs args _)) = foldArguments (Shell.noCommands forgotToPinVersion) args check _ = True+{-# INLINEABLE rule #-} - forgotToPinVersion cmd =- isNpmInstall cmd && installIsFirst cmd && not (all versionFixed (packages cmd)) - isNpmInstall = Shell.cmdHasArgs "npm" ["install"]- installIsFirst cmd = ["install"] `isPrefixOf` Shell.getArgsNoFlags cmd- packages cmd = stripInstallPrefix (Shell.getArgsNoFlags cmd)+forgotToPinVersion :: Shell.Command -> Bool+forgotToPinVersion cmd =+ isNpmInstall cmd && installIsFirst cmd && not (all versionFixed (packages cmd)) - versionFixed package- | hasGitPrefix package = isVersionedGit package- | hasTarballSuffix package = True- | isFolder package = True- | otherwise = hasVersionSymbol package+isNpmInstall :: Shell.Command -> Bool+isNpmInstall = Shell.cmdHasArgs "npm" ["install"] - gitPrefixes = ["git://", "git+ssh://", "git+http://", "git+https://"]- pathPrefixes = ["/", "./", "../", "~/"]- tarballSuffixes = [".tar", ".tar.gz", ".tgz"]+installIsFirst :: Shell.Command -> Bool+installIsFirst cmd = ["install"] `isPrefixOf` Shell.getArgsNoFlags+ (Shell.dropFlagArg ignoreFlags cmd) - hasGitPrefix package = or [p `Text.isPrefixOf` package | p <- gitPrefixes]- hasTarballSuffix package = or [p `Text.isSuffixOf` package | p <- tarballSuffixes]- isFolder package = or [p `Text.isPrefixOf` package | p <- pathPrefixes]- isVersionedGit package = "#" `Text.isInfixOf` package+packages :: Shell.Command -> [Text.Text]+packages cmd = stripInstallPrefix+ (Shell.getArgsNoFlags (Shell.dropFlagArg ignoreFlags cmd)) - hasVersionSymbol package = "@" `Text.isInfixOf` dropScope package- where- dropScope pkg =- if "@" `Text.isPrefixOf` pkg- then Text.dropWhile ('/' <) pkg- else pkg-{-# INLINEABLE rule #-}+versionFixed :: Text.Text -> Bool+versionFixed package+ | hasGitPrefix package = isVersionedGit package+ | hasTarballSuffix package = True+ | isFolder package = True+ | otherwise = hasVersionSymbol package +hasGitPrefix :: Text.Text -> Bool+hasGitPrefix package = or [p `Text.isPrefixOf` package | p <- gitPrefixes]++hasTarballSuffix :: Text.Text -> Bool+hasTarballSuffix package = or [p `Text.isSuffixOf` package | p <- tarballSuffixes]++isFolder :: Text.Text -> Bool+isFolder package = or [p `Text.isPrefixOf` package | p <- pathPrefixes]++isVersionedGit :: Text.Text -> Bool+isVersionedGit package = "#" `Text.isInfixOf` package++hasVersionSymbol :: Text.Text -> Bool+hasVersionSymbol package = "@" `Text.isInfixOf` dropScope package+ where+ dropScope pkg =+ if "@" `Text.isPrefixOf` pkg+ then Text.dropWhile ('/' <) pkg+ else pkg+ stripInstallPrefix :: [Text.Text] -> [Text.Text] stripInstallPrefix cmd = dropWhile (== "install") (dropWhile (/= "install") cmd)+++ignoreFlags :: [Text.Text]+ignoreFlags = ["loglevel"]++gitPrefixes :: [Text.Text]+gitPrefixes = ["git://", "git+ssh://", "git+http://", "git+https://"]++pathPrefixes :: [Text.Text]+pathPrefixes = ["/", "./", "../", "~/"]++tarballSuffixes :: [Text.Text]+tarballSuffixes = [".tar", ".tar.gz", ".tgz"]
src/Hadolint/Rule/DL3045.hs view
@@ -1,5 +1,6 @@ module Hadolint.Rule.DL3045 (rule) where +import qualified Data.Char as Char import Data.Map.Strict (Map) import qualified Data.Map.Strict as Map import Data.Maybe (fromMaybe)@@ -34,7 +35,7 @@ check line st (Copy (CopyArgs _ (TargetPath dest) _ _)) | Acc s m <- state st, Just True <- Map.lookup s m = st -- workdir has been set | "/" `Text.isPrefixOf` Text.dropAround quotePredicate dest = st -- absolute dest. normal- | ":\\" `Text.isPrefixOf` Text.drop 1 (Text.dropAround quotePredicate dest) = st -- absolute dest. windows+ | isWindowsAbsolute (Text.dropAround quotePredicate dest) = st -- absolute dest. windows | "$" `Text.isPrefixOf` Text.dropAround quotePredicate dest = st -- dest is a variable | otherwise = st |> addFail CheckFailure {..} check _ st _ = st@@ -80,4 +81,9 @@ quotePredicate c | c == '"' = True | c == '\'' = True+ | otherwise = False++isWindowsAbsolute :: Text.Text -> Bool+isWindowsAbsolute path+ | Char.isLetter (Text.index path 0) && (':' == Text.index path 1) = True | otherwise = False
+ src/Hadolint/Rule/DL3058.hs view
@@ -0,0 +1,26 @@+module Hadolint.Rule.DL3058 (rule) where++import qualified Data.Map as Map+import Text.Email.Validate as Email+import Hadolint.Rule+import Language.Docker.Syntax+import Data.Text.Encoding+++rule :: LabelSchema -> Rule args+rule labelschema = mconcat $+ fmap labelIsNotEmailRule (Map.keys (Map.filter (== Email) labelschema))+{-# INLINEABLE rule #-}+++labelIsNotEmailRule :: LabelName -> Rule args+labelIsNotEmailRule label = simpleRule code severity message check+ where+ code = "DL3058"+ severity = DLWarningC+ message = "Label `" <> label <> "` is not a valid email format - must be conform to RFC5322."+ check (Label ls) = null $ getBadEmailformatLabels label ls+ check _ = True++getBadEmailformatLabels :: LabelName -> Pairs -> Pairs+getBadEmailformatLabels lbl pairs = [(l, v) | (l, v) <- pairs, l == lbl, not (Email.isValid (encodeUtf8 v))]
src/Hadolint/Shell.hs view
@@ -18,14 +18,14 @@ { arg :: Text, partId :: Int }- deriving (Show)+ deriving (Eq, Show) data Command = Command { name :: Text.Text, arguments :: [CmdPart], flags :: [CmdPart] }- deriving (Show)+ deriving (Eq, Show) data ParsedShell = ParsedShell { original :: Text.Text,@@ -202,8 +202,8 @@ getFlagArg :: Text.Text -> Command -> [Text.Text] getFlagArg flag Command {arguments, flags} = extractArgs where- idsToGet = [getValueId fId arguments | CmdPart f fId <- flags, f == flag]- extractArgs = [arg | (CmdPart arg aId) <- arguments, aId `elem` idsToGet]+ idsToGet = Set.fromList [getValueId fId arguments | CmdPart f fId <- flags, f == flag]+ extractArgs = [arg | (CmdPart arg aId) <- arguments, aId `Set.member` idsToGet] getValueId :: Int -> [CmdPart] -> Int getValueId fId flags = foldl min (maxBound :: Int) $ filter (> fId) $ map partId flags
test/ConfigSpec.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE OverloadedStrings #-}- module ConfigSpec where import Control.Monad (unless)
+ test/DL3000.hs view
@@ -0,0 +1,26 @@+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 view
@@ -0,0 +1,16 @@+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 view
@@ -0,0 +1,86 @@+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 view
@@ -0,0 +1,12 @@+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 view
@@ -0,0 +1,19 @@+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 view
@@ -0,0 +1,16 @@+module DL3005 (tests) where++import Helpers+import Test.Hspec+++tests :: SpecWith ()+tests = do+ let ?rulesConfig = mempty+ describe "DL3005" $ do+ it "apt-get upgrade" $ do+ ruleCatches "DL3005" "RUN apt-get update && apt-get upgrade"+ onBuildRuleCatches "DL3005" "RUN apt-get update && apt-get upgrade"+ 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 view
@@ -0,0 +1,42 @@+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 view
@@ -0,0 +1,20 @@+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 view
@@ -0,0 +1,61 @@+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 view
@@ -0,0 +1,77 @@+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
+ test/DL3010.hs view
@@ -0,0 +1,12 @@+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"
+ test/DL3011.hs view
@@ -0,0 +1,12 @@+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 view
@@ -0,0 +1,18 @@+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 view
@@ -0,0 +1,139 @@+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 view
@@ -0,0 +1,31 @@+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 view
@@ -0,0 +1,26 @@+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 view
@@ -0,0 +1,123 @@+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/DL3017.hs view
@@ -0,0 +1,13 @@+module DL3017 (tests) where++import Helpers+import Test.Hspec+++tests :: SpecWith ()+tests = do+ let ?rulesConfig = mempty+ describe "DL3017 - Do not use `apk upgrade`." $ do+ it "apk upgrade" $ do+ ruleCatches "DL3017" "RUN apk update && apk upgrade"+ onBuildRuleCatches "DL3017" "RUN apk update && apk upgrade"
+ test/DL3018.hs view
@@ -0,0 +1,86 @@+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
+ test/DL3019.hs view
@@ -0,0 +1,16 @@+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"
+ test/DL3020.hs view
@@ -0,0 +1,18 @@+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 zip" $ ruleCatchesNot "DL3020" "ADD file.zip /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/"
+ test/DL3021.hs view
@@ -0,0 +1,13 @@+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 view
@@ -0,0 +1,29 @@+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 view
@@ -0,0 +1,26 @@+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 view
@@ -0,0 +1,27 @@+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 view
@@ -0,0 +1,36 @@+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 view
@@ -0,0 +1,56 @@+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 view
@@ -0,0 +1,19 @@+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 view
@@ -0,0 +1,42 @@+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 view
@@ -0,0 +1,12 @@+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 view
@@ -0,0 +1,18 @@+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/DL3031.hs view
@@ -0,0 +1,18 @@+module DL3031 (tests) where++import Helpers+import Test.Hspec+++tests :: SpecWith ()+tests = do+ let ?rulesConfig = mempty+ describe "DL3031" $ do+ it "yum update" $ do+ ruleCatches "DL3031" "RUN yum update"+ onBuildRuleCatches "DL3031" "RUN yum update"+ it "not yum update" $ do+ ruleCatchesNot "DL3031" "RUN yum install -y httpd-2.4.42 && yum clean all"+ ruleCatchesNot "DL3031" "RUN bash -c `# not even a yum command`"+ onBuildRuleCatchesNot "DL3031" "RUN yum install -y httpd-2.4.42 && yum clean all"+ onBuildRuleCatchesNot "DL3031" "RUN bash -c `# not even a yum command`"
+ test/DL3032.hs view
@@ -0,0 +1,18 @@+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 view
@@ -0,0 +1,18 @@+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 view
@@ -0,0 +1,18 @@+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 view
@@ -0,0 +1,20 @@+module DL3035 (tests) where++import Helpers+import Test.Hspec+++tests :: SpecWith ()+tests = do+ let ?rulesConfig = mempty+ describe "DL3035" $ do+ it "not ok: zypper update" $ do+ ruleCatches "DL3035" "RUN zypper update"+ ruleCatches "DL3035" "RUN zypper up"+ onBuildRuleCatches "DL3035" "RUN zypper update"+ onBuildRuleCatches "DL3035" "RUN zypper up"+ 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 view
@@ -0,0 +1,18 @@+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 view
@@ -0,0 +1,28 @@+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 view
@@ -0,0 +1,18 @@+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/DL3039.hs view
@@ -0,0 +1,18 @@+module DL3039 (tests) where++import Helpers+import Test.Hspec+++tests :: SpecWith ()+tests = do+ let ?rulesConfig = mempty+ describe "DL3039" $ do+ it "dnf update" $ do+ ruleCatches "DL3039" "RUN dnf upgrade"+ ruleCatches "DL3039" "RUN dnf upgrade-minimal"+ onBuildRuleCatches "DL3039" "RUN dnf upgrade"+ onBuildRuleCatches "DL3039" "RUN dnf upgrade-minimal"+ it "not dnf upgrade" $ do+ ruleCatchesNot "DL3039" "RUN notdnf upgrade"+ onBuildRuleCatchesNot "DL3039" "RUN notdnf upgrade"
+ test/DL3040.hs view
@@ -0,0 +1,18 @@+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 view
@@ -0,0 +1,18 @@+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 view
@@ -0,0 +1,34 @@+module DL3042 (tests) where++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"
+ test/DL3043.hs view
@@ -0,0 +1,43 @@+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 view
@@ -0,0 +1,42 @@+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 view
@@ -0,0 +1,152 @@+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
+ test/DL3046.hs view
@@ -0,0 +1,15 @@+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 view
@@ -0,0 +1,71 @@+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 view
@@ -0,0 +1,37 @@+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 view
@@ -0,0 +1,80 @@+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 view
@@ -0,0 +1,25 @@+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 view
@@ -0,0 +1,22 @@+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 view
@@ -0,0 +1,22 @@+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 view
@@ -0,0 +1,22 @@+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 view
@@ -0,0 +1,22 @@+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 view
@@ -0,0 +1,25 @@+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 view
@@ -0,0 +1,22 @@+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 view
@@ -0,0 +1,18 @@+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 view
@@ -0,0 +1,22 @@+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/DL4000.hs view
@@ -0,0 +1,15 @@+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 view
@@ -0,0 +1,69 @@+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 view
@@ -0,0 +1,42 @@+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 view
@@ -0,0 +1,45 @@+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 view
@@ -0,0 +1,19 @@+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 view
@@ -0,0 +1,83 @@+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
+ test/Helpers.hs view
@@ -0,0 +1,120 @@+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.Formatter.TTY (formatCheck)+import qualified Hadolint.Process+import Hadolint.Rule (CheckFailure (..), Failures, RuleCode (..))+import Language.Docker.Parser+import Language.Docker.Syntax+import Test.HUnit hiding (Label)+import Test.Hspec+++assertChecks ::+ (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ 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++assertOnBuildChecks ::+ (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ Text.Text ->+ (Failures -> IO a) ->+ IO a+assertOnBuildChecks dockerfile makeAssertions =+ case parseText (dockerfile <> "\n") of+ Left err -> assertFailure $ show err+ Right dockerFile -> checkOnBuild dockerFile+ where+ checkOnBuild dockerFile = makeAssertions $ Hadolint.Process.run ?rulesConfig (fmap wrapInOnBuild dockerFile)+ wrapInOnBuild (InstructionPos (Run args) so li) = InstructionPos (OnBuild (Run args)) so li+ wrapInOnBuild i = i++hasInvalidLines :: Failures -> Bool+hasInvalidLines =+ Foldl.fold+ (Foldl.any (\CheckFailure {line} -> line <= 0))++-- Assert a failed check exists for rule+ruleCatches ::+ (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ RuleCode ->+ Text.Text ->+ Assertion+ruleCatches expectedCode dockerfile = assertChecks dockerfile f+ where+ f checks = do+ failsWithSome expectedCode checks+ assertBool "Incorrect line number for result" $ not $ hasInvalidLines checks++onBuildRuleCatches ::+ (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ RuleCode ->+ Text.Text ->+ Assertion+onBuildRuleCatches ruleCode dockerfile = assertOnBuildChecks dockerfile f+ where+ f checks = do+ failsWith 1 ruleCode checks+ assertBool "Incorrect line number for result" $ not $ hasInvalidLines checks++ruleCatchesNot ::+ (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ RuleCode ->+ Text.Text ->+ Assertion+ruleCatchesNot ruleCode dockerfile = assertChecks dockerfile f+ where+ f = failsWith 0 ruleCode++onBuildRuleCatchesNot ::+ (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>+ RuleCode ->+ Text.Text ->+ Assertion+onBuildRuleCatchesNot ruleCode dockerfile = assertOnBuildChecks dockerfile f+ where+ f = failsWith 0 ruleCode++formatChecksNoColor :: Foldable f => f CheckFailure -> Text.Text+formatChecksNoColor = Foldl.fold (Foldl.premap (\c -> formatCheck True "line" c <> "\n") Foldl.mconcat)++failsWithSome :: HasCallStack => RuleCode -> Failures -> Assertion+failsWithSome expectedCode failures =+ when (null matched) $+ assertFailure $ "I was expecting to catch at least one error for " <> show (unRuleCode expectedCode)+ where+ matched = Seq.filter (\CheckFailure {code} -> expectedCode == code) failures++failsWith :: HasCallStack => Int -> RuleCode -> Failures -> Assertion+failsWith times expectedCode failures =+ when (length matched /= times) $+ assertFailure $+ "I was expecting to catch exactly " <> show times <> " error(s) for " <> show (unRuleCode expectedCode) <> ". Found: \n"+ <> (Text.unpack . formatChecksNoColor $ matched)+ where+ matched = Seq.filter (\CheckFailure {code} -> expectedCode == code) failures++failsShellcheck :: HasCallStack => Failures -> Assertion+failsShellcheck checks =+ when (null matched) $+ assertFailure+ "I was expecting to catch at least one error with shellcheck"+ where+ matched = Seq.filter (\CheckFailure {code = RuleCode rc} -> "SC" `Text.isPrefixOf` rc) checks++passesShellcheck :: HasCallStack => Failures -> Assertion+passesShellcheck checks =+ unless (null matched) $+ assertFailure $+ "I was expecting to catch no errors with shellcheck. Found: \n"+ <> (Text.unpack . formatChecksNoColor $ matched)+ where+ matched = Seq.filter (\CheckFailure {code = RuleCode rc} -> "SC" `Text.isPrefixOf` rc) checks
test/ShellSpec.hs view
@@ -1,5 +1,3 @@-{-# LANGUAGE OverloadedStrings #-}- module ShellSpec where import Data.Text as Text@@ -11,32 +9,45 @@ describe "Shell unit tests" $ do -- it "getFlagArgs" $ do- let cmd = Command (Text.pack "useradd")- [ CmdPart (Text.pack "12345") 2,- CmdPart (Text.pack "67890") 4,- CmdPart (Text.pack "luser") 5 ]- [ CmdPart (Text.pack "u") 1,- CmdPart (Text.pack "u") 3 ]- in getFlagArg (Text.pack "u") cmd `shouldBe` [ Text.pack "12345",- Text.pack "67890" ]- let cmd = Command (Text.pack "useradd")- [ CmdPart (Text.pack "12345") 2,- CmdPart (Text.pack "luser") 3 ]- [ CmdPart (Text.pack "u") 1 ]- in getFlagArg (Text.pack "u") cmd `shouldBe` [ Text.pack "12345" ]+ 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 (Text.pack "useradd")- [ CmdPart (Text.pack "12345") 2,- CmdPart (Text.pack "luser") 3 ]- [ CmdPart (Text.pack "u") 1 ]- in getFlagArg (Text.pack "f") cmd `shouldBe` []+ let cmd = Command "useradd"+ [ CmdPart "12345" 2,+ CmdPart "luser" 3 ]+ [ CmdPart "u" 1 ]+ in getFlagArg "f" cmd `shouldBe` [] -- it "hasFlag" $ do- let cmd = Command (Text.pack "useradd")- [ CmdPart (Text.pack "luser") 1 ]- [ CmdPart (Text.pack "l") 0 ]- in hasFlag (Text.pack "l") cmd `shouldBe` True- let cmd = Command (Text.pack "useradd")- [ CmdPart (Text.pack "luser") 1 ]- [ CmdPart (Text.pack "l") 0 ]- in hasFlag (Text.pack "f") cmd `shouldBe` False+ 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 view
@@ -0,0 +1,121 @@+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 powershell" $+ let dockerFile =+ Text.unlines+ [ "SHELL [\"pwsh\", \"-c\"]",+ "RUN Get-Variable PSVersionTable | Select-Object -ExpandProperty Value"+ ]+ in do+ assertChecks dockerFile passesShellcheck+ assertOnBuildChecks dockerFile passesShellcheck
test/Spec.hs view
@@ -1,1998 +1,227 @@-{-# LANGUAGE ImplicitParams #-}-{-# LANGUAGE OverloadedLists #-}-{-# LANGUAGE OverloadedStrings #-}--import qualified ConfigSpec-import qualified Control.Foldl as Foldl-import Control.Monad (unless, when)-import qualified Data.Sequence as Seq-import qualified Data.Map as Map-import qualified Data.Text as Text-import Hadolint.Formatter.TTY (formatCheck, formatError)-import qualified Hadolint.Process-import Hadolint.Rule (CheckFailure (..), Failures, RuleCode (..))-import Hadolint.Rule as Rule-import Language.Docker.Parser-import Language.Docker.Syntax-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 "FROM rules" $ do- it "no untagged" $ ruleCatches "DL3006" "FROM debian"- it "no untagged with name" $ ruleCatches "DL3006" "FROM debian AS builder"- 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 platform flag" $ ruleCatches "DL3029" "FROM --platform=linux debian:jessie"- it "no platform flag" $ ruleCatchesNot "DL3029" "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"- 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- --- describe "no root or sudo rules" $ do- it "sudo" $ do- ruleCatches "DL3004" "RUN sudo apt-get update"- onBuildRuleCatches "DL3004" "RUN sudo apt-get update"-- 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-- 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"- --- describe "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"- --- describe "gem" $- 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"- --- describe "yum rules" $ do- it "yum update" $ do- ruleCatches "DL3031" "RUN yum update"- ruleCatchesNot "DL3031" "RUN yum install -y httpd-2.4.42 && yum clean all"- ruleCatchesNot "DL3031" "RUN bash -c `# not even a yum command`"- onBuildRuleCatches "DL3031" "RUN yum update"- onBuildRuleCatchesNot "DL3031" "RUN yum install -y httpd-2.4.42 && yum clean all"- onBuildRuleCatchesNot "DL3031" "RUN bash -c `# not even a yum command`"- it "yum version pinning" $ do- ruleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"- ruleCatchesNot "DL3033" "RUN yum install -y tomcat-9.2 && yum clean all"- ruleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"- onBuildRuleCatches "DL3033" "RUN yum install -y tomcat && yum clean all"- onBuildRuleCatchesNot "DL3033" "RUN yum install -y tomcat-9.2 && yum clean all"- onBuildRuleCatchesNot "DL3033" "RUN bash -c `# not even a yum command`"- it "yum no clean all" $ do- ruleCatches "DL3032" "RUN yum install -y mariadb-10.4"- ruleCatchesNot "DL3032" "RUN yum install -y mariadb-10.4 && yum clean all"- ruleCatchesNot "DL3032" "RUN bash -c `# not even a yum command`"- onBuildRuleCatches "DL3032" "RUN yum install -y mariadb-10.4"- onBuildRuleCatchesNot "DL3032" "RUN yum install -y mariadb-10.4 && yum clean all"- onBuildRuleCatchesNot "DL3032" "RUN bash -c `# not even a yum command`"- it "yum non-interactive" $ do- ruleCatches "DL3030" "RUN yum install httpd-2.4.24 && yum clean all"- ruleCatchesNot "DL3030" "RUN yum install -y httpd-2.4.24 && yum clean all"- ruleCatchesNot "DL3030" "RUN bash -c `# not even a yum command`"- onBuildRuleCatches "DL3030" "RUN yum install httpd-2.4.24 && yum clean all"- onBuildRuleCatchesNot "DL3030" "RUN yum install -y httpd-2.4.24 && yum clean all"- onBuildRuleCatchesNot "DL3030" "RUN bash -c `# not even a yum command`"- --- describe "zypper rules" $ do- it "zupper update" $ do- ruleCatches "DL3035" "RUN zypper update"- ruleCatches "DL3035" "RUN zypper up"- ruleCatches "DL3035" "RUN zypper dist-upgrade"- ruleCatches "DL3035" "RUN zypper dup"- onBuildRuleCatches "DL3035" "RUN zypper update"- onBuildRuleCatches "DL3035" "RUN zypper up"- onBuildRuleCatches "DL3035" "RUN zypper dist-upgrade"- onBuildRuleCatches "DL3035" "RUN zypper dup"- it "zypper version pinning" $ do- -- NOTE: In Haskell strings, '\' has to be escaped. And in shell commands, '>'- -- and '<' have to be escaped. Hence the double escaping.- ruleCatches "DL3037" "RUN zypper install -y tomcat && zypper clean"- 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"- onBuildRuleCatches "DL3037" "RUN zypper install -y tomcat && 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"- it "zypper no clean all" $ do- ruleCatches "DL3036" "RUN zypper install -y mariadb=10.4"- ruleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper clean"- ruleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper cc"- onBuildRuleCatches "DL3036" "RUN zypper install -y mariadb=10.4"- onBuildRuleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper clean"- onBuildRuleCatchesNot "DL3036" "RUN zypper install -y mariadb=10.4 && zypper cc"- it "zypper non-interactive" $ do- ruleCatches "DL3034" "RUN zypper install 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"- onBuildRuleCatches "DL3034" "RUN zypper install 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"- --- describe "dnf rules" $ do- it "dnf update" $ do- ruleCatches "DL3039" "RUN dnf upgrade"- onBuildRuleCatches "DL3039" "RUN dnf upgrade"- it "dnf version pinning" $ do- ruleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"- ruleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"- onBuildRuleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"- onBuildRuleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"- it "dnf no clean all" $ do- ruleCatches "DL3040" "RUN dnf install -y mariadb-10.4"- ruleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && dnf clean all"- onBuildRuleCatches "DL3040" "RUN dnf install -y mariadb-10.4"- onBuildRuleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && dnf clean all"- it "dnf non-interactive" $ do- ruleCatches "DL3038" "RUN dnf install httpd-2.4.24 && dnf clean all"- ruleCatchesNot "DL3038" "RUN dnf install -y httpd-2.4.24 && dnf clean all"- onBuildRuleCatches "DL3038" "RUN dnf install httpd-2.4.24 && dnf clean all"- onBuildRuleCatchesNot "DL3038" "RUN dnf install -y httpd-2.4.24 && dnf clean all"- --- describe "dnf rules" $ do- it "dnf update" $ do- ruleCatches "DL3039" "RUN dnf upgrade"- ruleCatches "DL3039" "RUN dnf upgrade-minimal"- ruleCatchesNot "DL3039" "RUN notdnf upgrade"- onBuildRuleCatches "DL3039" "RUN dnf upgrade"- onBuildRuleCatches "DL3039" "RUN dnf upgrade-minimal"- onBuildRuleCatchesNot "DL3039" "RUN notdnf upgrade"- it "dnf version pinning" $ do- ruleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"- ruleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"- ruleCatchesNot "DL3041" "RUN notdnf install tomcat"- onBuildRuleCatches "DL3041" "RUN dnf install -y tomcat && dnf clean all"- onBuildRuleCatchesNot "DL3041" "RUN dnf install -y tomcat-9.0.1 && dnf clean all"- onBuildRuleCatchesNot "DL3041" "RUN notdnf install tomcat"- it "dnf no clean all" $ do- ruleCatches "DL3040" "RUN dnf install -y mariadb-10.4"- ruleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && dnf clean all"- ruleCatchesNot "DL3040" "RUN notdnf install mariadb"- onBuildRuleCatches "DL3040" "RUN dnf install -y mariadb-10.4"- onBuildRuleCatchesNot "DL3040" "RUN dnf install -y mariadb-10.4 && dnf clean all"- onBuildRuleCatchesNot "DL3040" "RUN notdnf install mariadb"- it "dnf non-interactive" $ do- ruleCatches "DL3038" "RUN dnf install httpd-2.4.24 && dnf clean all"- ruleCatchesNot "DL3038" "RUN dnf install -y httpd-2.4.24 && dnf clean all"- ruleCatchesNot "DL3038" "RUN notdnf install httpd"- onBuildRuleCatches "DL3038" "RUN dnf install httpd-2.4.24 && dnf clean all"- onBuildRuleCatchesNot "DL3038" "RUN dnf install -y httpd-2.4.24 && dnf clean all"- onBuildRuleCatchesNot "DL3038" "RUN notdnf install httpd"- --- describe "apt-get rules" $ do- it "apt" $- let dockerFile =- [ "FROM ubuntu",- "RUN apt install python"- ]- in do- ruleCatches "DL3027" $ Text.unlines dockerFile- onBuildRuleCatches "DL3027" $ Text.unlines dockerFile- it "apt-get upgrade" $ do- ruleCatches "DL3005" "RUN apt-get update && apt-get upgrade"- onBuildRuleCatches "DL3005" "RUN apt-get update && apt-get upgrade"- 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"- 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 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 "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 "has deprecated maintainer" $- ruleCatches "DL4000" "FROM busybox\nMAINTAINER hudu@mail.com"- --- describe "apk add rules" $ do- it "apk upgrade" $ do- ruleCatches "DL3017" "RUN apk update && apk upgrade"- onBuildRuleCatches "DL3017" "RUN apk update && apk upgrade"- 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 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 "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- --- describe "EXPOSE rules" $ do- it "invalid port" $ ruleCatches "DL3011" "EXPOSE 80000"- it "valid port" $ ruleCatchesNot "DL3011" "EXPOSE 60000"- --- describe "pip pinning" $ 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"-- --- describe "pip cache dir" $ 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"- --- describe "npm pinning" $ 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"- --- describe "use 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"- --- --- 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 powershell" $- let dockerFile =- Text.unlines- [ "SHELL [\"pwsh\", \"-c\"]",- "RUN Get-Variable PSVersionTable | Select-Object -ExpandProperty Value"- ]- in do- assertChecks dockerFile passesShellcheck- assertOnBuildChecks dockerFile passesShellcheck- --- --- 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"- --- describe "`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 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- --- describe "other rules" $ 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"- 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"- 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-- 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"- it "using add" $ ruleCatches "DL3020" "ADD file /usr/src/app/"-- 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"- 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"- 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 "scratch" $ ruleCatchesNot "DL3006" "FROM scratch"- --- describe "add files and archives" $ do- it "add for tar" $ ruleCatchesNot "DL3020" "ADD file.tar /usr/src/app/"- it "add for zip" $ ruleCatchesNot "DL3020" "ADD file.zip /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/"- --- describe "copy last argument" $ 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/"- --- describe "copy from existing 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- --- describe "copy from own FROM" $ 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- --- describe "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- --- 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, or end of input "- case ast of- Left err -> assertEqual "Unexpected error msg" expectedMsg (formatError 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 "JSON notation in ENTRYPOINT and CMD" $ 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-- --- describe "Detects missing pipefail option" $ 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- --- describe "Allowed docker registries" $ 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- --- describe "Wget or Curl" $ 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")- --- describe "Wget no progress" $ 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- --- 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"- --- 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"- --- describe "ONBUILD" $ 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\""- --- describe "Selfreferencing `ENV`s" $ 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}\""- --- describe "warn when using `useradd` with long UID and without `-l`" $ 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"- --- describe "Missing label rule tests" $- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("foo", Rule.RawText)]) False- in 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")- describe "Strict Labels" $- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("required", Rule.RawText)]) True- in 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\""- describe "Label is not empty rule" $- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("emptylabel", Rule.RawText)]) False- in 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=\"\""- describe "Label is not URL rule" $- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("urllabel", Rule.Url)]) False- in 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\""- describe "Label is not RFC3339 date rule" $- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("datelabel", Rule.Rfc3339)]) False- in 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\""- describe "Label is not SPDX license identifier rule" $- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("spdxlabel", Rule.Spdx)]) False- in 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\""- describe "Label is not git hash rule" $- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("githash", Rule.GitHash)]) False- in 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\""- describe "Label is not semantic version rule" $- let ?rulesConfig = Hadolint.Process.RulesConfig [] (Map.fromList [("semver", Rule.SemVer)]) False- in 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\""- --- describe "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\""- --- 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--assertChecks ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>- 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--assertOnBuildChecks ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>- Text.Text ->- (Failures -> IO a) ->- IO a-assertOnBuildChecks dockerfile makeAssertions =- case parseText (dockerfile <> "\n") of- Left err -> assertFailure $ show err- Right dockerFile -> checkOnBuild dockerFile- where- checkOnBuild dockerFile = makeAssertions $ Hadolint.Process.run ?rulesConfig (fmap wrapInOnBuild dockerFile)- wrapInOnBuild (InstructionPos (Run args) so li) = InstructionPos (OnBuild (Run args)) so li- wrapInOnBuild i = i--hasInvalidLines :: Failures -> Bool-hasInvalidLines =- Foldl.fold- (Foldl.any (\CheckFailure {line} -> line <= 0))---- Assert a failed check exists for rule-ruleCatches ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>- RuleCode ->- Text.Text ->- Assertion-ruleCatches expectedCode dockerfile = assertChecks dockerfile f- where- f checks = do- failsWithSome expectedCode checks- assertBool "Incorrect line number for result" $ not $ hasInvalidLines checks--onBuildRuleCatches ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>- RuleCode ->- Text.Text ->- Assertion-onBuildRuleCatches ruleCode dockerfile = assertOnBuildChecks dockerfile f- where- f checks = do- failsWith 1 ruleCode checks- assertBool "Incorrect line number for result" $ not $ hasInvalidLines checks--ruleCatchesNot ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>- RuleCode ->- Text.Text ->- Assertion-ruleCatchesNot ruleCode dockerfile = assertChecks dockerfile f- where- f = failsWith 0 ruleCode--onBuildRuleCatchesNot ::- (HasCallStack, ?rulesConfig :: Hadolint.Process.RulesConfig) =>- RuleCode ->- Text.Text ->- Assertion-onBuildRuleCatchesNot ruleCode dockerfile = assertOnBuildChecks dockerfile f- where- f = failsWith 0 ruleCode--formatChecksNoColor :: Foldable f => f CheckFailure -> Text.Text-formatChecksNoColor = Foldl.fold (Foldl.premap (\c -> formatCheck True "line" c <> "\n") Foldl.mconcat)--failsWithSome :: HasCallStack => RuleCode -> Failures -> Assertion-failsWithSome expectedCode failures =- when (null matched) $- assertFailure $ "I was expecting to catch at least one error for " <> show (unRuleCode expectedCode)- where- matched = Seq.filter (\CheckFailure {code} -> expectedCode == code) failures--failsWith :: HasCallStack => Int -> RuleCode -> Failures -> Assertion-failsWith times expectedCode failures =- when (length matched /= times) $- assertFailure $- "I was expecting to catch exactly " <> show times <> " error(s) for " <> show (unRuleCode expectedCode) <> ". Found: \n"- <> (Text.unpack . formatChecksNoColor $ matched)- where- matched = Seq.filter (\CheckFailure {code} -> expectedCode == code) failures--failsShellcheck :: HasCallStack => Failures -> Assertion-failsShellcheck checks =- when (null matched) $- assertFailure- "I was expecting to catch at least one error with shellcheck"- where- matched = Seq.filter (\CheckFailure {code = RuleCode rc} -> "SC" `Text.isPrefixOf` rc) checks--passesShellcheck :: HasCallStack => Failures -> Assertion-passesShellcheck checks =- unless (null matched) $- assertFailure $- "I was expecting to catch no errors with shellcheck. Found: \n"- <> (Text.unpack . formatChecksNoColor $ matched)- where- matched = Seq.filter (\CheckFailure {code = RuleCode rc} -> "SC" `Text.isPrefixOf` rc) checks+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 DL3017+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 DL3031+import qualified DL3032+import qualified DL3033+import qualified DL3034+import qualified DL3035+import qualified DL3036+import qualified DL3037+import qualified DL3038+import qualified DL3039+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 DL4001+import qualified DL4003+import qualified DL4004+import qualified DL4005+import qualified DL4006+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, or end of input "+ case ast of+ Left err -> assertEqual "Unexpected error msg" expectedMsg (formatError 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+ DL3017.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+ DL3031.tests+ DL3032.tests+ DL3033.tests+ DL3034.tests+ DL3035.tests+ DL3036.tests+ DL3037.tests+ DL3038.tests+ DL3039.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+ DL4001.tests+ DL4003.tests+ DL4004.tests+ DL4005.tests+ DL4006.tests