packages feed

language-docker 10.0.2 → 10.1.0

raw patch · 7 files changed

+571/−339 lines, 7 files

Files

language-docker.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 0acabc7fcd899e8d19189dd6a3e3541b451fb5e35c3ed9751f8f0261404d8197+-- hash: 3b38c6eec0fc0543e696e51fa5a654074e9718ae41bd50f4114824964938c535  name:           language-docker-version:        10.0.2+version:        10.1.0 synopsis:       Dockerfile parser, pretty-printer and embedded DSL description:    All functions for parsing and pretty-printing Dockerfiles are exported through @Language.Docker@. For more fine-grained operations look for specific modules that implement a certain functionality.                 See the <https://github.com/hadolint/language-docker GitHub project> for the source-code and examples.@@ -81,8 +81,10 @@   main-is: Spec.hs   other-modules:       Language.Docker.IntegrationSpec+      Language.Docker.ParseCopySpec       Language.Docker.ParsePragmaSpec       Language.Docker.ParserSpec+      Language.Docker.ParseRunSpec       Language.Docker.PrettyPrintSpec       TestHelper       Paths_language_docker
src/Language/Docker/Parser/Arguments.hs view
@@ -15,9 +15,17 @@  -- Parse arguments of a command in the shell form argumentsShell :: (?esc :: Char) => Parser (Arguments Text)-argumentsShell = ArgumentsText <$> toEnd+argumentsShell =+  try (ArgumentsText <$> untilHeredoc)+    <|> (ArgumentsText <$> toEnd)   where     toEnd = untilEol "the shell arguments" +-- Parse arguments of a command in the heredoc format+argumentsHeredoc :: Parser (Arguments Text)+argumentsHeredoc = ArgumentsText <$> heredoc+ arguments :: (?esc :: Char) => Parser (Arguments Text)-arguments = try argumentsExec <|> try argumentsShell+arguments = try argumentsHeredoc+  <|> try argumentsExec+  <|> try argumentsShell
src/Language/Docker/Parser/Copy.hs view
@@ -42,12 +42,13 @@             case sourceFlags of               [] -> NoSource               f : _ -> f-      fileList "COPY" (\src dest -> Copy (CopyArgs src dest cho chm fr))+      try (heredocList (\src dest -> Copy (CopyArgs src dest cho chm fr)))+        <|> fileList "COPY" (\src dest -> Copy (CopyArgs src dest cho chm fr))  parseAdd :: (?esc :: Char) => Parser (Instruction Text) parseAdd = do   reserved "ADD"-  flags <- copyFlag `sepEndBy` requiredWhitespace+  flags <- addFlag `sepEndBy` requiredWhitespace   let chownFlags = [c | FlagChown c <- flags]   let chmodFlags = [c | FlagChmod c <- flags]   let invalidFlags = [i | FlagInvalid i <- flags]@@ -66,6 +67,17 @@                   c : _ -> c       fileList "ADD" (\src dest -> Add (AddArgs src dest cho chm)) +heredocList :: (?esc :: Char) =>+               (NonEmpty SourcePath -> TargetPath -> Instruction Text) ->+               Parser (Instruction Text)+heredocList constr = do+  markers <- try (spaceSep1 heredocMarker) <?> "a list of heredoc markers"+  target <- untilEol "target path"+  case reverse markers of+    [] -> customError $ FileListError "empty list of heredoc markers"+    m:_ -> void $ heredocContent m+  return $ constr (SourcePath <$> fromList markers) (TargetPath target)+ fileList :: (?esc :: Char) => Text ->             (NonEmpty SourcePath -> TargetPath -> Instruction Text) ->             Parser (Instruction Text)@@ -86,11 +98,12 @@ unexpectedFlag name _ = customFailure $ InvalidFlagError (T.unpack name)  copyFlag :: (?esc :: Char) => Parser CopyFlag-copyFlag =-  (FlagChown <$> try chown <?> "only one --chown")-    <|> (FlagChmod <$> try chmod <?> "only one --chmod")-    <|> (FlagSource <$> try copySource <?> "only one --from")-    <|> (FlagInvalid <$> try anyFlag <?> "no other flags")+copyFlag = (FlagSource <$> try copySource <?> "only one --from") <|> addFlag++addFlag :: (?esc :: Char) => Parser CopyFlag+addFlag = (FlagChown <$> try chown <?> "--chown")+  <|> (FlagChmod <$> try chmod <?> "--chmod")+  <|> (FlagInvalid <$> try anyFlag <?> "other flag")  chown :: (?esc :: Char) => Parser Chown chown = do
src/Language/Docker/Parser/Prelude.hs view
@@ -7,11 +7,16 @@     reserved,     natural,     commaSep,+    spaceSep1,     stringLiteral,     brackets,+    heredoc,+    heredocMarker,+    heredocContent,     whitespace,     requiredWhitespace,     untilEol,+    untilHeredoc,     symbol,     onlySpaces,     onlyWhitespaces,@@ -126,14 +131,70 @@ commaSep :: (?esc :: Char) => Parser a -> Parser [a] commaSep p = sepBy (p <* whitespace) (symbol ",") +spaceSep1 :: Parser a -> Parser [a]+spaceSep1 p = sepEndBy1 p onlySpaces++-- | Note this is just an alias for compatibility stringLiteral :: Parser Text-stringLiteral = do-  void (char '"')-  lit <- manyTill L.charLiteral (char '"')-  return (T.pack lit)+stringLiteral = doubleQuotedString +singleQuotedString :: Parser Text+singleQuotedString = quotedString '\''++doubleQuotedString :: Parser Text+doubleQuotedString = quotedString '\"'++quotedString :: Char -> Parser Text+quotedString c = do+  void $ char c+  lit <- manyTill L.charLiteral (char c)+  return $ T.pack lit+ brackets :: (?esc :: Char) => Parser a -> Parser a brackets = between (symbol "[" *> whitespace) (whitespace *> symbol "]")++justWhitespace :: Parser Text+justWhitespace = do+  c <- choice+    [ char ' ',+      char '\t',+      char '\n'+    ]+  return (T.pack [c])++untilWS :: Parser Text+untilWS = do+  s <- manyTill L.charLiteral justWhitespace+  return $ T.pack s++heredocMarker :: Parser Text+heredocMarker = do+  void $ string "<<"+  void $ takeWhileP (Just "dash") (== '-')+  m <- try doubleQuotedString <|> try singleQuotedString <|> untilWS+  optional heredocRedirect+  pure m++heredocRedirect :: Parser Text+heredocRedirect = do+  void $ char '>'+  takeWhileP (Just "heredoc path") (/= '\n')++heredocContent :: Text -> Parser Text+heredocContent marker = do+  doc <- manyTill L.charLiteral (string marker)+  return $ T.strip $ T.pack doc++heredoc :: Parser Text+heredoc = do+  m <- heredocMarker+  heredocContent m++-- | Parses text until a heredoc is found. Will also consume the heredoc.+untilHeredoc :: Parser Text+untilHeredoc = do+  txt <- manyTill L.charLiteral heredoc+  return $ T.strip $ T.pack txt  onlySpaces :: Parser Text onlySpaces = takeWhileP (Just "spaces") (\c -> c == ' ' || c == '\t')
+ test/Language/Docker/ParseCopySpec.hs view
@@ -0,0 +1,138 @@+module Language.Docker.ParseCopySpec where++import qualified Data.Text as Text+import Language.Docker.Parser+import Language.Docker.Syntax+import TestHelper+import Test.HUnit hiding (Label)+import Test.Hspec+++spec :: Spec+spec = do+  describe "regular Copy" $ do+    it "simple COPY" $+      let file = Text.unlines ["COPY . /app", "COPY baz /some/long/path"]+       in assertAst+            file+            [ Copy $ CopyArgs [SourcePath "."] (TargetPath "/app") NoChown NoChmod NoSource,+              Copy $ CopyArgs [SourcePath "baz"] (TargetPath "/some/long/path") NoChown NoChmod NoSource+            ]+    it "multifiles COPY" $+      let file = Text.unlines ["COPY foo bar baz /app"]+       in assertAst+            file+            [ Copy $ CopyArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") NoChown NoChmod NoSource+            ]+    it "list of quoted files" $+      let file = Text.unlines ["COPY [\"foo\", \"bar\", \"baz\", \"/app\"]"]+       in assertAst+            file+            [ Copy $ CopyArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") NoChown NoChmod NoSource+            ]+    it "supports windows paths" $+      let file = Text.unlines ["COPY C:\\\\go C:\\\\go"]+       in assertAst+            file+            [ Copy $ CopyArgs (fmap SourcePath ["C:\\\\go"]) (TargetPath "C:\\\\go") NoChown NoChmod NoSource+            ]+    it "does not get confused with trailing whitespace" $+      let file = Text.unlines ["COPY a b  "]+       in assertAst+            file+            [ Copy $ CopyArgs [SourcePath "a"] (TargetPath "b") NoChown NoChmod NoSource+            ]++  describe "Copy with flags" $ do+    it "with chown flag" $+      let file = Text.unlines ["COPY --chown=user:group foo bar"]+       in assertAst+            file+            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "user:group") NoChmod NoSource+            ]+    it "with chmod flag" $+      let file = Text.unlines ["COPY --chmod=777 foo bar"]+       in assertAst+            file+            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") NoChown (Chmod "777") NoSource+            ]+    it "with from flag" $+      let file = Text.unlines ["COPY --from=node foo bar"]+       in assertAst+            file+            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") NoChown NoChmod (CopySource "node")+            ]+    it "with all three flags" $+      let file = Text.unlines ["COPY --from=node --chmod=751 --chown=user:group foo bar"]+       in assertAst+            file+            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "user:group") (Chmod "751") (CopySource "node")+            ]+    it "with all three flags in different order" $+      let file = Text.unlines ["COPY --chown=user:group --from=node --chmod=644 foo bar"]+       in assertAst+            file+            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "user:group") (Chmod "644") (CopySource "node")+            ]++  describe "Copy with Heredocs" $ do+    it "empty heredoc" $+      let file = Text.unlines ["COPY <<EOF /target", "EOF"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "EOF"] (TargetPath "/target") NoChown NoChmod NoSource+              ]+    it "foo heredoc" $+      let file = Text.unlines ["COPY <<FOO /target", "foo", "FOO"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "FOO"] (TargetPath "/target") NoChown NoChmod NoSource+              ]+    it "foo heredoc lowercase +extensions" $+      let file = Text.unlines ["COPY <<foo.txt /target", "foo content", "line 2", "foo.txt"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "foo.txt"] (TargetPath "/target") NoChown NoChmod NoSource+              ]+    it "foo heredoc single quoted marker" $+      let file = Text.unlines ["COPY <<\'FOO\' /target", "foo", "FOO"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "FOO"] (TargetPath "/target") NoChown NoChmod NoSource+              ]+    it "foo heredoc double quoted marker" $+      let file = Text.unlines ["COPY <<\"FOO\" /target", "foo", "FOO"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "FOO"] (TargetPath "/target") NoChown NoChmod NoSource+              ]+    it "foo heredoc +dash" $+      let file = Text.unlines ["COPY <<-FOO /target", "foo", "FOO"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "FOO"] (TargetPath "/target") NoChown NoChmod NoSource+              ]+    it "foo heredoc single quoted marker +dash" $+      let file = Text.unlines ["COPY <<-\'FOO\' /target", "foo", "FOO"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "FOO"] (TargetPath "/target") NoChown NoChmod NoSource+              ]+    it "foo heredoc double quoted marker +dash" $+      let file = Text.unlines ["COPY <<-\"FOO\" /target", "foo", "FOO"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "FOO"] (TargetPath "/target") NoChown NoChmod NoSource+              ]+    it "multiple heredocs" $+      let file = Text.unlines ["COPY <<FOO <<BAR /target", "foo", "FOO", "bar", "BAR"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "FOO", SourcePath "BAR"] (TargetPath "/target") NoChown NoChmod NoSource+              ]+    it "multiple heredocs with single/double quotes and dash mixed" $+      let file = Text.unlines ["COPY <<FOO <<-\"BAR\" <<\'FIZZ\' <<-BUZZ /target", "foo", "FOO", "bar", "BAR", "fizz", "FIZZ", "buss", "BUZZ"]+       in assertAst+            file+              [ Copy $ CopyArgs [SourcePath "FOO", SourcePath "BAR", SourcePath "FIZZ", SourcePath "BUZZ"] (TargetPath "/target") NoChown NoChmod NoSource+              ]
+ test/Language/Docker/ParseRunSpec.hs view
@@ -0,0 +1,334 @@+module Language.Docker.ParseRunSpec where++import Data.Default.Class (def)+import qualified Data.Text as Text+import Language.Docker.Parser+import Language.Docker.Syntax+import TestHelper+import Test.HUnit hiding (Label)+import Test.Hspec+++spec :: Spec+spec = do+  describe "parse RUN instructions" $ do+    it "escaped with space before" $+      let dockerfile = Text.unlines ["RUN yum install -y \\", "imagemagick \\", "mysql"]+       in assertAst dockerfile [Run "yum install -y imagemagick mysql"]+    it "does not choke on unmatched brackets" $+      let dockerfile = Text.unlines ["RUN [foo"]+       in assertAst dockerfile [Run "[foo"]+    it "Distinguishes between text and a list" $+      let dockerfile =+            Text.unlines+              [ "RUN echo foo",+                "RUN [\"echo\", \"foo\"]"+              ]+       in assertAst dockerfile [Run $ RunArgs (ArgumentsText "echo foo") def, Run $ RunArgs (ArgumentsList "echo foo") def]+    it "Accepts spaces inside the brackets" $+      let dockerfile =+            Text.unlines+              [ "RUN [  \"echo\", \"foo\"  ]"+              ]+       in assertAst dockerfile [Run $ RunArgs (ArgumentsList "echo foo") def]++  describe "RUN with experimental flags" $ do+    it "--mount=type=bind and target" $+      let file = Text.unlines ["RUN --mount=type=bind,target=/foo echo foo"]+          flags = def {mount = Just $ BindMount (def {bTarget = "/foo"})}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount default to bind" $+      let file = Text.unlines ["RUN --mount=target=/foo echo foo"]+          flags = def {mount = Just $ BindMount (def {bTarget = "/foo"})}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=bind all modifiers" $+      let file = Text.unlines ["RUN --mount=type=bind,target=/foo,source=/bar,from=ubuntu,ro echo foo"]+          flags = def {mount = Just $ BindMount (BindOpts {bTarget = "/foo", bSource = Just "/bar", bFromImage = Just "ubuntu", bReadOnly = Just True})}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=cache with target" $+      let file =+            Text.unlines+              [ "RUN --mount=type=cache,target=/foo echo foo",+                "RUN --mount=type=cache,target=/bar echo foo",+                "RUN --mount=type=cache,target=/baz echo foo"+              ]+          flags1 = def {mount = Just $ CacheMount (def {cTarget = "/foo"})}+          flags2 = def {mount = Just $ CacheMount (def {cTarget = "/bar"})}+          flags3 = def {mount = Just $ CacheMount (def {cTarget = "/baz"})}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags1,+              Run $ RunArgs (ArgumentsText "echo foo") flags2,+              Run $ RunArgs (ArgumentsText "echo foo") flags3+            ]+    it "--mount=type=cache with all modifiers" $+      let file =+            Text.unlines+              [ "RUN --mount=type=cache,target=/foo,sharing=private,id=a,ro,from=ubuntu,source=/bar,mode=0700,uid=0,gid=0 echo foo"+              ]+          flags =+            def+              { mount =+                  Just $+                    CacheMount+                      ( def+                          { cTarget = "/foo",+                            cSharing = Just Private,+                            cCacheId = Just "a",+                            cReadOnly = Just True,+                            cFromImage = Just "ubuntu",+                            cSource = Just "/bar",+                            cMode = Just "0700",+                            cUid = Just 0,+                            cGid = Just 0+                          }+                      )+              }+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=tmpfs" $+      let file = Text.unlines ["RUN --mount=type=tmpfs,target=/foo echo foo"]+          flags = def {mount = Just $ TmpfsMount (def {tTarget = "/foo"})}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=ssh" $+      let file = Text.unlines ["RUN --mount=type=ssh echo foo"]+          flags = def {mount = Just $ SshMount def}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=ssh,required=false" $+      let file = Text.unlines ["RUN --mount=type=ssh,required=false echo foo"]+          flags = def {mount = Just $ SshMount def {sIsRequired = Just False}}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=ssh,required=False" $+      let file = Text.unlines ["RUN --mount=type=ssh,required=False echo foo"]+          flags = def {mount = Just $ SshMount def {sIsRequired = Just False}}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=secret,required=true" $+      let file = Text.unlines ["RUN --mount=type=secret,required=true echo foo"]+          flags = def {mount = Just $ SecretMount def {sIsRequired = Just True}}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=secret,required=True" $+      let file = Text.unlines ["RUN --mount=type=secret,required=True echo foo"]+          flags = def {mount = Just $ SecretMount def {sIsRequired = Just True}}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=ssh all modifiers" $+      let file = Text.unlines ["RUN --mount=type=ssh,target=/foo,id=a,required,source=/bar,mode=0700,uid=0,gid=0 echo foo"]+          flags =+            def+              { mount =+                  Just $+                    SshMount+                      ( def+                          { sTarget = Just "/foo",+                            sCacheId = Just "a",+                            sIsRequired = Just True,+                            sSource = Just "/bar",+                            sMode = Just "0700",+                            sUid = Just 0,+                            sGid = Just 0+                          }+                      )+              }+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=ssh all modifiers, required explicit" $+      let file = Text.unlines ["RUN --mount=type=ssh,target=/foo,id=a,required=true,source=/bar,mode=0700,uid=0,gid=0 echo foo"]+          flags =+            def+              { mount =+                  Just $+                    SshMount+                      ( def+                          { sTarget = Just "/foo",+                            sCacheId = Just "a",+                            sIsRequired = Just True,+                            sSource = Just "/bar",+                            sMode = Just "0700",+                            sUid = Just 0,+                            sGid = Just 0+                          }+                      )+              }+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=secret all modifiers" $+      let file = Text.unlines ["RUN --mount=type=secret,target=/foo,id=a,required,source=/bar,mode=0700,uid=0,gid=0 echo foo"]+          flags =+            def+              { mount =+                  Just $+                    SecretMount+                      ( def+                          { sTarget = Just "/foo",+                            sCacheId = Just "a",+                            sIsRequired = Just True,+                            sSource = Just "/bar",+                            sMode = Just "0700",+                            sUid = Just 0,+                            sGid = Just 0+                          }+                      )+              }+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--mount=type=secret all modifiers, required explicit" $+      let file = Text.unlines ["RUN --mount=type=secret,target=/foo,id=a,required=true,source=/bar,mode=0700,uid=0,gid=0 echo foo"]+          flags =+            def+              { mount =+                  Just $+                    SecretMount+                      ( def+                          { sTarget = Just "/foo",+                            sCacheId = Just "a",+                            sIsRequired = Just True,+                            sSource = Just "/bar",+                            sMode = Just "0700",+                            sUid = Just 0,+                            sGid = Just 0+                          }+                      )+              }+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--network=none" $+      let file = Text.unlines ["RUN --network=none echo foo"]+          flags = def {network = Just NetworkNone}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--network=host" $+      let file = Text.unlines ["RUN --network=host echo foo"]+          flags = def {network = Just NetworkHost}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--network=default" $+      let file = Text.unlines ["RUN --network=default echo foo"]+          flags = def {network = Just NetworkDefault}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--security=insecure" $+      let file = Text.unlines ["RUN --security=insecure echo foo"]+          flags = def {security = Just Insecure}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "--security=sandbox" $+      let file = Text.unlines ["RUN --security=sandbox echo foo"]+          flags = def {security = Just Sandbox}+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]+    it "allows all flags" $+      let file = Text.unlines ["RUN --mount=target=/foo --network=none --security=sandbox echo foo"]+          flags =+            def+              { security = Just Sandbox,+                network = Just NetworkNone,+                mount = Just $ BindMount $ def {bTarget = "/foo"}+              }+       in assertAst+            file+            [ Run $ RunArgs (ArgumentsText "echo foo") flags+            ]++  describe "parse RUN in heredocs format" $ do+    it "foo heredoc" $+      let file = Text.unlines [ "RUN <<EOF", "foo", "EOF"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo") flags ]+    it "foo heredoc marker" $+      let file = Text.unlines [ "RUN <<FOO", "foo", "FOO"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo") flags ]+    it "foo heredoc quoted marker" $+      let file = Text.unlines [ "RUN <<\"FOO\"", "foo", "FOO"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo") flags ]+    it "foo heredoc single quoted marker" $+      let file = Text.unlines [ "RUN <<\'FOO\'", "foo", "FOO"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo") flags ]+    it "foo heredoc +dash" $+      let file = Text.unlines [ "RUN <<-FOO", "foo", "FOO"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo") flags ]+    it "foo heredoc quoted +dash" $+      let file = Text.unlines [ "RUN <<-\"FOO\"", "foo", "FOO"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo") flags ]+    it "foo heredoc single quoted +dash" $+      let file = Text.unlines [ "RUN <<-\'FOO\'", "foo", "FOO"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo") flags ]+    it "multiline foo heredoc" $+      let file = Text.unlines [ "RUN <<EOF", "foo", "bar", "", "dodo", "EOF"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo\nbar\n\ndodo") flags ]+    it "heredoc with shebang/commend" $+      let file = Text.unlines [ "RUN <<EOF", "#/usr/bin/env python", "", "#print foo", "print(\"foo\")", "EOF" ]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "#/usr/bin/env python\n\n#print foo\nprint(\"foo\")") flags ]+    it "empty heredoc" $+      let file = Text.unlines [ "RUN <<EOF", "EOF"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "") flags ]+    it "evil heredoc" $+      let file = Text.unlines [ "RUN <<EOF foo", "bar EOF"]+          flags = def { security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo\nbar") flags ]+    it "heredoc with redirection to file" $+      let file = Text.unlines [ "RUN <<EOF > /file", "foo", "EOF" ]+          flags = def {security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "foo") flags ]+    it "heredoc to program stdin" $+      let file = Text.unlines [ "RUN python <<EOF", "print(\"foo\")", "EOF" ]+          flags = def {security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "python") flags ]+    it "heredoc to program stdin with redirect to file" $+      let file = Text.unlines [ "RUN python <<EOF > /file", "print(\"foo\")", "EOF" ]+          flags = def {security = Nothing }+       in assertAst file [ Run $ RunArgs (ArgumentsText "python") flags ]
test/Language/Docker/ParserSpec.hs view
@@ -174,26 +174,6 @@             [ Env [("PHP_FPM_ACCESS_FORMAT", "%R - %u %t \"%m %r\" %s")]             ]        in assertAst dockerfile ast-  describe "parse RUN" $ do-    it "escaped with space before" $-      let dockerfile = Text.unlines ["RUN yum install -y \\", "imagemagick \\", "mysql"]-       in assertAst dockerfile [Run "yum install -y imagemagick mysql"]-    it "does not choke on unmatched brackets" $-      let dockerfile = Text.unlines ["RUN [foo"]-       in assertAst dockerfile [Run "[foo"]-    it "Distinguishes between text and a list" $-      let dockerfile =-            Text.unlines-              [ "RUN echo foo",-                "RUN [\"echo\", \"foo\"]"-              ]-       in assertAst dockerfile [Run $ RunArgs (ArgumentsText "echo foo") def, Run $ RunArgs (ArgumentsList "echo foo") def]-    it "Accepts spaces inside the brackets" $-      let dockerfile =-            Text.unlines-              [ "RUN [  \"echo\", \"foo\"  ]"-              ]-       in assertAst dockerfile [Run $ RunArgs (ArgumentsList "echo foo") def]   describe "parse CMD" $ do     it "one line cmd" $ assertAst "CMD true" [Cmd "true"]     it "cmd over several lines" $@@ -413,308 +393,4 @@        in assertAst             file             [ Add $ AddArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") (Chown "user:group") NoChmod-            ]-  describe "COPY" $ do-    it "simple COPY" $-      let file = Text.unlines ["COPY . /app", "COPY baz /some/long/path"]-       in assertAst-            file-            [ Copy $ CopyArgs [SourcePath "."] (TargetPath "/app") NoChown NoChmod NoSource,-              Copy $ CopyArgs [SourcePath "baz"] (TargetPath "/some/long/path") NoChown NoChmod NoSource-            ]-    it "multifiles COPY" $-      let file = Text.unlines ["COPY foo bar baz /app"]-       in assertAst-            file-            [ Copy $ CopyArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") NoChown NoChmod NoSource-            ]-    it "list of quoted files" $-      let file = Text.unlines ["COPY [\"foo\", \"bar\", \"baz\", \"/app\"]"]-       in assertAst-            file-            [ Copy $ CopyArgs (fmap SourcePath ["foo", "bar", "baz"]) (TargetPath "/app") NoChown NoChmod NoSource-            ]-    it "with chown flag" $-      let file = Text.unlines ["COPY --chown=user:group foo bar"]-       in assertAst-            file-            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "user:group") NoChmod NoSource-            ]-    it "with chmod flag" $-      let file = Text.unlines ["COPY --chmod=777 foo bar"]-       in assertAst-            file-            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") NoChown (Chmod "777") NoSource-            ]-    it "with from flag" $-      let file = Text.unlines ["COPY --from=node foo bar"]-       in assertAst-            file-            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") NoChown NoChmod (CopySource "node")-            ]-    it "with all three flags" $-      let file = Text.unlines ["COPY --from=node --chmod=751 --chown=user:group foo bar"]-       in assertAst-            file-            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "user:group") (Chmod "751") (CopySource "node")-            ]-    it "with all three flags in different order" $-      let file = Text.unlines ["COPY --chown=user:group --from=node --chmod=644 foo bar"]-       in assertAst-            file-            [ Copy $ CopyArgs (fmap SourcePath ["foo"]) (TargetPath "bar") (Chown "user:group") (Chmod "644") (CopySource "node")-            ]-    it "supports windows paths" $-      let file = Text.unlines ["COPY C:\\\\go C:\\\\go"]-       in assertAst-            file-            [ Copy $ CopyArgs (fmap SourcePath ["C:\\\\go"]) (TargetPath "C:\\\\go") NoChown NoChmod NoSource-            ]-    it "does not get confused with trailing whitespace" $-      let file = Text.unlines ["COPY a b  "]-       in assertAst-            file-            [ Copy $ CopyArgs [SourcePath "a"] (TargetPath "b") NoChown NoChmod NoSource-            ]-  describe "RUN with experimental flags" $ do-    it "--mount=type=bind and target" $-      let file = Text.unlines ["RUN --mount=type=bind,target=/foo echo foo"]-          flags = def {mount = Just $ BindMount (def {bTarget = "/foo"})}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount default to bind" $-      let file = Text.unlines ["RUN --mount=target=/foo echo foo"]-          flags = def {mount = Just $ BindMount (def {bTarget = "/foo"})}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=bind all modifiers" $-      let file = Text.unlines ["RUN --mount=type=bind,target=/foo,source=/bar,from=ubuntu,ro echo foo"]-          flags = def {mount = Just $ BindMount (BindOpts {bTarget = "/foo", bSource = Just "/bar", bFromImage = Just "ubuntu", bReadOnly = Just True})}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=cache with target" $-      let file =-            Text.unlines-              [ "RUN --mount=type=cache,target=/foo echo foo",-                "RUN --mount=type=cache,target=/bar echo foo",-                "RUN --mount=type=cache,target=/baz echo foo"-              ]-          flags1 = def {mount = Just $ CacheMount (def {cTarget = "/foo"})}-          flags2 = def {mount = Just $ CacheMount (def {cTarget = "/bar"})}-          flags3 = def {mount = Just $ CacheMount (def {cTarget = "/baz"})}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags1,-              Run $ RunArgs (ArgumentsText "echo foo") flags2,-              Run $ RunArgs (ArgumentsText "echo foo") flags3-            ]-    it "--mount=type=cache with all modifiers" $-      let file =-            Text.unlines-              [ "RUN --mount=type=cache,target=/foo,sharing=private,id=a,ro,from=ubuntu,source=/bar,mode=0700,uid=0,gid=0 echo foo"-              ]-          flags =-            def-              { mount =-                  Just $-                    CacheMount-                      ( def-                          { cTarget = "/foo",-                            cSharing = Just Private,-                            cCacheId = Just "a",-                            cReadOnly = Just True,-                            cFromImage = Just "ubuntu",-                            cSource = Just "/bar",-                            cMode = Just "0700",-                            cUid = Just 0,-                            cGid = Just 0-                          }-                      )-              }-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=tmpfs" $-      let file = Text.unlines ["RUN --mount=type=tmpfs,target=/foo echo foo"]-          flags = def {mount = Just $ TmpfsMount (def {tTarget = "/foo"})}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=ssh" $-      let file = Text.unlines ["RUN --mount=type=ssh echo foo"]-          flags = def {mount = Just $ SshMount def}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=ssh,required=false" $-      let file = Text.unlines ["RUN --mount=type=ssh,required=false echo foo"]-          flags = def {mount = Just $ SshMount def {sIsRequired = Just False}}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=ssh,required=False" $-      let file = Text.unlines ["RUN --mount=type=ssh,required=False echo foo"]-          flags = def {mount = Just $ SshMount def {sIsRequired = Just False}}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=secret,required=true" $-      let file = Text.unlines ["RUN --mount=type=secret,required=true echo foo"]-          flags = def {mount = Just $ SecretMount def {sIsRequired = Just True}}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=secret,required=True" $-      let file = Text.unlines ["RUN --mount=type=secret,required=True echo foo"]-          flags = def {mount = Just $ SecretMount def {sIsRequired = Just True}}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=ssh all modifiers" $-      let file = Text.unlines ["RUN --mount=type=ssh,target=/foo,id=a,required,source=/bar,mode=0700,uid=0,gid=0 echo foo"]-          flags =-            def-              { mount =-                  Just $-                    SshMount-                      ( def-                          { sTarget = Just "/foo",-                            sCacheId = Just "a",-                            sIsRequired = Just True,-                            sSource = Just "/bar",-                            sMode = Just "0700",-                            sUid = Just 0,-                            sGid = Just 0-                          }-                      )-              }-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=ssh all modifiers, required explicit" $-      let file = Text.unlines ["RUN --mount=type=ssh,target=/foo,id=a,required=true,source=/bar,mode=0700,uid=0,gid=0 echo foo"]-          flags =-            def-              { mount =-                  Just $-                    SshMount-                      ( def-                          { sTarget = Just "/foo",-                            sCacheId = Just "a",-                            sIsRequired = Just True,-                            sSource = Just "/bar",-                            sMode = Just "0700",-                            sUid = Just 0,-                            sGid = Just 0-                          }-                      )-              }-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=secret all modifiers" $-      let file = Text.unlines ["RUN --mount=type=secret,target=/foo,id=a,required,source=/bar,mode=0700,uid=0,gid=0 echo foo"]-          flags =-            def-              { mount =-                  Just $-                    SecretMount-                      ( def-                          { sTarget = Just "/foo",-                            sCacheId = Just "a",-                            sIsRequired = Just True,-                            sSource = Just "/bar",-                            sMode = Just "0700",-                            sUid = Just 0,-                            sGid = Just 0-                          }-                      )-              }-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--mount=type=secret all modifiers, required explicit" $-      let file = Text.unlines ["RUN --mount=type=secret,target=/foo,id=a,required=true,source=/bar,mode=0700,uid=0,gid=0 echo foo"]-          flags =-            def-              { mount =-                  Just $-                    SecretMount-                      ( def-                          { sTarget = Just "/foo",-                            sCacheId = Just "a",-                            sIsRequired = Just True,-                            sSource = Just "/bar",-                            sMode = Just "0700",-                            sUid = Just 0,-                            sGid = Just 0-                          }-                      )-              }-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--network=none" $-      let file = Text.unlines ["RUN --network=none echo foo"]-          flags = def {network = Just NetworkNone}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--network=host" $-      let file = Text.unlines ["RUN --network=host echo foo"]-          flags = def {network = Just NetworkHost}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--network=default" $-      let file = Text.unlines ["RUN --network=default echo foo"]-          flags = def {network = Just NetworkDefault}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--security=insecure" $-      let file = Text.unlines ["RUN --security=insecure echo foo"]-          flags = def {security = Just Insecure}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "--security=sandbox" $-      let file = Text.unlines ["RUN --security=sandbox echo foo"]-          flags = def {security = Just Sandbox}-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags-            ]-    it "allows all flags" $-      let file = Text.unlines ["RUN --mount=target=/foo --network=none --security=sandbox echo foo"]-          flags =-            def-              { security = Just Sandbox,-                network = Just NetworkNone,-                mount = Just $ BindMount $ def {bTarget = "/foo"}-              }-       in assertAst-            file-            [ Run $ RunArgs (ArgumentsText "echo foo") flags             ]